Должно ли правое заполнение секретного ключа HMAC SHA256 с \0 вернуть тот же хеш?
При использовании библиотек хеширования JavaX HMAC/SHA256, если я добавлю свой секретный ключ с ненулевым байтом справа, хэш для того же сообщения будет другим; как и ожидалось.
hmacSHA256digest( "secret".getBytes("UTF-8"), msg) = "244d9c89069406d40803722ec6a793e5e04c55234d9ca03039a7b505cb3f8f00"
hmacSHA256digest("secret\1".getBytes("UTF-8"), msg) = "4f94305c91ca9d8dec13ffcff7e455d6f0c49373e1bbc4035da2b500b11063fb"
Однако, если я добавлю правый секретный ключ с произвольным числом байтов \0, хэш вернется к одинаковому для различных байтовых массивов, таких как:
- "секрет"
- "Секрет \0"
- "Секрет \0\0"
Итак, JavaX HMAC SHA256 возвращает тот же хеш, хотя массив byte[], возвращаемый из getBytes("UTF-8") для секрета, имеет только несколько дополнительных нулей в конце (так что это не проблема UTF-8):
hmacSHA256digest( "secret".getBytes("UTF-8"), msg)
= "244d9c89069406d40803722ec6a793e5e04c55234d9ca03039a7b505cb3f8f00"
hmacSHA256digest( "secret\0".getBytes("UTF-8"), msg)
= "244d9c89069406d40803722ec6a793e5e04c55234d9ca03039a7b505cb3f8f00"
hmacSHA256digest( "secret\0\0".getBytes("UTF-8"), msg)
= "244d9c89069406d40803722ec6a793e5e04c55234d9ca03039a7b505cb3f8f00"
Вызовы других методов JavaX для MD5 и обычного SHA256 не возвращают один и тот же хэш, когда к секрету добавляются дополнительные \0, поэтому они передают наш тест безопасности на предмет уникальности хеширования для разных секретов. Является ли сбой этого случая с нулевыми секретами с 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\0".getBytes("UTF-8"));
// Plain SHA256
byte[] b5 = SHA256digest(msg, "secret".getBytes("UTF-8"));
byte[] b6 = SHA256digest(msg, "secret\0".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, упомянутых выше, может ли кто-нибудь объяснить разницу в поведении и можно ли это использовать?
1 ответ
Это правильное поведение конструкции HMAC, разработанное.
В идеале секретный ключ должен иметь размер блока, соответствующий основному алгоритму хеширования. Для SHA-256 размер блока составляет 512 бит, поэтому ваш ключ должен быть 64 байта.
Из RFC 2104, если ключ длиннее, чем размер блока, он будет сокращен путем его передачи через хэш-функцию и использования хеша в качестве ключа. Если ключ короче размера блока, он будет расширен путем добавления нулей.
Это первый шаг алгоритма HMAC:
(1) добавьте нули в конец K, чтобы создать строку байтов B (например, если K имеет длину 20 байтов и B=64, то к K будет добавлено 44 нулевых байта 0x00)
Рекомендация RFC заключается в том, чтобы использовать ключи, размер которых по крайней мере должен соответствовать выходному значению хэш-функции (32 байта в вашем случае). Несмотря на то, что в вашем тестовом случае это все равно не удастся, ключ может быть дополнен нулями и произведет тот же HMAC.