【问题标题】:HMC SHA1 hash - Java producing different hash output than C#HMC SHA1 哈希 - Java 产生与 C# 不同的哈希输出
【发布时间】:2012-02-05 12:08:59
【问题描述】:

这是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 实现将适用于真实世界的数据。谢谢大家。

【问题讨论】:

  • 您是否确认您在两种语言的哈希函数中提供了相同的值?如果输入不同,那么输出也会不同。
  • 更进一步@M.Babcock 的观点,我建议您将字符串和键更改为简单的东西,例如“Hello”和“World”。它看起来在这两种情况下都有相同的值,但使用简单的字符串更容易验证。

标签: java c# cryptography sha1 hmac


【解决方案1】:

我认为问题出在您的 .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 返回的内容,因为我认为它是私钥的字符串。
  • 原来密钥实际上是字符串格式。真正的罪魁祸首原来是 unencryptedMessage 格式。请参阅我的问题中的更新 2。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 我尝试只传入未加密的密钥(不是 SHA1'd),然后从中获取字节以传递到 SecretKeySpec,但无济于事。它返回一个不同的哈希,但仍然不是正确的。
    猜你喜欢
    • 2011-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-20
    • 1970-01-01
    • 2018-05-23
    • 2018-11-26
    相关资源
    最近更新 更多