hmac(keyed-hash message authentication code)是一种使用密钥的哈希消息认证码,广泛应用于api认证和数据完整性验证。在跨平台或跨语言实现hmac时,开发者常会遇到计算结果不一致的问题,尤其是在处理非标准格式的密钥(如十六进制字符串)时。本文将以一个具体的案例为例,详细讲解如何在google apps script中正确处理带十六进制密钥的hmac计算,使其与php等其他语言的实现保持一致。
问题描述在API认证场景中,我们可能需要使用HMAC对请求进行签名。假设API提供了一个8字节的十六进制密钥,例如 C77C96EEF6F6995B。当使用PHP脚本按照厂商说明成功生成HMAC并完成认证后,尝试在Google Apps Script中以类似方式实现时,却得到了不同的哈希值,导致认证失败。
PHP工作示例:
<?php // 将十六进制字符串转换为原始字节序列作为密钥 $klucz = chr(hexdec('C7')).chr(hexdec('7C')).chr(hexdec('96')).chr(hexdec('EE')).chr(hexdec('F6')).chr(hexdec('F6')).chr(hexdec('99')).chr(hexdec('5B')); $url = "https://www.ifirma.pl/iapi/abonent/miesiacksiegowy.json"; $nazwaUsera = "user@example.com"; // 实际应用中请替换为真实邮箱 $nazwaKlucza = "abonent"; $hashWiadomosci = hash_hmac("sha1", $url.$nazwaUsera.$nazwaKlucza, $klucz); echo $hashWiadomosci; ?>
上述PHP脚本生成的哈希值为 9b5f9bb41ac30b9c55b10f3cecdd6e12e852f274,该值经验证可用于API认证。
Google Apps Script尝试实现(失败):
function bytesToHex(data) { return data.map(function(e) { var v = (e < 0 ? e + 256 : e).toString(16); return v.length == 1 ? "0" + v : v; }).join(""); } function callNumbers() { // 尝试将十六进制转换为字符作为密钥 var klucz = String.fromCharCode(parseInt("C7",16))+String.fromCharCode(parseInt("7C",16))+String.fromCharCode(parseInt("96",16))+String.fromCharCode(parseInt("EE",16))+String.fromCharCode(parseInt("F6",16))+String.fromCharCode(parseInt("F6",16))+String.fromCharCode(parseInt("99",16))+String.fromCharCode(parseInt("5B",16)); var user = "user@example.com"; // 实际应用中请替换为真实邮箱 var url = "https://www.ifirma.pl/iapi/abonent/miesiacksiegowy.json"; var wiadomosc = url+user+"abonent"; // 直接传入字符串和字符密钥 var hmac = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_1, wiadomosc, klucz); hmac=bytesToHex(hmac) Logger.log(hmac); }
这段Google Apps Script代码生成的哈希值为 6f50edae11b1f7f5b8488a99c4df3e797662c739,与PHP结果不符。
根源分析:字节数组的精确处理导致HMAC计算不一致的根本原因在于Google Apps Script中的 Utilities.computeHmacSignature 函数对输入参数(消息和密钥)的期望类型和字节表示方式与PHP存在差异。
密钥的误解: PHP的 chr(hexdec('XX')) 将十六进制值转换为单个原始字节字符,这些字符串连接起来形成一个字节序列作为HMAC密钥。 Google Apps Script中的 String.fromCharCode(parseInt(hex, 16)) 会创建JavaScript字符串。虽然这些字符串看起来包含正确的字符,但当它们被 Utilities.computeHmacSignature 作为密钥处理时,可能不会被解释为期望的原始字节数组。Utilities.computeHmacSignature 函数的 key 参数期望的是 byte[] 类型,即一个Java风格的带符号8位整数数组。
-
消息的误解:Utilities.computeHmacSignature 的 value 参数同样期望 byte[] 类型。直接传入JavaScript字符串 wiadomosc 可能导致内部默认的字符串到字节数组转换与API期望的编码(通常是UTF-8)不完全一致,或者与PHP中字符串的字节表示方式不同。PHP中的字符串在哈希函数中通常直接按其字节内容处理。
PIA
全面的AI聚合平台,一站式访问所有顶级AI模型
226 查看详情
要解决这个问题,我们需要确保将十六进制密钥和消息字符串都精确地转换为Google Apps Script Utilities.computeHmacSignature 函数所期望的 byte[] 类型。
1. 十六进制密钥转换为带符号字节数组Google Apps Script的 byte[] 存储的是带符号的8位整数(范围从-128到127)。而十六进制值通常表示无符号字节(范围从0到255)。因此,我们需要将无符号十六进制值转换为其对应的带符号字节表示。
例如,十六进制 C7 对应的无符号十进制是 199。作为带符号字节,199 实际上是 -57(因为 199 - 256 = -57)。对于十六进制 7C,对应的无符号十进制是 124,它在带符号字节范围内,因此保持 124。
以下代码片段展示了如何将十六进制字符串 C77C96EEF6F6995B 转换为符合 byte[] 要求的带符号字节数组:
var hexKeyString = "C77C96EEF6F6995B"; var kluczByteArray = hexKeyString.match(/.{2}/g).map(e => { let byteValue = parseInt(e, 16); // 将每对十六进制字符转换为无符号整数 // 如果无符号值大于127(即最高位为1),则转换为对应的带符号值 return (parseInt(e[0], 16).toString(2).length == 4) ? byteValue - 256 : byteValue; }); // 简化的条件判断:如果解析的十六进制值大于127,则减去256 // return (byteValue > 127) ? byteValue - 256 : byteValue;
解释上述 map 函数中的条件判断:parseInt(e[0], 16).toString(2).length == 4 这个条件检查的是十六进制对 e 的第一个字符 e[0] 对应的十进制值(例如,'C' 对应 12)转换成二进制后是否为4位。这个条件对于所有十六进制数字 8 到 F 都是 true,而对于 0 到 7 则是 false。 这意味着,当十六进制对的第一个字符是 8-F 时(即该字节的无符号值在 128-255 范围内),byteValue - 256 会被执行,将其转换为对应的负数带符号字节。如果第一个字符是 0-7(即该字节的无符号值在 0-127 范围内),则 byteValue 保持不变,因为其已在带符号字节范围内。这正是将无符号字节正确映射到带符号字节的方法。
2. 消息字符串转换为字节数组对于消息字符串,我们需要使用 Utilities.newBlob(string).getBytes() 方法将其转换为UTF-8编码的字节数组。这确保了消息的字节表示与PHP中字符串的默认处理方式保持一致。
var wiadomoscString = url + user + "abonent"; var wiadomoscByteArray = Utilities.newBlob(wiadomoscString).getBytes();3. 整合到Google Apps Script
将上述转换应用到Google Apps Script代码中,修正后的 callNumbers 函数如下:
function bytesToHex(data) { return data.map(function(e) { var v = (e < 0 ? e + 256 : e).toString(16); return v.length == 1 ? "0" + v : v; }).join(""); } function callNumbers() { var hexKeyString = "C77C96EEF6F6995B"; // 将十六进制密钥转换为带符号字节数组 var kluczByteArray = hexKeyString.match(/.{2}/g).map(e => { let byteValue = parseInt(e, 16); // 根据十六进制值范围转换为带符号字节 return (parseInt(e[0], 16).toString(2).length == 4) ? byteValue - 256 : byteValue; }); var user = "user@example.com"; // 实际应用中请替换为真实邮箱 var url = "https://www.ifirma.pl/iapi/abonent/miesiacksiegowy.json"; var wiadomoscString = url + user + "abonent"; // 将消息字符串转换为UTF-8编码的字节数组 var wiadomoscByteArray = Utilities.newBlob(wiadomoscString).getBytes(); // 使用转换后的字节数组进行HMAC计算 var hmac = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_1, wiadomoscByteArray, kluczByteArray); hmac = bytesToHex(hmac); Logger.log(hmac); }
以上就是正确计算Google Apps Script中带十六进制密钥的HMAC的详细内容,更多请关注知识资源分享宝库其它相关文章!
相关标签: php javascript java js json go app mac 邮箱 常见问题 php脚本 Java php JavaScript String 字符串 Length map 大家都在看: 生成准确表达文章主题的标题 使用嵌套循环在PHP中镜像三角形图案 php PSR标准是什么 php PSR规范核心内容解读 PHP如何实现分页功能_PHP数据库查询结果分页功能的实现逻辑 使用嵌套循环在PHP中镜像三角形图案 php如何增加内存限制?PHP内存限制配置与优化
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。