【问题标题】:Calculating AMP Script Hash With C#使用 C# 计算 AMP 脚本哈希
【发布时间】:2021-12-27 01:09:40
【问题描述】:

我正在尝试为我们网站的 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;

【问题讨论】:

  • 您实际上并不打算返回Base64-of-Base16-of-hash;只是Base64-of-hash。我认为关于“这个和应该用十六进制表示”的部分。让你失望:该页面是使用toolbox-script-csp 为 JS 开发人员编写的,并且 JS 库都有自己处理二进制数据的特殊方式,而在 .NET 中我们可以使用 Byte[] 就好了。
  • 重要提示:不要使用String 来表示您将要散列服务的 JS 脚本文件:a 的散列由于在解码和重新编码过程中元信息丢失,字符串通常与原始文件不同,即使脚本的文本内容相同。例如,非规范化的 Unicode 字节在解码时被规范化,缺少前导字节顺序标记(Encoding.UTF8 默认呈现,顺便说一句!),甚至从 \n\r\n 的换行符转换(反之亦然) -versa) 取决于您的环境是如何设置的。
  • @Dai 感谢您的回复!关于以十六进制表示的总和的那部分在这里让我感到困惑......但是仅执行 Base64-of-hash(Byte[])也不会给出正确的结果。
  • 另外,您需要处理您的sha384 实例(使用using() 块,或使用.NET Core 中引入的新static HashData 方法)。
  • 我建议您在我的回答中尝试该方法。输出应该是正确的以及匹配其他程序计算的哈希值(例如 7Zip 的资源管理器菜单以显示哈希值)。您如何确定输出是否正确?如果我的回答不起作用,那么我怀疑您做错了其他事情 - 如果是这样,那么您需要发布更多上下文(以及代码和示例脚本文件),以便我们可以尝试重现该问题。

标签: c# encoding hash amp-html ampscript


【解决方案1】:

您实际上并不打算返回Base64-of-Base16-of-hash;只是Base64-of-hash。我认为关于“这个和应该用十六进制表示”的部分。让你失望:该页面上的说明是为使用 toolbox-script-csp NPM 模块的 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 的换行符转换(反之亦然) -versa) 取决于您的环境设置。

...因此,下面的代码使用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 中,ReadAllBytesAsyncstatic 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;
}

【讨论】:

  • 我应该提到我正在使用 .NET Framework 4.7.2,因此我无法使用 SHA384.HashData 方法。我只能使用 ComputeHash 函数,但您建议将脚本作为 byte[] 或 Stream 传递吗?
  • @SamK 我已经用 .NET Framework 4.x 兼容代码更新了我的答案。但是你为什么还在使用 4.7.2?今天没有理由不使用 4.8。
猜你喜欢
  • 2021-04-27
  • 2016-06-23
  • 2012-07-12
  • 1970-01-01
  • 2020-07-04
  • 1970-01-01
  • 2015-10-25
  • 2016-08-03
  • 2017-02-04
相关资源
最近更新 更多