HMC SHA1哈希 - Java生成的哈希输出与C#不同

6
这是对this问题的跟进,但我正在尝试将C#代码移植到Java,而不是像相关问题中的情况那样将Ruby代码移植到C#。我正在尝试验证从Recurly.js api返回的加密signature是否有效。不幸的是,Recurly没有Java库来协助验证,因此我必须自己实现签名验证。
根据上述相关问题(this),以下C#代码可以生成所需的哈希以验证从Recurly返回的签名:
var privateKey = Configuration.RecurlySection.Current.PrivateKey;
var hashedKey = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(privateKey));
var hmac = new HMACSHA1(hashedKey);
var hash = hmac.ComputeHash(Encoding.ASCII.GetBytes(dataToProtect));
return BitConverter.ToString(hash).Replace("-", "").ToLower();

Recurly在其signature文档页面上提供以下示例数据:

未加密的验证消息: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]

私钥: 0123456789ABCDEF0123456789ABCDEF

生成的签名: 0f5630424b32402ec03800e977cd7a8b13dbd153-1312701386

这是我的Java实现:

String unencryptedMessage = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
String privateKey = "0123456789ABCDEF0123456789ABCDEF";
String encryptedMessage = getHMACSHA1(unencryptedMessage, getSHA1(privateKey));

private static byte[] getSHA1(String source) throws NoSuchAlgorithmException, UnsupportedEncodingException{
    MessageDigest md = MessageDigest.getInstance("SHA-1");
    byte[] bytes = md.digest(source.getBytes("UTF-8"));
    return bytes;
}

private static String getHMACSHA1(String baseString, byte[] keyBytes) throws GeneralSecurityException, UnsupportedEncodingException {
    SecretKey secretKey = new SecretKeySpec(keyBytes, "HmacSHA1");
    Mac mac = Mac.getInstance("HmacSHA1");
    mac.init(secretKey);
    byte[] bytes = baseString.getBytes("ASCII");
    return Hex.encodeHexString(mac.doFinal(bytes));
}

然而,当我打印出encryptedMessage变量时,它与示例签名的消息部分不匹配。具体而言,我得到的值是"c8a9188dcf85d1378976729e50f1de5093fabb78",而不是"0f5630424b32402ec03800e977cd7a8b13dbd153"。
更新
根据@M.Babcock的建议,我使用示例数据重新运行了C#代码,并返回了与Java代码相同的输出结果。因此,看来我的哈希方法是正确的,但我传入了错误的数据(未加密的消息)。唉。如果/当我能确定要加密的正确数据(Recurly文档中提供的“未加密验证消息”似乎缺少某些内容)时,我将更新此帖子。
更新2
错误实际上是“未加密的验证消息”的数据/格式。示例数据中的消息实际上无法加密为提供的示例签名-所以可能是过时的文档?不管怎样,我已确认Java实现将适用于真实世界的数据。谢谢大家。

1
你验证过在两种语言中是否向哈希函数提供了相同的值吗?如果输入不同,则输出也会不同。 - M.Babcock
根据@M.Babcock的建议,我建议您将字符串和键更改为简单的内容,例如“Hello”和“World”。在这两种情况下,它们看起来是相同的值,但使用简单的字符串更容易验证。 - Cameron Skinner
3个回答

5
我认为问题出在你的.NET代码中。 Configuration.RecurlySection.Current.PrivateKey 返回一个字符串吗?那个值是你期望的密钥吗?
使用以下代码,.NET和Java返回相同的结果。 .NET代码
string message = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
string privateKey = "0123456789ABCDEF0123456789ABCDEF";

var hashedKey = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(privateKey));
var hmac = new HMACSHA1(hashedKey);
var hash = hmac.ComputeHash(Encoding.ASCII.GetBytes(message));

Console.WriteLine("  Message: {0}", message);
Console.WriteLine("      Key: {0}\n", privateKey);
Console.WriteLine("Key bytes: {0}", BitConverter.ToString(hashedKey).Replace("-", "").ToLower());
Console.WriteLine("   Result: {0}", BitConverter.ToString(hash).Replace("-", "").ToLower());

结果:

  消息: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
      密钥: 0123456789ABCDEF0123456789ABCDEF
密钥字节: 4d857d2408b00c3dd17f0c4ffcf15b97f1049867 结果: c8a9188dcf85d1378976729e50f1de5093fabb78

Java

String message = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
String privateKey = "0123456789ABCDEF0123456789ABCDEF";

MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] keyBytes = md.digest(privateKey.getBytes("UTF-8"));

SecretKey sk = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(sk);
byte[] result = mac.doFinal(message.getBytes("ASCII"));

System.out.println("  Message: " + message);
System.out.println("      Key: " + privateKey + "\n");
System.out.println("Key Bytes: " + toHex(keyBytes));
System.out.println("  Results: " + toHex(result));

结果:

  消息:[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
      密钥:0123456789ABCDEF0123456789ABCDEF
密钥字节:4d857d2408b00c3dd17f0c4ffcf15b97f1049867 结果:c8a9188dcf85d1378976729e50f1de5093fabb78

这是真的。我将不得不调查Configuration.RecurlySection.Current.PrivateKey返回的内容,因为我认为它是私钥的字符串。 - Frank
事实证明,密钥实际上是字符串格式。真正的罪魁祸首竟然是未加密的消息格式。请参见我的问题中的更新2。 - Frank

1

我怀疑你正在处理的值的默认编码可能不同。由于它们没有指定,它们将使用基于你所在平台的字符串的默认编码值。

我进行了快速搜索以验证这是否正确,但结果仍然不确定,但这让我想到.NET中的字符串默认为UTF-16编码,而Java默认为UTF-8。(有人可以确认吗?)

如果是这种情况,那么你使用UTF-8编码的GetBytes方法已经为每种情况生成了不同的输出。


0

根据this的示例代码,看起来Java希望您在创建SecretKeySpec之前尚未对密钥进行SHA1处理。您试过了吗?


我尝试过直接传入未加密的密钥(未经SHA1处理),然后从中获取字节以传递到SecretKeySpec中,但是没有成功。它返回了一个不同的哈希值,但仍然不是正确的哈希值。 - Frank

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接