【问题标题】:How to protect webfonts?如何保护网络字体?
【发布时间】:2014-02-23 13:24:01
【问题描述】:

我的客户购买了我想在他的网站上使用的字体,但制作该字体的公司 (Adobe) 告诉我,目前没有针对该字体的 webfont 服务,我可以在网站上使用它仅当我将其栅格化或无法以任何其他方式下载/窃取字体时。

因此,问题是:

有什么可靠的方法来保护网络字体?

(第二部分,可选部分)

有什么方法通常被认为足够,因此使用该字体在法律上是安全的?

到目前为止我学到了什么:

1) 我听说过 Cufon,它使用 SVG 文件,这些文件(根据 w3.org)用于此目的:

SVG 字体的目的是允许在 仅显示环境。伴随网页的 SVG 字体必须是 仅在浏览和查看情况下支持。图形编辑 应用程序或文件翻译工具不得尝试转换 SVG 字体转换为系统字体。目的是 SVG 文件是 可以在两个内容创建者之间互换,但不能在 SVG 字体之间互换 可能伴随这些 SVG 文件。相反,每个内容创建者 需要获得给定字体的许可才能成功 编辑 SVG 文件。

但我也看到很少有从 SVG 到普通字体格式的在线转换器(还没有真正尝试过)。

2)我也看到了这个帖子,上面说它不是完全可能的,但是它已经有两年了,也许技术已经进步了。: How to protect WebFonts

3) 我也相信 PDF 有一些方法可以让我无法提取字体,几年前我看到我无法从中提取文本的 PDF(即使使用 3rd 方工具),因为有故意弄乱的字体表在里面,所以我不得不将整个文档渲染成图像并使用 OCR。但是,情况可能并非如此,因为我试图提取文本而不是字体。

4) 字体销售公司必须以某种方式保护他们的字体 (?)

【问题讨论】:

  • 有志者事竟成...您的浏览器看到的任何内容都可以下载/保存。我不知道他们怎么能指望你这样做,他们不提供任何可靠的解决方案,因为那样就不会对损失负责。 99.9% 的用户不会考虑使用字体。其他人会找到方法...
  • 编辑了我的问题(添加了可选部分)。
  • 即使您对图像进行了光栅化,也有人可以将这些图像用作叠加层以用于重新创建字体:/
  • 是否有足够的法律安全性使用该字体可能与How to protect WebFonts 重复,请注意该答案中的会话部分,它基本上就像一个 CSRF 令牌,它可能足以阻止公众

标签: php svg fonts webfonts protection


【解决方案1】:

从根本上说,如果您要使用字体而不使用图像(这是一种可怕的做法,如果您在页面上谈论多个字符,应该避免这种做法!)您无法阻止人们进行逆向工程你的字体。例如,将它们处理为SVGcanvas(或SVGVML),就像Cufon 所做的那样,如果用户想窃取它,就会迫使他们做一些工作,但是有plenty of SVG->TFF converters out there

即使没有转换器,您仍然可以根据 SVG 定义的矢量创建新字体集。防止这种情况的唯一方法是不提供任何基于矢量的字体系统(即使用位图),但这基本上会破坏您网站的可访问性。即便如此,如果您编写了一个动态系统来提供基于图像的字体,如果您让用户拥有足够高分辨率的图像位图(即服务器提供大字体),那么有可用的脚本可以将轮廓转换为向量。

字体销售公司(至少我见过的那些)都只提供基于图像的字体版本以供预览,并限制您可以演示的最大字体大小。

【讨论】:

  • 我想到了类似让 javascript 呈现字体,但拒绝除该脚本之外的任何人访问该文件(Cufon 不做类似的事情吗?)。还在我的问题中添加了可选部分。
  • 这是您需要在服务器上配置的内容(即按HTTP_REFERRER 过滤),但这是一个可欺骗的标头,因此您不能依赖它。您可以尝试通过当前 URL 限制访问(使用REQUEST_URI),但是攻击者只需要修改他们的host 文件来欺骗请求的域。知道自己在做什么的人可以检索原件。
  • 我对网络安全了解不多,但是除了直接在服务器上的脚本之外,真的没有办法拒绝所有用户查看文件吗?我觉得这很奇怪。
  • 是的。浏览器需要足够的信息才能呈现文本,对于任何可缩放字体(TTF、WOFF、SVG 等),呈现字体所需的最少信息是字母向量的数学表达式。但是,一旦您知道用于创建字母的向量,您就可以简单地重新创建字体。光栅化解决了这个问题(因为您永远不必向浏览器显示矢量)但是 - 正如我所提到的 - 它对网络非常不友好。你可以做很多混淆技巧,但最终浏览器需要能够显示字体。
【解决方案2】:

