我从http://tools.ietf.org/html/rfc6238借用了HMAC- SHA1 Java代码,并稍加修改以对其进行硬编码,以使用一个具有已知输出的已知密钥/消息对。
然后,我尝试在Python中编写相同的代码以验证结果,但是在Python和Java中获得了不同的值。
已知Java值很好。
Java代码:
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代码:
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')
运行Java的结果:
Key is...3132333435363738393031323334353637383930 I hashed key 3132333435363738393031323334353637383930 against message 00000000023523EC and got... HASHED: 278C02E53610F84C40BD9135ACD4101012410A14 07081804
运行Python的结果:
I hashed key 3132333435363738393031323334353637383930 against msg 00000000023523EC and got... fa9362e87c80a1ac61f705b5f9d5095adaec9525
“键”和“消息”相同,但是Java版本获得的HMAC与Python实现获得的HMAC不同。
我怀疑Python代码中的某个地方有一个微妙的错误(因为Java版本与RFC的预期结果匹配),但是我不确定在哪里。看起来很简单。
我认为问题是在Java中,您将原始字节用作键(仅将它们转换为十六进制字符串以进行输出):
System.out.println("Key is..." + bytesToHex(keyBytes) + "\n"); // ... SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
但是在Python中,您使用的是十六进制字符串:
k = "3132333435363738393031323334353637383930"
看起来您可以使用以下方式解码十六进制字符串:
raw_key = k.decode('hex')