From 87c42f1cd34d395e462c3326a5c2be627b2a8fac Mon Sep 17 00:00:00 2001 From: Arnaud Duforat Date: Mon, 12 Jan 2026 11:26:49 +0100 Subject: [PATCH] Add unit tests for HMAC 256 based on RFC 4231 (#2744) * Add unit tests for HMAC 256 based on RFC 4231 * Formatting, shorten initializers. --------- Co-authored-by: Christoffer Lerno --- releasenotes.md | 1 + test/unit/stdlib/hash/sha256.c3 | 211 +++++++++++++++++++++++--------- 2 files changed, 156 insertions(+), 56 deletions(-) diff --git a/releasenotes.md b/releasenotes.md index 8001e9ff9..e3d184924 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -77,6 +77,7 @@ - Deprecated `DString.append_string` for DStrings, use `DString.append_dstring` instead. - Added `DString.append_bytes`. - Add `streebog` (aka "GOST-12") hashing with 256-bit and 512-bit outputs. #2659 +- Add unit tests for HMAC 256 based on RFC 4231. #2743 ## 0.7.8 Change list diff --git a/test/unit/stdlib/hash/sha256.c3 b/test/unit/stdlib/hash/sha256.c3 index 5efab0217..9f0ff8579 100644 --- a/test/unit/stdlib/hash/sha256.c3 +++ b/test/unit/stdlib/hash/sha256.c3 @@ -3,99 +3,198 @@ import std::hash::sha256; fn void test_sha256_empty() { - Sha256 sha; - sha.init(); - sha.update(""); + Sha256 sha; + sha.init(); + sha.update(""); - test::@check(sha.final() == x"E3B0C442 98FC1C14 9AFBF4C8 996FB924 27AE41E4 649B934C A495991B 7852B855"); + test::@check(sha.final() == x"E3B0C442 98FC1C14 9AFBF4C8 996FB924 27AE41E4 649B934C A495991B 7852B855"); } fn void test_sha256_abc() { - Sha256 sha; - sha.init(); - sha.update("abc"); + Sha256 sha; + sha.init(); + sha.update("abc"); - test::@check(sha.final() == x"BA7816BF 8F01CFEA 414140DE 5DAE2223 B00361A3 96177A9C B410FF61 F20015AD"); + test::@check(sha.final() == x"BA7816BF 8F01CFEA 414140DE 5DAE2223 B00361A3 96177A9C B410FF61 F20015AD"); } fn void test_sha256_longer() { - Sha256 sha; - sha.init(); - sha.update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopqabcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); - test::@check(sha.final() == x"59F109D9 533B2B70 E7C3B814 A2BD218F 78EA5D37 14455BC6 7987CF0D 664399CF"); + Sha256 sha; + sha.init(); + sha.update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopqabcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + test::@check(sha.final() == x"59F109D9 533B2B70 E7C3B814 A2BD218F 78EA5D37 14455BC6 7987CF0D 664399CF"); } fn void test_sha256_multi_update_permute() { - char[] input = "a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string"; - for (usz step = 1; step < input.len; step++) - { - Sha256 sha; - sha.init(); - usz i = 0; - for (; i < input.len / step; i++) sha.update(input[i*step : step]); - if (i*step < input.len) sha.update(input[i*step..]); - test::@check(sha.final() == x"b527293dfb70dcce37e593f4c43e1b81909615722bad041b90b8df22bebd00a0", "Mismatch for step %d", step); - } + char[] input = "a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string a really long string"; + for (usz step = 1; step < input.len; step++) + { + Sha256 sha; + sha.init(); + usz i = 0; + for (; i < input.len / step; i++) sha.update(input[i * step : step]); + if (i * step < input.len) sha.update(input[i * step..]); + test::@check(sha.final() == x"b527293dfb70dcce37e593f4c43e1b81909615722bad041b90b8df22bebd00a0", "Mismatch for step %d", step); + } } /* fn void gigahash_sha256() { - // > 256 MiB is just at the threshold where the SHA bitcounter rolls overflows 'len', but doesn't hit the uint.max limit... - char[] c = calloc(257 * (1024*1024))[:(257*1024*1024)]; - defer free(c); + // > 256 MiB is just at the threshold where the SHA bitcounter rolls overflows 'len', but doesn't hit the uint.max limit... + char[] c = calloc(257 * (1024*1024))[:(257*1024*1024)]; + defer free(c); - Sha256 sha; - sha.init(); - sha.update(c); - test::@check(sha.final() == x"053EADFD EC682CF1 6F3F8704 C7609C57 868DD757 65E08DC5 A7491F5D 06BCB74D"); + Sha256 sha; + sha.init(); + sha.update(c); + test::@check(sha.final() == x"053EADFD EC682CF1 6F3F8704 C7609C57 868DD757 65E08DC5 A7491F5D 06BCB74D"); } */ fn void test_pbkdf2() { - char[] pw = "password"; - char[] s = "salt"; - char[32] out; - sha256::pbkdf2(pw, s, 1, &out); - test::@check(out == x'120FB6CF FCF8B32C 43E72252 56C4F837 A86548C9 2CCC3548 0805987C B70BE17B'); - sha256::pbkdf2(pw, s, 2, &out); - test::@check(out == x'AE4D0C95 AF6B46D3 2D0ADFF9 28F06DD0 2A303F8E F3C251DF D6E2D85A 95474C43'); - sha256::pbkdf2(pw, s, 4096, &out); - test::@check(out == x'C5E478D5 9288C841 AA530DB6 845C4C8D 962893A0 01CE4E11 A4963873 AA98134A'); + char[] pw = "password"; + char[] s = "salt"; + char[32] out; + sha256::pbkdf2(pw, s, 1, &out); + test::@check(out == x'120FB6CF FCF8B32C 43E72252 56C4F837 A86548C9 2CCC3548 0805987C B70BE17B'); + sha256::pbkdf2(pw, s, 2, &out); + test::@check(out == x'AE4D0C95 AF6B46D3 2D0ADFF9 28F06DD0 2A303F8E F3C251DF D6E2D85A 95474C43'); + sha256::pbkdf2(pw, s, 4096, &out); + test::@check(out == x'C5E478D5 9288C841 AA530DB6 845C4C8D 962893A0 01CE4E11 A4963873 AA98134A'); } fn void test_pbkdf2_2() { - char[] pw = "passwordPASSWORDpassword"; - char[] s = "saltSALTsaltSALTsaltSALTsaltSALTsalt"; - char[32] out; - sha256::pbkdf2(pw, s, 4096, &out); - test::@check(out == x'348C89DB CBD32B2F 32D814B8 116E84CF 2B17347E BC180018 1C4E2A1F B8DD53E1'); + char[] pw = "passwordPASSWORDpassword"; + char[] s = "saltSALTsaltSALTsaltSALTsaltSALTsalt"; + char[32] out; + sha256::pbkdf2(pw, s, 4096, &out); + test::@check(out == x'348C89DB CBD32B2F 32D814B8 116E84CF 2B17347E BC180018 1C4E2A1F B8DD53E1'); } fn void test_pbkdf2_3() { - char[] pw = "pass\0word"; - char[] salt = "sa\0lt"; - char[32] out; - sha256::pbkdf2(pw, salt, 4096, &out); + char[] pw = "pass\0word"; + char[] salt = "sa\0lt"; + char[32] out; + sha256::pbkdf2(pw, salt, 4096, &out); - test::@check(out == x'89B69D05 16F82989 3C696226 650A8687 8C029AC1 3EE27650 9D5AE58B 6466A724'); + test::@check(out == x'89B69D05 16F82989 3C696226 650A8687 8C029AC1 3EE27650 9D5AE58B 6466A724'); } fn void test_sha256_million_a() { - Sha256 sha; - sha.init(); - const int COUNT = 1_000_000; - for (int i = 0; i < COUNT / 10; i++) - { - sha.update("aaaaaaaaaa"); - } - test::@check(sha.final() == x"CDC76E5C 9914FB92 81A1C7E2 84D73E67 F1809A48 A497200E 046D39CC C7112CD0"); + Sha256 sha; + sha.init(); + const int COUNT = 1_000_000; + for (int i = 0; i < COUNT / 10; i++) + { + sha.update("aaaaaaaaaa"); + } + test::@check(sha.final() == x"CDC76E5C 9914FB92 81A1C7E2 84D73E67 F1809A48 A497200E 046D39CC C7112CD0"); +} + +fn void test_rfc_4231_4_2_test_case_1() @test { + char[20] key = { [0..19] = 0x0b }; + const char[] MSG = "Hi There"; + const char[] EXPECTED = + x"b0344c61d8db38535ca8afceaf0bf12b" + x"881dc200c9833da726e9376c2e32cff7"; + char[sha256::HASH_SIZE] mac = sha256::hmac(&key, MSG); + test::@check( + &mac == EXPECTED, + "Expected %h, got %h", + EXPECTED, + mac + ); +} + +fn void test_rfc_4231_4_3_test_case_2() @test { + const char[] KEY = "Jefe"; + const char[] MSG = "what do ya want for nothing?"; + const char[] EXPECTED = + x"5bdcc146bf60754e6a042426089575c7" + x"5a003f089d2739839dec58b964ec3843"; + char[sha256::HASH_SIZE] mac = sha256::hmac(KEY, MSG); + test::@check( + &mac == EXPECTED, + "Expected %h, got %h", + EXPECTED, + mac + ); +} + +fn void test_rfc_4231_4_4_test_case_3() @test { + char[20] key = { [0..19] = 0xaa }; + char[50] msg = { [0..49] = 0xdd }; + const char[] EXPECTED = + x"773ea91e36800e46854db8ebd09181a7" + x"2959098b3ef8c122d9635514ced565fe"; + char[sha256::HASH_SIZE] mac = sha256::hmac(&key, &msg); + test::@check( + &mac == EXPECTED, + "Expected %h, got %h", + EXPECTED, + mac + ); +} + +fn void test_rfc_4231_4_5_test_case_4() @test { + char[25] key; + foreach (i, &c : key) *c = (char)(i + 1); + char[50] msg = { [0..49] = 0xcd }; + const char[] EXPECTED = + x"82558a389a443c0ea4cc819899f2083a" + x"85f0faa3e578f8077a2e3ff46729665b"; + char[sha256::HASH_SIZE] mac = sha256::hmac(&key, &msg); + test::@check( + &mac == EXPECTED, + "Expected %h, got %h", + EXPECTED, + mac + ); +} + +// RFC 4231 4.6 Test Case 5 ignored +// hmac method from sha256 module does not support truncated output +// please truncate it yourself + +fn void test_rfc_4231_4_7_test_case_6() @test { + char[131] key = { [0..130] = 0xaa }; + const char[] MSG = + "Test Using Larger Than Block-Size Key - Hash Key First"; + const char[] EXPECTED = + x"60e431591ee0b67f0d8a26aacbf5b77f" + x"8e0bc6213728c5140546040f0ee37f54"; + char[sha256::HASH_SIZE] mac = sha256::hmac(&key, MSG); + test::@check( + &mac == EXPECTED, + "Expected %h, got %h", + EXPECTED, + mac + ); +} + +fn void test_rfc_4231_4_8_test_case_7() @test { + char[131] key = { [0..130] = 0xaa }; + const char[] MSG = + "This is a test using a larger than block-size key and a larger " + "than block-size data. The key needs to be hashed before being " + "used by the HMAC algorithm."; + const char[] EXPECTED = + x"9b09ffa71b942fcb27635fbcd5b0e944" + x"bfdc63644f0713938a7f51535c3a35e2"; + char[sha256::HASH_SIZE] mac = sha256::hmac(&key, MSG); + test::@check( + &mac == EXPECTED, + "Expected %h, got %h", + EXPECTED, + mac + ); }