没有办法防止字体被反向编码为任何格式。 TTF等一些格式里面有版权信息,但是在线转换器可以随意忽略。

但是,您可以使字体相对不实用,即使其失去对潜在黑客的吸引力。

例如,如果您知道您的页面不会使用某些字符,您可以将其缩减为真实字形的子集。我并不是说将字体拆分为子部分。我的意思是不提供您网站上未使用的任何字形。例如,如果您的网站是英文的,请去掉所有变音符号或非拉丁字符集。

此外,在使用某些格式(例如,SVG 字体是裸字形)时,一些精细的信息(例如字距调整和提示)会丢失。将它们反向编码为 TTF 会产生原始字体的穷人版本。

另一方面,小字体的 SVG 输出效果很差。在伤害窃贼之前,使用 SVG 可能会伤害您的网站。

【讨论】:

  • +1 获取有关 SVG 输出不佳的信息,这可能节省了我很多时间
  • 我的荣幸。来过这里,做到了。我看不出你为什么要像我一样受苦:)
【解决方案3】:

可能没有法律上安全的方法来做到这一点,因为即使是精简版的字体仍会包含受版权保护的信息,并且可以规避任何其他保护措施。

如果字体没有提供带有较少压迫性条款的明确网络许可,最好不要在网络上使用它,除非以光栅化图像的形式。期间。

我会看看像Google Fonts 这样的服务是否提供外观上足够相似的字体。

您还可以四处寻找不同的字体许可方是否为相同的字体提供网络许可,或者至少是外观相似的字体。像Identifont 这样的字体标识符可以帮助您找到相似的字体。

【讨论】:

  • 为此 +1 - 除非您正在与一个非常挑剔的客户打交道,否则几乎总是有一些足够接近的事情可以做得很好。
【解决方案4】:

也许您可以使用基本的 CSRF 方法,并且每次请求只加载一次字体,创建一次使用后过期的会话密钥并检查引荐来源网址(如果用户在后续请求中清空那里可能会中断)。

但是对于一些待办事项;p 我已经整理了一个示例来说明我的意思,它不是 100%,但它会阻止公众尝试直接访问该字体。 download the source example

使用此方法,获取您需要的字体:

  1. 向页面发出请求,但不加载 css 字体。 (fgc,curl,ect)
  2. 解析页面源代码并从 CSS 中获取键。
  3. 然后向字体加载器发出自定义请求,设置引荐来源网址 并使用键值对。

    • 也许它会通过那里的保护期望。

CSS,你可以做的不是指向字体的路径,而是使用PHP来加载字体,这样你就可以在查询中添加一些nonce。

@font-face {
    font-family: 'TheFontName';
    src: local('TheFontName'), url('<?php echo SITE_URL.'/fonts.php?font=TheFontName.woff&'.$_SESSION['font_csrf_key'].'='.$_SESSION['font_csrf_token']?>') format('woff');
    font-weight: normal;
    font-style: normal;
}

示例(index.php)

<?php 
/**
 * @name ProtectFont
 * @link https://www.dropbox.com/s/xsbpw4g3xn4fzai/ProtectFont.zip
 */
session_start(); 

//Define paths
define('BASE_FOLDER', dirname($_SERVER['SCRIPT_NAME']));
define('SITE_ROOT',   pathinfo($_SERVER['SCRIPT_FILENAME'], PATHINFO_DIRNAME));
define('SITE_URL',    rtrim( 'http://'.$_SERVER['HTTP_HOST'].BASE_FOLDER, '/'));
//Site host will be checked against as the referrer host
define('SITE_HOST',   parse_url(SITE_URL, PHP_URL_HOST));

/* Now for the font protection, we create 2 CSRF keys, 
   one for the $_GET key and the other as the value.
*/
$_SESSION['font_csrf_key']     = sha1(uniqid());
$_SESSION['font_csrf_token']   = sha1(uniqid());
?>
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
@font-face {
    font-family: 'TheFontName';
    src: local('TheFontName'), url('<?php echo SITE_URL.'/fonts.php?font=TheFontName.woff&'.$_SESSION['font_csrf_key'].'='.$_SESSION['font_csrf_token']?>') format('woff');
    font-weight: normal;
    font-style: normal;
}
body {
  font-size: 110%;
}
h1{
  font-family: 'TheFontName',serif;
}
</style>
<body>

<h1>Your Font, try and nick me...</h1>

<p>This is the CSS thats on this page, the font is only accessible once for this request, if you try to link it will fail:<br>

<pre>
@font-face {
    font-family: 'TheFontName';
    src: local('TheFontName'), url(<span style="color:green">'<?php echo SITE_URL.'/fonts.php?font=TheFontName.woff&'.$_SESSION['font_csrf_key'].'='.$_SESSION['font_csrf_token']?></span>') format('woff');
    font-weight: normal;
    font-style: normal;
}
body {
  font-size: 110%;
}
h1{
  font-family: 'TheFontName',serif;
}
</pre>

