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

时间:2012-01-08 18:54:28

标签: java c# cryptography sha1 hmac

这是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,货币: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”。

更新

Per @ M.Babcock,我用示例数据重新编写C#代码,并返回与Java代码相同的输出。所以看来我的哈希方法是正确的,但我传递了错误的数据(unencryptedMessage)。叹。如果/当我可以确定要加密的正确数据时,我将更新此帖子,因为Recurly文档中提供的“未加密的验证消息”似乎缺少某些内容。

更新2

错误结果是“未加密的验证消息”数据/格式。示例数据中的消息实际上并不加密到提供的示例签名 - 所以可能是过时的文档?无论如何,我已经确认Java实现将适用于实际数据。谢谢大家。

3 个答案:

答案 0 :(得分: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());

<强>结果:

  Message: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
      Key: 0123456789ABCDEF0123456789ABCDEF

Key bytes: 4d857d2408b00c3dd17f0c4ffcf15b97f1049867
   Result: c8a9188dcf85d1378976729e50f1de5093fabb78

<强>爪哇

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));

<强>结果:

  Message: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
      Key: 0123456789ABCDEF0123456789ABCDEF

Key Bytes: 4d857d2408b00c3dd17f0c4ffcf15b97f1049867
  Results: c8a9188dcf85d1378976729e50f1de5093fabb78

答案 1 :(得分:1)

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

我做了一个快速搜索来验证这是否属实并且它仍然没有结果,但它让我认为.NET中的字符串默认为UTF-16编码,而Java默认为UTF-8。 (有人可以证实这一点吗?)

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

答案 2 :(得分:0)

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