aboutsummaryrefslogtreecommitdiffstats
path: root/security-utils
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@yahooinc.com>2022-10-14 11:12:50 +0200
committerTor Brede Vekterli <vekterli@yahooinc.com>2022-10-14 11:18:36 +0200
commit4351d05681d01b0aafd3491c7d06e37b24ce18c8 (patch)
tree12e334d5e466594dcb0eec87270f213fa2b95123 /security-utils
parente46e4cd47626438c402938727f30c23a65bc914b (diff)
Test some specific HKDF vectors from Google's Wycheproof crypto test suite
Diffstat (limited to 'security-utils')
-rw-r--r--security-utils/src/test/java/com/yahoo/security/HKDFTest.java128
1 files changed, 106 insertions, 22 deletions
diff --git a/security-utils/src/test/java/com/yahoo/security/HKDFTest.java b/security-utils/src/test/java/com/yahoo/security/HKDFTest.java
index c2d8b99e9a7..bf000cbf8d2 100644
--- a/security-utils/src/test/java/com/yahoo/security/HKDFTest.java
+++ b/security-utils/src/test/java/com/yahoo/security/HKDFTest.java
@@ -4,6 +4,10 @@ package com.yahoo.security;
import org.bouncycastle.util.encoders.Hex;
import org.junit.jupiter.api.Test;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -27,6 +31,19 @@ public class HKDFTest {
return Hex.toHexString(bytes);
}
+ private static byte[] sha256DigestOf(byte[]... buffers) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ for (byte[] buf : buffers) {
+ digest.update(buf);
+ }
+ return digest.digest();
+ } catch (NoSuchAlgorithmException e) {
+ // SHA-256 should always be present, so this should never be reached in practice
+ throw new RuntimeException(e);
+ }
+ }
+
/*
A.1. Test Case 1
@@ -52,10 +69,10 @@ public class HKDFTest {
var hkdf = HKDF.extractedFrom(salt, ikm);
var okm = hkdf.expand(42, info);
- assertEquals(toHex(okm),
- "3cb25f25faacd57a90434f64d0362f2a" +
+ assertEquals("3cb25f25faacd57a90434f64d0362f2a" +
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf" +
- "34007208d5b887185865");
+ "34007208d5b887185865",
+ toHex(okm));
}
@Test
@@ -66,20 +83,20 @@ public class HKDFTest {
var hkdf = HKDF.extractedFrom(salt, ikm);
var okm = hkdf.expand(31, info); // One less than block size
- assertEquals(toHex(okm),
- "3cb25f25faacd57a90434f64d0362f2a" +
- "2d2d0a90cf1a5a4c5db02d56ecc4c5");
+ assertEquals("3cb25f25faacd57a90434f64d0362f2a" +
+ "2d2d0a90cf1a5a4c5db02d56ecc4c5",
+ toHex(okm));
okm = hkdf.expand(32, info); // Exactly equal to block size
- assertEquals(toHex(okm),
- "3cb25f25faacd57a90434f64d0362f2a" +
- "2d2d0a90cf1a5a4c5db02d56ecc4c5bf");
+ assertEquals("3cb25f25faacd57a90434f64d0362f2a" +
+ "2d2d0a90cf1a5a4c5db02d56ecc4c5bf",
+ toHex(okm));
okm = hkdf.expand(33, info); // One more than block size
- assertEquals(toHex(okm),
- "3cb25f25faacd57a90434f64d0362f2a" +
+ assertEquals("3cb25f25faacd57a90434f64d0362f2a" +
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf" +
- "34");
+ "34",
+ toHex(okm));
}
/*
@@ -134,13 +151,13 @@ public class HKDFTest {
var hkdf = HKDF.extractedFrom(salt, ikm);
var okm = hkdf.expand(82, info);
- assertEquals(toHex(okm),
- "b11e398dc80327a1c8e7f78c596a4934" +
+ assertEquals("b11e398dc80327a1c8e7f78c596a4934" +
"4f012eda2d4efad8a050cc4c19afa97c" +
"59045a99cac7827271cb41c65e590e09" +
"da3275600c2f09b8367793a9aca3db71" +
"cc30c58179ec3e87c14c01d5c1f3434f" +
- "1d87");
+ "1d87",
+ toHex(okm));
}
/*
@@ -168,14 +185,14 @@ public class HKDFTest {
// We don't allow empty salt to the salted factory function, so this is equivalent.
var hkdf = HKDF.unsaltedExtractedFrom(ikm);
var okm = hkdf.expand(42, info);
- var expectedKey = "8da4e775a563c18f715f802a063c5a31" +
+ var expectedOkm = "8da4e775a563c18f715f802a063c5a31" +
"b8a11f5c5ee1879ec3454e5f3c738d2d" +
"9d201395faa4b61a96c8";
- assertEquals(toHex(okm), expectedKey);
+ assertEquals(expectedOkm, toHex(okm));
// expand() without explicit context should return as if an empty context array was passed
okm = hkdf.expand(42);
- assertEquals(toHex(okm), expectedKey);
+ assertEquals(expectedOkm, toHex(okm));
}
@Test
@@ -189,10 +206,6 @@ public class HKDFTest {
assertThrows(IllegalArgumentException.class, () -> hkdf.expand(0)); // Need at least 1 key byte
assertThrows(IllegalArgumentException.class, () -> hkdf.expand(HKDF.MAX_OUTPUT_SIZE + 1)); // 1 too large
-
- // However, exactly max should work (though a strange size to request in practice)
- var okm = hkdf.expand(HKDF.MAX_OUTPUT_SIZE);
- assertEquals(okm.length, HKDF.MAX_OUTPUT_SIZE);
}
@Test
@@ -211,4 +224,75 @@ public class HKDFTest {
assertThrows(IllegalArgumentException.class, () -> HKDF.unsaltedExtractedFrom(new byte[0]));
}
+ //
+ // Subset of Wycheproof test vectors for specific named edge cases
+ // From https://github.com/google/wycheproof/blob/master/testvectors/hkdf_sha256_test.json
+ //
+
+ @Test
+ void maximal_output_size() {
+ var ikm = fromHex("bdd9c30b5fab7f22d859db774779b41cc124daf3ce872f6e80951c0edd8f8214");
+ var salt = fromHex("90983ed74912c6173d0f7cf8164b525361b89bda04d085341a057bde9083b5af");
+ var info = fromHex("e6483e923d37e4ba");
+
+ var hkdf = HKDF.extractedFrom(salt, ikm);
+ assertEquals(8160, HKDF.MAX_OUTPUT_SIZE);
+ var okm = hkdf.expand(HKDF.MAX_OUTPUT_SIZE, info);
+ // To avoid shoving an 8K sized hex string into the source code, check against the pre-hashed
+ // value of the expected OKM output. It's hashes all the way down!
+ var expectedOkmSha256Digest = "c17ce0403e133570191dd1d2ca46f6b62623d62e4f0def8de23a51d65d40a009";
+ var okmDigest = sha256DigestOf(okm);
+ assertEquals(expectedOkmSha256Digest, toHex(okmDigest));
+ }
+
+ @Test
+ void output_collision_for_different_salts() {
+ var ikm = fromHex("5943c65bc33bf05a205b04be8ae0ab2e");
+ var info = fromHex("be082f301a03f87787a80fbea88941214d50c42b");
+ var hkdf = HKDF.unsaltedExtractedFrom(ikm);
+
+ var okm = hkdf.expand(32, info);
+ var expectedOkm = "e7f384df2eae32addabd068a758dec84ed7fcfd87a5fcceb37b70c51422d7387";
+ assertEquals(expectedOkm, toHex(okm));
+
+ var salt = fromHex("0000000000000000000000000000000000000000000000000000000000000000");
+ hkdf = HKDF.extractedFrom(salt, ikm);
+ okm = hkdf.expand(32, info);
+ assertEquals(expectedOkm, toHex(okm));
+ }
+
+ @Test
+ void salt_longer_than_block_size_is_equivalent_to_hash_of_the_salt() {
+ var ikm = fromHex("624a5b59c2be55cbe29ea90c0020a7e8c60f2501");
+ var info = fromHex("5447e595250d02165aae3e61fa90313e25509a7b");
+ var salts = List.of("c737d7278df1ec7c0a549ce964abd51c3df1d3584d49e77208cd3f9f5bbfb32e",
+ "1a08959149f4b073bcd902c9bc4ed0324c21c95590773afc77037d610b9584806aeeeda8b5" +
+ "d588d0cd79e7c12211b8e394067516ce12946d61111a52042b539353");
+ var expectedOkm = "d45c3909269f4b5f9de1fb2eeb0593a7cb9175c8835aba37e0ee0c4cb3bd87c4";
+ for (var salt : salts) {
+ var hkdf = HKDF.extractedFrom(fromHex(salt), ikm);
+ var okm = hkdf.expand(32, info);
+ assertEquals(expectedOkm, toHex(okm));
+ }
+ }
+
+ @Test
+ void salt_shorter_than_the_block_size_is_padded_with_zeros() {
+ var ikm = fromHex("5943c65bc33bf05a205b04be8ae0ab2e");
+ var info = fromHex("be082f301a03f87787a80fbea88941214d50c42b");
+ var expectedOkm = "43e371354001617abb70454751059625ef1a64e0f818469c2f886b27140a0166";
+ var salts = List.of("e69dcaad55fb0536",
+ "e69dcaad55fb05360000000000000000",
+ "e69dcaad55fb053600000000000000000000000000000000",
+ "e69dcaad55fb0536000000000000000000000000000000000000000000000000",
+ "e69dcaad55fb05360000000000000000000000000000000000000000000000000000000000000000",
+ "e69dcaad55fb053600000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "e69dcaad55fb0536000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
+ for (var salt : salts) {
+ var hkdf = HKDF.extractedFrom(fromHex(salt), ikm);
+ var okm = hkdf.expand(32, info);
+ assertEquals(expectedOkm, toHex(okm), "Failed for salt %s".formatted(salt));
+ }
+ }
+
}