<p>Using this method, to get the font you would need to:</p>

<ol>
  <li>Make a request to this page, without loading the css font. Can be done with file_get_content, curl 
  ect.</li>
  <li>Parse the pages source and get the <?php echo $_SESSION['font_csrf_key'].'='.$_SESSION['font_csrf_token']?> keys.</li>
  <li>Make a request to the font loader using the above key value pairs </li>
  <li>And set the referrer in that request to: <?php echo SITE_URL; ?></li>
</ol>

<p>Perhaps its enough to stop a few people but not everyone.</p>
<p>Where there's a will, there's a way... Anything 
that your browser sees can be downloaded/saved. I don't know how they can expect 
you todo this, they don't offer any reliable solution because then there not 
liable for loss. 99.9% of your users wont think about taking the font. Others 
will find a way...</p>

<p>Good luck</p>

</body>
</html>

现在我们进入 fonts.php 字体加载器,该文件仅在窗帘值匹配(CSRF 令牌和引用者)时加载字体,否则发送空白 404。

示例(fonts.php)

<?php
/**
 * Font loader, this file will only load the font if curtain values match
 */
session_start();

//Define script our paths
define('BASE_FOLDER', dirname($_SERVER['SCRIPT_NAME']));
define('SITE_ROOT',   pathinfo($_SERVER['SCRIPT_FILENAME'], PATHINFO_DIRNAME));
define('SITE_URL',    rtrim( 'http://'.$_SERVER['HTTP_HOST'].BASE_FOLDER, '/'));

//This will be checked against as the passed referer
define('SITE_HOST',   parse_url(SITE_URL, PHP_URL_HOST));

if(
    //Check required variables are set
    // -The tokens for the request
    isset($_SESSION['font_csrf_key']) &&
    isset($_SESSION['font_csrf_token']) &&

    // -The font you want to load
    isset($_GET['font']) &&

    // -The $_GET request key
    isset($_GET[$_SESSION['font_csrf_key']]) &&

    // -The referer
    isset($_SERVER["HTTP_REFERER"])&&

    // - Validate session keys with the passed token
    $_GET[$_SESSION['font_csrf_key']] == $_SESSION['font_csrf_token'] &&

    // - Validate the Referer
    parse_url($_SERVER["HTTP_REFERER"], PHP_URL_HOST) == SITE_HOST
    ){
        //check font exists
        if(file_exists(SITE_ROOT.'/_fonts/'.basename($_GET['font']))){

            //no cache
            header("Cache-control: no-store, no-cache, must-revalidate");
            header("Expires: Mon, 26 Jun 1997 05:00:00 GMT");
            header("Pragma: no-cache");
            //I think this is the right header
            header("Content-Type: application/octet-stream");
            set_time_limit(0);
            //ok nows lets load and send the font
            $font = null;
            $h = fopen(SITE_ROOT.'/_fonts/'.basename($_GET['font']), 'rb');
            if($h){
                while ($line = fgets($h, 4096)){
                    $font .= $line;
                }
            }else{
                header("HTTP/1.0 404 Not Found");
            }
            fclose($h);
            header('Content-Length: '.strlen($font));
            echo $font;
        }else{
            header("HTTP/1.0 404 Not Found");
        }
}
else{
    header("HTTP/1.0 404 Not Found");
}

//Unset to stop second access
unset($_SESSION['font_csrf_key'], $_SESSION['font_csrf_token']);
?>

希望对您有所帮助,download the example source here。您还可以将 cookie 添加到保护中以添加额外的保护层。或者甚至将文件名TheFontName.woff 更改为会话密钥并将文件名的真实值存储在会话中,或者将其混合并在查询字符串中添加诱饵,您可以在会话中设置正确的值或顺序。要有创意,但最终它不是防弹的。祝你好运

【讨论】:

  • 好的,但是什么会阻止用户打开开发工具并查看网络流量,然后找到字体并从浏览器的缓存中取回?
  • 您绝对正确...感谢您的投票,没有任何服务是私有的,即使没有这样的浏览器开发工具,也可以从内存中提取数据或捕获网络数据包。但它确实为公众直接下载字体提供了一点保护,我确实注意到也许它会通过那里的保护期望。这是 OPs 的问题,它指出它不是 100%而不是在几个地方防弹。只是想帮助 OP
  • 我没有对你投反对票。我认为您的帖子非常清楚不可能有 100% 的保证,因此我认为它有效且有用。
  • @loz-cherone-ツ 是否可以在单文件 HTML 页面中添加 PHP 脚本?如果我只想使用 base64?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多