是否应该用 \0 return 相同的哈希值正确填充 HMAC SHA256 密钥?
Should right padding an HMAC SHA256 secret key with \0 return the same hash?
当使用 JavaX HMAC/SHA256 哈希库时,如果我用非零字节右填充我的密钥,同一消息的哈希是不同的;正如预期的那样。
hmacSHA256digest( "secret".getBytes("UTF-8"), msg) = "244d9c89069406d40803722ec6a793e5e04c55234d9ca03039a7b505cb3f8f00"
hmacSHA256digest("secret".getBytes("UTF-8"), msg) = "4f94305c91ca9d8dec13ffcff7e455d6f0c49373e1bbc4035da2b500b11063fb"
但是,如果我用任意数量的 \0 字节右填充密钥,对于不同的字节数组,哈希返回相同,例如:
- "secret"
- "secret[=48=]"
- "secret[=49=][=49=]"
因此,JavaX HMAC SHA256 returning 相同的散列,即使 byte[] 数组 returned 来自getBytes("UTF-8") 的秘密只是在末尾有几个额外的零(所以这不是 UTF-8 问题):
hmacSHA256digest( "secret".getBytes("UTF-8"), msg)
= "244d9c89069406d40803722ec6a793e5e04c55234d9ca03039a7b505cb3f8f00"
hmacSHA256digest( "secret[=12=]".getBytes("UTF-8"), msg)
= "244d9c89069406d40803722ec6a793e5e04c55234d9ca03039a7b505cb3f8f00"
hmacSHA256digest( "secret[=12=][=12=]".getBytes("UTF-8"), msg)
= "244d9c89069406d40803722ec6a793e5e04c55234d9ca03039a7b505cb3f8f00"
当额外的 \0 附加到秘密时,对 MD5 和普通 SHA256 的其他 JavaX 方法的调用不会 return 相同的哈希,因此它们通过了我们的安全测试用例,以确保跨不同秘密的哈希唯一性。 MAC/SHA256 这个零填充秘密案例的失败是否是一个可能的攻击向量?
这是示例代码:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
static void testRightZeroPaddedSecretsHaveDifferentHashes() {
try {
byte[] msg = "msg".getBytes("UTF-8");
// HMAC SHA256
byte[] b3 = hmacSHA256digest(msg, "secret".getBytes("UTF-8"));
byte[] b4 = hmacSHA256digest(msg, "secret[=13=]".getBytes("UTF-8"));
// Plain SHA256
byte[] b5 = SHA256digest(msg, "secret".getBytes("UTF-8"));
byte[] b6 = SHA256digest(msg, "secret[=13=]".getBytes("UTF-8"));
boolean same34 = Arrays.equals(b3, b4);
boolean same56 = Arrays.equals(b5, b6);
System.out.println(
"\n" + Arrays.toString(b3) +
"\n" + Arrays.toString(b4) +
"\nHMAC SHA256 - identical hash results? = " + same34 +
"\n" +
"\n" + Arrays.toString(b5) +
"\n" + Arrays.toString(b6) +
"\nPlain SHA256 - identical hash results? = " + same56
);
} catch (Throwable e) {
e.printStackTrace();
}
}
static byte[] hmacSHA256digest(byte[] msg, byte[] secret) {
try {
SecretKeySpec keySpec = new SecretKeySpec(secret, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(keySpec);
byte[] hmac = mac.doFinal(msg);
return hmac;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
static byte[] SHA256digest(byte[] msg, byte[] secret) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(msg);
byte[] hash = digest.digest(secret);
return hash;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
样本输出:
[-2, 79, -100, 65, -113, 104, 63, 3, 79, 106, -7, 13, 29, -43, -72, 106, -64, 53, 93, -39, 99, 50, -59, -100, -57, 69, -104, -48, 115, 97, 7, -10]
[-2, 79, -100, 65, -113, 104, 63, 3, 79, 106, -7, 13, 29, -43, -72, 106, -64, 53, 93, -39, 99, 50, -59, -100, -57, 69, -104, -48, 115, 97, 7, -10]
HMAC SHA256 - identical hash results? = true
[-88, 92, 89, -29, -65, -48, -127, 51, 125, -120, 78, -38, 25, 57, -91, 91, -50, 111, -33, 40, -3, 0, -95, 89, -50, -88, 39, 118, 101, -56, 91, 126]
[-40, 39, 49, -64, 58, 40, 124, 64, 110, -100, 50, 115, -32, 114, -107, 24, -73, -17, -37, 11, 67, -26, -48, -65, 109, -24, 119, 45, 74, -31, -81, 119]
Plain SHA256 - identical hash results? = false
由于 JavaX HMAC SHA256 未能通过上述普通 SHA256/MD5 算法的零填充秘密测试用例,谁能解释行为上的差异以及是否可以利用这一点?
这是 HMAC 结构设计的正确行为。
理想情况下,密钥的大小应与底层哈希算法的块大小相同。对于 SHA-256,块大小为 512 位,因此您的密钥应为 64 字节。
从RFC 2104开始,如果一个键长于块大小,将通过散列函数将其传递并使用散列作为键来缩短它。 如果密钥比块大小短,它将通过附加零来扩展。
这是HMAC算法的第一步:
(1) append zeros to the end of K to create a B byte string
(e.g., if K is of length 20 bytes and B=64, then K will be appended with 44 zero bytes 0x00)
RFC 的建议是使用至少为散列函数输出大小的密钥,在您的情况下为 32 字节。尽管这仍然会使您的测试用例失败,但可以用零填充密钥并生成相同的 HMAC。
当使用 JavaX HMAC/SHA256 哈希库时,如果我用非零字节右填充我的密钥,同一消息的哈希是不同的;正如预期的那样。
hmacSHA256digest( "secret".getBytes("UTF-8"), msg) = "244d9c89069406d40803722ec6a793e5e04c55234d9ca03039a7b505cb3f8f00"
hmacSHA256digest("secret".getBytes("UTF-8"), msg) = "4f94305c91ca9d8dec13ffcff7e455d6f0c49373e1bbc4035da2b500b11063fb"
但是,如果我用任意数量的 \0 字节右填充密钥,对于不同的字节数组,哈希返回相同,例如:
- "secret"
- "secret[=48=]"
- "secret[=49=][=49=]"
因此,JavaX HMAC SHA256 returning 相同的散列,即使 byte[] 数组 returned 来自getBytes("UTF-8") 的秘密只是在末尾有几个额外的零(所以这不是 UTF-8 问题):
hmacSHA256digest( "secret".getBytes("UTF-8"), msg)
= "244d9c89069406d40803722ec6a793e5e04c55234d9ca03039a7b505cb3f8f00"
hmacSHA256digest( "secret[=12=]".getBytes("UTF-8"), msg)
= "244d9c89069406d40803722ec6a793e5e04c55234d9ca03039a7b505cb3f8f00"
hmacSHA256digest( "secret[=12=][=12=]".getBytes("UTF-8"), msg)
= "244d9c89069406d40803722ec6a793e5e04c55234d9ca03039a7b505cb3f8f00"
当额外的 \0 附加到秘密时,对 MD5 和普通 SHA256 的其他 JavaX 方法的调用不会 return 相同的哈希,因此它们通过了我们的安全测试用例,以确保跨不同秘密的哈希唯一性。 MAC/SHA256 这个零填充秘密案例的失败是否是一个可能的攻击向量?
这是示例代码:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
static void testRightZeroPaddedSecretsHaveDifferentHashes() {
try {
byte[] msg = "msg".getBytes("UTF-8");
// HMAC SHA256
byte[] b3 = hmacSHA256digest(msg, "secret".getBytes("UTF-8"));
byte[] b4 = hmacSHA256digest(msg, "secret[=13=]".getBytes("UTF-8"));
// Plain SHA256
byte[] b5 = SHA256digest(msg, "secret".getBytes("UTF-8"));
byte[] b6 = SHA256digest(msg, "secret[=13=]".getBytes("UTF-8"));
boolean same34 = Arrays.equals(b3, b4);
boolean same56 = Arrays.equals(b5, b6);
System.out.println(
"\n" + Arrays.toString(b3) +
"\n" + Arrays.toString(b4) +
"\nHMAC SHA256 - identical hash results? = " + same34 +
"\n" +
"\n" + Arrays.toString(b5) +
"\n" + Arrays.toString(b6) +
"\nPlain SHA256 - identical hash results? = " + same56
);
} catch (Throwable e) {
e.printStackTrace();
}
}
static byte[] hmacSHA256digest(byte[] msg, byte[] secret) {
try {
SecretKeySpec keySpec = new SecretKeySpec(secret, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(keySpec);
byte[] hmac = mac.doFinal(msg);
return hmac;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
static byte[] SHA256digest(byte[] msg, byte[] secret) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(msg);
byte[] hash = digest.digest(secret);
return hash;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
样本输出:
[-2, 79, -100, 65, -113, 104, 63, 3, 79, 106, -7, 13, 29, -43, -72, 106, -64, 53, 93, -39, 99, 50, -59, -100, -57, 69, -104, -48, 115, 97, 7, -10]
[-2, 79, -100, 65, -113, 104, 63, 3, 79, 106, -7, 13, 29, -43, -72, 106, -64, 53, 93, -39, 99, 50, -59, -100, -57, 69, -104, -48, 115, 97, 7, -10]
HMAC SHA256 - identical hash results? = true
[-88, 92, 89, -29, -65, -48, -127, 51, 125, -120, 78, -38, 25, 57, -91, 91, -50, 111, -33, 40, -3, 0, -95, 89, -50, -88, 39, 118, 101, -56, 91, 126]
[-40, 39, 49, -64, 58, 40, 124, 64, 110, -100, 50, 115, -32, 114, -107, 24, -73, -17, -37, 11, 67, -26, -48, -65, 109, -24, 119, 45, 74, -31, -81, 119]
Plain SHA256 - identical hash results? = false
由于 JavaX HMAC SHA256 未能通过上述普通 SHA256/MD5 算法的零填充秘密测试用例,谁能解释行为上的差异以及是否可以利用这一点?
这是 HMAC 结构设计的正确行为。
理想情况下,密钥的大小应与底层哈希算法的块大小相同。对于 SHA-256,块大小为 512 位,因此您的密钥应为 64 字节。
从RFC 2104开始,如果一个键长于块大小,将通过散列函数将其传递并使用散列作为键来缩短它。 如果密钥比块大小短,它将通过附加零来扩展。
这是HMAC算法的第一步:
(1) append zeros to the end of K to create a B byte string (e.g., if K is of length 20 bytes and B=64, then K will be appended with 44 zero bytes 0x00)
RFC 的建议是使用至少为散列函数输出大小的密钥,在您的情况下为 32 字节。尽管这仍然会使您的测试用例失败,但可以用零填充密钥并生成相同的 HMAC。