How to generate an HMAC in Java equivalent to a Python example?
Asked Answered
M

2

52

I'm looking at implementing an app getting Twitter authorization via Oauth in Java. The first step is getting a request token. Here is a Python example for app engine.

To test my code, I am running Python and checking output with Java. Here is an example of Python generating a Hash-Based Message Authentication Code (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]

Output:

$ ./foo.py
+3h2gpjf4xcynjCGU5lbdMBwGOc=

How does one replicate this example in Java?

I've seen an example of HMAC in Java:

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) {
}

It uses javax.crypto.Mac, all good. However, the SecretKey constructors take bytes and an algorithm.

What's the algorithm in the Python example? How can one create a Java secret key without an algorithm?

Munro answered 8/7, 2010 at 21:31 Comment(0)
L
70

HmacSHA1 seems to be the algorithm name you need:

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

produces:

+3h2gpjf4xcynjCGU5lbdMBwGOc=

Note that I've used sun.misc.BASE64Encoder for a quick implementation here, but you should probably use something that doesn't depend on the Sun JRE. The base64-encoder in Commons Codec would be a better choice, for example.

Lineman answered 8/7, 2010 at 22:27 Comment(5)
@Lineman hi, could you explain how can I "zero pad" the secret key here if it is smaller than the recommended block size, which is 160 bits for SHA1 tnxOphir
Thank you very much, great example. For the record though, I think you definitely change your code sample with the Apache Commons Codec or the Guava encoder ;)Alake
"string".getBytes() looks rather strange. It will return platform dependend bytes (specify an encoding!) and it will not cover the whole range of byte values. I would expect either using an encoding (like bae64, base32 or hexdigest) for the string representation of the key or to use a password strengthening (PBKDF) to return pure well distributed bytes. (But of course this is a problem of the spec/python sample as well).Morgun
@Morgun Very good point, I almost always use getBytes("UTF-8") myself. As you say, the string in the Python example doesn't start with u"....", and we don't know whether it's Python 3.Lineman
Regarding the base64 encoding, here in 2016, we do it by String java.xml.bind.DatatypeConverter.printBase64Binary(byte[] val)Designate
S
28

A minor thing but if you are looking for an equivalent to hmac(key,message) then by default the python library will use the MD5 algorithm, so you need to use the HmacMD5 algorithm in Java.

I mention this because I had this exact problem and found this answer which was helpful, but I missed the part where a digest method was passed in to hmac() and thus went down a rabbit hole. Hopefully this answer will prevent others doing the same in the future.

e.g. in Python REPL

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

This is equivalent to the Java method:

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

Note that in my example I'm doing the equivalent of .hexdigest()

Stephniestepladder answered 19/6, 2012 at 13:43 Comment(1)
encodeHexString did not work on Android so I had to use this instead: return new String(Hex.encodeHex(rawHmac));Veratrine

© 2022 - 2024 — McMap. All rights reserved.