I borrowed the HMAC-SHA1 Java code from https://www.rfc-editor.org/rfc/rfc6238 and adapted slightly to hardcode it to use one known key/message pair with known output.
I then tried to write the same code in Python to verify the results, however I'm getting different values in Python and Java.
The Java values are known to be good.
Java code:
import java.lang.reflect.UndeclaredThrowableException;
import java.security.GeneralSecurityException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.util.TimeZone;
import java.util.Arrays;
public class make_hmac {
private make_hmac() {}
private static byte[] hmac_sha(String crypto, byte[] keyBytes,
byte[] text){
try {
System.out.println("Key is..." + bytesToHex(keyBytes) + "\n");
Mac hmac;
hmac = Mac.getInstance(crypto);
SecretKeySpec macKey =
new SecretKeySpec(keyBytes, "RAW");
hmac.init(macKey);
return hmac.doFinal(text);
} catch (GeneralSecurityException gse) {
throw new UndeclaredThrowableException(gse);
}
}
private static byte[] hexStr2Bytes(String hex){
// Adding one byte to get the right conversion
// Values starting with "0" can be converted
byte[] bArray = new BigInteger("10" + hex,16).toByteArray();
// Copy all the REAL bytes, not the "first"
byte[] ret = new byte[bArray.length - 1];
for (int i = 0; i < ret.length; i++)
ret[i] = bArray[i+1];
return ret;
}
private static final int[] DIGITS_POWER
// 0 1 2 3 4 5 6 7 8
= {1,10,100,1000,10000,100000,1000000,10000000,100000000 };
public static String generateTOTP(String key,
String time,
String returnDigits,
String crypto){
int codeDigits = Integer.decode(returnDigits).intValue();
String result = null;
// Using the counter
// First 8 bytes are for the movingFactor
// Compliant with base RFC 4226 (HOTP)
while (time.length() < 16 )
time = "0" + time;
// Get the HEX in a Byte[]
byte[] msg = hexStr2Bytes(time);
byte[] k = hexStr2Bytes(key);
byte[] hash = hmac_sha(crypto, k, msg);
System.out.println("I hashed key " + bytesToHex(k) + " against message " + bytesToHex(msg) + " and got...\n");
System.out.println("HASHED: " + bytesToHex(hash) + "\n");
// put selected bytes into result int
int offset = hash[hash.length - 1] & 0xf;
int binary =
((hash[offset] & 0x7f) << 24) |
((hash[offset + 1] & 0xff) << 16) |
((hash[offset + 2] & 0xff) << 8) |
(hash[offset + 3] & 0xff);
int otp = binary % DIGITS_POWER[codeDigits];
result = Integer.toString(otp);
while (result.length() < codeDigits) {
result = "0" + result;
}
return result;
}
public static String bytesToHex(byte[] bytes) {
final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char[] hexChars = new char[bytes.length * 2];
int v;
for ( int j = 0; j < bytes.length; j++ ) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static void main(String[] args) {
// Seed for HMAC-SHA1 - 20 bytes
String seed = "3132333435363738393031323334353637383930";
long T0 = 0;
long X = 30;
long testTime = 1111111109L;
String steps = "0";
long T = (testTime - T0)/X;
steps = Long.toHexString(T).toUpperCase();
while (steps.length() < 16) steps = "0" + steps;
System.out.println(generateTOTP(seed, steps, "8",
"HmacSHA1"));
}
}
Python code:
import hmac
from hashlib import sha1
k = "3132333435363738393031323334353637383930"
msg = "00000000023523EC"
print "I hashed key", k, "against msg", msg, "and got...\n"
a = hmac.new(k, msg, sha1)
print a.digest().encode('hex')
Results of running Java:
Key is...3132333435363738393031323334353637383930
I hashed key 3132333435363738393031323334353637383930 against message 00000000023523EC and got...
HASHED: 278C02E53610F84C40BD9135ACD4101012410A14
07081804
Results of running Python:
I hashed key 3132333435363738393031323334353637383930 against msg 00000000023523EC and got...
fa9362e87c80a1ac61f705b5f9d5095adaec9525
The "key" and "message" are the same, but the Java version gets a different HMAC than the Python implementation does.
I suspect there's a subtle error somewhere in the Python code(because the Java version matches the expected results from the RFC) but I'm not sure where. It looks so straightforward.
I think the problem is that in Java, you're using the raw bytes as the key (only converting them to a hex string for output):
System.out.println("Key is..." + bytesToHex(keyBytes) + "\n");
// ...
SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
But in Python, you're using the hex string:
k = "3132333435363738393031323334353637383930"
It looks like you can decode the hex string with:
raw_key = k.decode('hex')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With