0

我正在尝试为我们网站的 AMP 脚本计算 AMP 脚本哈希,因此我们不必在每次脚本中的某些内容发生更改时手动更新它们。我们不使用 node.js,或者我只会使用 amp-script 文档页面 ( https://amp.dev/documentation/components/amp-script/ ) 上的给定解决方案。我已经尝试按照文档页面上列出的算法的步骤进行操作,但我无法让哈希值与元链接实际需要的哈希值相匹配。到目前为止,这是我的代码...

        // Compute sha384 sum of script contents
        var sha384 = SHA384.Create();
        var hashBytes = sha384.ComputeHash(Encoding.UTF8.GetBytes(model.ScriptContents));
        // Express hash sum as hexadecimal
        var hashHex = BitConverter.ToString(hashBytes);
        // Base64url-encode the result
        var base64 = Convert.ToBase64String(Encoding.ASCII.GetBytes(hashHex)).TrimEnd('=').Replace('+', '-').Replace('/', '_');
        // Prefix with 'sha384-'
        var hash = "sha384-" + base64;
4

1 回答 1

0

您实际上并不打算返回Base64-of-Base16-of-hash; 只是Base64-of-hash. 我认为关于“这个和应该用十六进制表示”的部分。让你失望:该页面上的说明是为使用toolbox-script-cspNPM 模块的 JS 开发人员编写的,尽管我同意编写它的人没有考虑非 JS 开发人员。

计算脚本内容的 SHA384 哈希和。该总和应以十六进制表示。

相反,请查看以下参考实现toolbox-script-csp

https://github.com/ampproject/amp-toolbox/blob/main/packages/script-csp/lib/calculateHash.js

function calculateHash(src, {algorithm = DEFAULT_ALGORITHM} = {}) {
  const algo = algorithm.toLowerCase();
  if (!SUPPORTED_ALGORITHMS.has(algo)) {
    throw new Error(`Unsupported algorithm for CSP: ${algo}`);
  }

  if (typeof src === 'string') {
    src = Buffer.from(src, 'utf8');
  }

  const hash = crypto.createHash(algo);
  const data = hash.update(src);
  const base64 = base64URLFormat(data.digest('base64'));
  return `${algo}-${base64}`;
}

function base64URLFormat(base64) {
  return base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
}

这可以以直接的方式转换为 C#/.NET,但是....

重要提示:请勿String用于表示您将要散列提供的 JS 脚本文件:由于解码和重新编码期间的元信息丢失,字符串的散列通常与原始文件不同,即使脚本的文字内容相同。例如,非规范化的 Unicode 字节在解码时被规范化,缺少前导字节顺序标记(Encoding.UTF8默认情况下呈现,顺便说一句!),甚至根据您的环境进行换行转换\n\r\n或反之亦然)设置。

...因此,下面的代码用于ReadOnlySpan<Byte>表示 JS 脚本,而不是System.String.


.NET Core 和 .NET 5+ 中的等价物是这样的:

public static async Task<String> CalculateHashForGoogleAmpAsync( FileInfo jsFile )
{
    // DO NOT read the JS file into a String (and then re-encode it to Byte[] to get the hash)! Doing so will apply a potentially lossy Unicode transformation such that the resultant re-encoded bytes will be different to the source file bytes and so cause the hash to not match the source file.
    // Instead just read the file's raw bytes and hash that (it's much simpler too!)

    Byte[] bytes = await File.ReadAllBytesAsync( jsFile.FullName ).ConfigureAwait(false);
    return CalculateHashForGoogleAmp( js: bytes );
}

public static String CalculateHashForGoogleAmp( ReadOnlySpan<Byte> js )
{
    Byte[] hash          = SHA384.HashData( js );
    String hashBase64    = Convert.ToBase64String( hash );
    String hashBase64Url = hashBase64.TrimEnd('=').Replace('+', '-').Replace('/', '_'); // Or use `Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode`
    return "sha384-" + hashBase64Url;
}

在 .NET Framework 4.x 和 .NET Standard 2.0 中,ReadAllBytesAsyncandstatic Byte[] HashData()方法不可用,但通过重新实现这些方法可以直接解决......这超出了这个问题的范围,所以这里是一个非异步版本:

public static String CalculateHashForGoogleAmp( FileInfo jsFile )
{
    Byte[] sha384Hash;

    using( SHA384 algo = SHA384.Create() )
    using( FileStream fs = File.OpenRead( jsFile.FullName ) )
    {
       sha384Hash = algo.ComputeHash( fs );
    }
    
    return FormatHashForGoogleAmp( sha384Hash );
}

private static String FormatHashForGoogleAmp( Byte[] sha384 )
{
    String hashBase64    = Convert.ToBase64String( sha384 );
    String hashBase64Url = hashBase64.TrimEnd('=').Replace('+', '-').Replace('/', '_'); // Or use `Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode`
    return "sha384-" + hashBase64Url;
}
于 2021-11-15T21:34:15.913 回答