如何在Java中生成与Python示例等效的HMAC?

52

我正在考虑在Java中实现一个应用程序,通过Oauth获取Twitter授权。第一步是获取请求令牌。这里有一个适用于应用引擎的Python示例

为了测试我的代码,我正在运行Python并使用Java检查输出。这是Python生成基于哈希的消息认证码(HMAC)的一个示例:

#!/usr/bin/python

from hashlib import sha1
from hmac import new as hmac

key = "qnscAdgRlkIhAUPY44oiexBKtQbGY0orf7OV1I50"
message = "foo"

print "%s" % hmac(key, message, sha1).digest().encode('base64')[:-1]

输出:

$ ./foo.py
+3h2gpjf4xcynjCGU5lbdMBwGOc=

如何在Java中复制此示例?

我已经看到了一个Java中的HMAC示例:

try {
    // Generate a key for the HMAC-MD5 keyed-hashing algorithm; see RFC 2104
    // In practice, you would save this key.
    KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
    SecretKey key = keyGen.generateKey();

    // Create a MAC object using HMAC-MD5 and initialize with key
    Mac mac = Mac.getInstance(key.getAlgorithm());
    mac.init(key);

    String str = "This message will be digested";

    // Encode the string into bytes using utf-8 and digest it
    byte[] utf8 = str.getBytes("UTF8");
    byte[] digest = mac.doFinal(utf8);

    // If desired, convert the digest into a string
    String digestB64 = new sun.misc.BASE64Encoder().encode(digest);
} catch (InvalidKeyException e) {
} catch (NoSuchAlgorithmException e) {
} catch (UnsupportedEncodingException e) {
}

它使用 javax.crypto.Mac,一切都好。然而,SecretKey 构造函数需要字节和算法。

Python示例中的算法是什么?如何在不使用算法的情况下创建Java秘密密钥?

2个回答

70

看起来你需要使用HmacSHA1算法:

SecretKeySpec keySpec = new SecretKeySpec(
        "qnscAdgRlkIhAUPY44oiexBKtQbGY0orf7OV1I50".getBytes(),
        "HmacSHA1");

Mac mac = Mac.getInstance("HmacSHA1");
mac.init(keySpec);
byte[] result = mac.doFinal("foo".getBytes());

BASE64Encoder encoder = new BASE64Encoder();
System.out.println(encoder.encode(result));

结果为:

+3h2gpjf4xcynjCGU5lbdMBwGOc=

请注意,我在这里使用了 sun.misc.BASE64Encoder 进行快速实现,但您应该使用不依赖于Sun JRE的东西。 例如,Commons Codec 中的 base64 编码器 将是更好的选择。


@Bruno 你好,能否解释一下如果我的密钥小于推荐的块大小(SHA1为160位),我如何对其进行“零填充”操作?谢谢。 - Spring
非常感谢,这是一个很好的例子。不过,为了记录,我认为你应该使用Apache Commons Codec或Guava编码器来更改代码示例 ;) - bric3
3
"string".getBytes() 看起来相当奇怪。它会返回平台相关的字节(需要指定编码!)并且不会涵盖整个字节值范围。我建议使用编码(如base64、base32或hexdigest)来表示密钥的字符串,或者使用密码强化(PBKDF)来返回纯净的分布良好的字节。(当然,这也是规范/Python示例的问题)。 - eckes
1
@eckes非常好的观点,我几乎总是使用getBytes(“UTF-8”)。正如您所说,Python示例中的字符串没有以u“....”开头,我们不知道它是否为Python 3。 - Bruno
关于base64编码,在2016年,我们使用String java.xml.bind.DatatypeConverter.printBase64Binary(byte[] val)进行编码。 - Scruffy

28

虽然这只是一件小事,但如果你在寻找与hmac(key,message)等效的算法,那么Python库默认会使用MD5算法,所以你需要在Java中使用HmacMD5算法。

我提到这一点是因为我曾经遇到过这个问题,并发现了这个有用的答案,但我错过了向hmac()传递摘要方法的部分,因此走了一条歧路。希望这个答案能防止其他人在未来犯同样的错误。

例如,在Python REPL中:

>>> import hmac
>>> hmac.new("keyValueGoesHere", "secretMessageToHash").hexdigest()
'1a7bb3687962c9e26b2d4c2b833b2bf2'

这相当于Java方法:

import org.apache.commons.codec.binary.Hex;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class HashingUtility {
    public static String HMAC_MD5_encode(String key, String message) throws Exception {

        SecretKeySpec keySpec = new SecretKeySpec(
                key.getBytes(),
                "HmacMD5");

        Mac mac = Mac.getInstance("HmacMD5");
        mac.init(keySpec);
        byte[] rawHmac = mac.doFinal(message.getBytes());

        return Hex.encodeHexString(rawHmac);
    }
}
请注意,我的示例中所做的相当于 .hexdigest()。

3
在Android上,encodeHexString无法正常工作,所以我不得不使用这个:return new String(Hex.encodeHex(rawHmac)); - JustinMorris

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