From d12699308168282e90a066d7c722009456828f98 Mon Sep 17 00:00:00 2001 From: Micah Date: Mon, 22 Apr 2024 17:28:24 -0700 Subject: [PATCH] Correctly hash messages where the input length was 55 + (64*n) or 111 + (128*n) (#9) * Test for buggy case * Fix buggy case in all SHA modules + update changelog --- modules/sha1/CHANGELOG.md | 1 + modules/sha1/init.luau | 8 ++++++-- modules/sha224/CHANGELOG.md | 1 + modules/sha224/init.luau | 7 ++++++- modules/sha256/CHANGELOG.md | 1 + modules/sha256/init.luau | 7 ++++++- modules/sha384/CHANGELOG.md | 1 + modules/sha384/init.luau | 8 +++++++- modules/sha512/CHANGELOG.md | 1 + modules/sha512/init.luau | 8 +++++++- testfiles/111-a.txt | 1 + testfiles/119-a.txt | 1 + testfiles/239-a.txt | 1 + testfiles/55-a.txt | 1 + tests/sha1.luau | 27 +++++++++++++++++++++++++++ tests/sha224.luau | 27 +++++++++++++++++++++++++++ tests/sha256.luau | 27 +++++++++++++++++++++++++++ tests/sha384.luau | 27 +++++++++++++++++++++++++++ tests/sha512.luau | 30 ++++++++++++++++++++++++++++++ 19 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 testfiles/111-a.txt create mode 100644 testfiles/119-a.txt create mode 100644 testfiles/239-a.txt create mode 100644 testfiles/55-a.txt diff --git a/modules/sha1/CHANGELOG.md b/modules/sha1/CHANGELOG.md index f6ecba8..e5550e1 100644 --- a/modules/sha1/CHANGELOG.md +++ b/modules/sha1/CHANGELOG.md @@ -4,6 +4,7 @@ - Several performance improvements - Add `--!native` directive to module to support native codegen in Roblox +- Fixed incorrect hashing result when input had a length of `55 + 64 * n` where `n` was any integer ## Version 1.0.2 diff --git a/modules/sha1/init.luau b/modules/sha1/init.luau index 510b92b..6096855 100644 --- a/modules/sha1/init.luau +++ b/modules/sha1/init.luau @@ -100,11 +100,15 @@ local function sha1(message: string): (string, { number }) processBlocks(digest, message, 1, messageLen - leftover) end - -- TODO find a way to do this more eloquently + -- Raise `leftover` to next multiple of 64 so that we can calculate + -- how much padding we need without a branch or loop. + -- The number here is just masking off the last 6 bits. + local nextMultipleOf64 = bit32.band(leftover + 32, 0xffffffc0) + local finalMessage = { if leftover ~= 0 then string.sub(message, -leftover) else "", "\x80", - string.rep("\0", 64 - (messageLen + 9) % 64), + string.rep("\0", (nextMultipleOf64 - leftover - 9) % 64), string.pack(">L", messageLen * 8), } local finalBlock = table.concat(finalMessage) diff --git a/modules/sha224/CHANGELOG.md b/modules/sha224/CHANGELOG.md index eb531c9..4735540 100644 --- a/modules/sha224/CHANGELOG.md +++ b/modules/sha224/CHANGELOG.md @@ -4,6 +4,7 @@ - Several performance improvements - Add `--!native` directive to module to support native codegen in Roblox +- Fixed incorrect hashing result when input had a length of `55 + 64 * n` where `n` was any integer ## Version 1.0.0 diff --git a/modules/sha224/init.luau b/modules/sha224/init.luau index bfa1aa9..bb76c70 100644 --- a/modules/sha224/init.luau +++ b/modules/sha224/init.luau @@ -119,10 +119,15 @@ local function sha224(message: string): (string, { number }) processBlocks(digest, message, 1, messageLen - leftover) end + -- Raise `leftover` to next multiple of 64 so that we can calculate + -- how much padding we need without a branch or loop. + -- The number here is just masking off the last 6 bits. + local nextMultipleOf64 = bit32.band(leftover + 32, 0xffffffc0) + local finalMessage = { if leftover ~= 0 then string.sub(message, -leftover) else "", "\x80", - string.rep("\0", 64 - (messageLen + 9) % 64), + string.rep("\0", (nextMultipleOf64 - leftover - 9) % 64), string.pack(">L", messageLen * 8), } local finalBlock = table.concat(finalMessage) diff --git a/modules/sha256/CHANGELOG.md b/modules/sha256/CHANGELOG.md index eb531c9..8129107 100644 --- a/modules/sha256/CHANGELOG.md +++ b/modules/sha256/CHANGELOG.md @@ -4,6 +4,7 @@ - Several performance improvements - Add `--!native` directive to module to support native codegen in Roblox +- Fixed incorrect hashing result when input had a length where `length % 55` was `0` ## Version 1.0.0 diff --git a/modules/sha256/init.luau b/modules/sha256/init.luau index e88c92e..d3f5296 100644 --- a/modules/sha256/init.luau +++ b/modules/sha256/init.luau @@ -119,10 +119,15 @@ local function sha256(message: string): (string, { number }) processBlocks(digest, message, 1, messageLen - leftover) end + -- Raise `leftover` to next multiple of 64 so that we can calculate + -- how much padding we need without a branch or loop. + -- The number here is just masking off the last 6 bits. + local nextMultipleOf64 = bit32.band(leftover + 32, 0xffff_ffc0) + local finalMessage = { if leftover ~= 0 then string.sub(message, -leftover) else "", "\x80", - string.rep("\0", 64 - (messageLen + 9) % 64), + string.rep("\0", (nextMultipleOf64 - leftover - 9) % 64), string.pack(">L", messageLen * 8), } local finalBlock = table.concat(finalMessage) diff --git a/modules/sha384/CHANGELOG.md b/modules/sha384/CHANGELOG.md index eb531c9..d109373 100644 --- a/modules/sha384/CHANGELOG.md +++ b/modules/sha384/CHANGELOG.md @@ -4,6 +4,7 @@ - Several performance improvements - Add `--!native` directive to module to support native codegen in Roblox +- Fixed incorrect hashing result when input had a length of `111 + 128 * n` where `n` was any integer ## Version 1.0.0 diff --git a/modules/sha384/init.luau b/modules/sha384/init.luau index b594c17..cf74be7 100644 --- a/modules/sha384/init.luau +++ b/modules/sha384/init.luau @@ -292,10 +292,16 @@ local function sha384(message: string): string if messageLen >= 128 then processBlocks(digest_front, digest_back, message, 1, messageLen - leftover) end + + -- Raise `leftover` to next multiple of 128 so that we can calculate + -- how much padding we need without a branch or loop. + -- The number here is just masking off the last 6 bits. + local nextMultipleOf128 = bit32.band(leftover + 64, 0xfffff000) + local finalMessage = { if leftover ~= 0 then string.sub(message, -leftover) else "", "\x80", - string.rep("\0", 128 - (messageLen + 17) % 128), + string.rep("\0", (nextMultipleOf128 - leftover - 17) % 128), string.pack(">L", messageLen * 8 / 2 ^ 32), string.pack(">L", bit32.bor(messageLen * 8)), } diff --git a/modules/sha512/CHANGELOG.md b/modules/sha512/CHANGELOG.md index eb531c9..d109373 100644 --- a/modules/sha512/CHANGELOG.md +++ b/modules/sha512/CHANGELOG.md @@ -4,6 +4,7 @@ - Several performance improvements - Add `--!native` directive to module to support native codegen in Roblox +- Fixed incorrect hashing result when input had a length of `111 + 128 * n` where `n` was any integer ## Version 1.0.0 diff --git a/modules/sha512/init.luau b/modules/sha512/init.luau index 570293a..2041d88 100644 --- a/modules/sha512/init.luau +++ b/modules/sha512/init.luau @@ -292,10 +292,16 @@ local function sha512(message: string): string if messageLen >= 128 then processBlocks(digest_front, digest_back, message, 1, messageLen - leftover) end + + -- Raise `leftover` to next multiple of 128 so that we can calculate + -- how much padding we need without a branch or loop. + -- The number here is just masking off the last 6 bits. + local nextMultipleOf128 = bit32.band(leftover + 64, 0xfffff000) + local finalMessage = { if leftover ~= 0 then string.sub(message, -leftover) else "", "\x80", - string.rep("\0", 128 - (messageLen + 17) % 128), + string.rep("\0", (nextMultipleOf128 - leftover - 17) % 128), string.pack(">L", messageLen * 8 / 2 ^ 32), string.pack(">L", messageLen * 8 % 2 ^ 32), } diff --git a/testfiles/111-a.txt b/testfiles/111-a.txt new file mode 100644 index 0000000..d64a843 --- /dev/null +++ b/testfiles/111-a.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/testfiles/119-a.txt b/testfiles/119-a.txt new file mode 100644 index 0000000..1325c3d --- /dev/null +++ b/testfiles/119-a.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/testfiles/239-a.txt b/testfiles/239-a.txt new file mode 100644 index 0000000..9a98e2c --- /dev/null +++ b/testfiles/239-a.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/testfiles/55-a.txt b/testfiles/55-a.txt new file mode 100644 index 0000000..d1985dd --- /dev/null +++ b/testfiles/55-a.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/tests/sha1.luau b/tests/sha1.luau index 9bf6f62..67fd296 100644 --- a/tests/sha1.luau +++ b/tests/sha1.luau @@ -31,6 +31,33 @@ assert( == "fe32af74bc982dc5da23e54055f5515e948a10bd", "(SHA-1) Fitness-Gram hash does not match" ) + +assert( + sha1("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") == "c1c8bbdc22796e28c0e15163d20899b65621d65a", + "(SHA-1) 55 character a hash does not match" +) + +assert( + sha1( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) == "ee971065aaa017e0632a8ca6c77bb3bf8b1dfc56", + "(SHA-1) 119 character a hash does not match" +) + +assert( + sha1( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) == "ac877859d427d9192054eea8feb3b8a403ef83a5", + "(SHA-1) 111 character a hash does not match" +) + +assert( + sha1( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) == "fdc30857cf7b957f47ebd8288d5e5d7426f44394", + "(SHA-1) 239 character a hash does not match" +) + if true then local e = string.rep("e", 199999) local a = string.rep("a", 1e6) diff --git a/tests/sha224.luau b/tests/sha224.luau index 6598190..001d7a9 100644 --- a/tests/sha224.luau +++ b/tests/sha224.luau @@ -36,6 +36,33 @@ assert( "(SHA-224) Fitness-Gram hash does not match" ) +assert( + sha224("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + == "fb0bd626a70c28541dfa781bb5cc4d7d7f56622a58f01a0b1ddd646f", + "(SHA-224) 55 character a hash does not match" +) + +assert( + sha224( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) == "e000e6709d26667b631faa7fc1bd404eb4774003c5fb4f51a0184875", + "(SHA-224) 119 character a hash does not match" +) + +assert( + sha224( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) == "4aeec1a49b2c1bc663abf2809b36faaa64359523d4f26d02dbc2cba3", + "(SHA-224) 111 character a hash does not match" +) + +assert( + sha224( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) == "9da4e535cdffdbb7ee783ef7c6b61cbda7bcd4b15ce59d6ce5c2f099", + "(SHA-224) 239 character a hash does not match" +) + if true then local e = string.rep("e", 199999) local a = string.rep("a", 1e6) diff --git a/tests/sha256.luau b/tests/sha256.luau index dc6d4d9..4eb2856 100644 --- a/tests/sha256.luau +++ b/tests/sha256.luau @@ -48,6 +48,33 @@ assert( "(SHA-256) Fitness-Gram hash does not match" ) +assert( + sha256("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + == "9f4390f8d30c2dd92ec9f095b65e2b9ae9b0a925a5258e241c9f1e910f734318", + "(SHA-256) 55 character a hash does not match" +) + +assert( + sha256( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) == "31eba51c313a5c08226adf18d4a359cfdfd8d2e816b13f4af952f7ea6584dcfb", + "(SHA-256) 119 character a hash does not match" +) + +assert( + sha256( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) == "6374f73208854473827f6f6a3f43b1f53eaa3b82c21c1a6d69a2110b2a79baad", + "(SHA-256) 111 character a hash does not match" +) + +assert( + sha256( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) == "064b3d122abe25c36265f79fc794b0adf28a6c5e4fe8ed3661f2287e8cecadcc", + "(SHA-256) 239 character a hash does not match" +) + if true then local e = string.rep("e", 199999) local a = string.rep("a", 1e6) diff --git a/tests/sha384.luau b/tests/sha384.luau index 35b0cf6..addb38f 100644 --- a/tests/sha384.luau +++ b/tests/sha384.luau @@ -59,6 +59,33 @@ assert( "(SHA-384) Fitness-Gram hash does not match" ) +assert( + sha384("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + == "5d91ac7e74e62b5c728904b40f10784d66b7af9cb6302123e48c92f0432ceb8d2a92c02de77dcb29ed75c4b42bde46f4", + "(SHA-384) 55 character a hash does not match" +) + +assert( + sha384( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) == "c2fbb1911d6889e3db556b482236ab82f3c736f00a22c088641a09fdbbca27e3f1e3b6235bad20aee1ca083c76ac590c", + "(SHA-384) 119 character a hash does not match" +) + +assert( + sha384( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) == "3c37955051cb5c3026f94d551d5b5e2ac38d572ae4e07172085fed81f8466b8f90dc23a8ffcdea0b8d8e58e8fdacc80a", + "(SHA-384) 111 character a hash does not match" +) + +assert( + sha384( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) == "e247c35f4bc1aa38026f8880c8c97305545d00d3f859e00c57d1c1f0a176b3c6b749c4eb081f08bd0fba500969cd056a", + "(SHA-384) 239 character a hash does not match" +) + if true then local e = string.rep("e", 199999) local a = string.rep("a", 1e6) diff --git a/tests/sha512.luau b/tests/sha512.luau index 8402f55..db41a2c 100644 --- a/tests/sha512.luau +++ b/tests/sha512.luau @@ -65,6 +65,36 @@ assert( "(SHA-512) Fitness-Gram hash does not match" ) +assert( + sha512("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + == "b0220c772cbf6c1822e2cb38a437d0e1d58772417a4bbb21c961364f8b6143e05aa6316dca8d1d7b19e16448419076395f6086cb55101fbd6d5497b148e1745f", + "(SHA-512) 55 character a hash does not match" +) + +assert( + sha512( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) + == "130396a75cb483f2eee8c56d8a668bb3d2641f5243212c0bee2bd33da096ad9eb8179fe18f9eaacf76e09fae9de4c3f14ba13341e345be05bf76c182cc3468cb", + "(SHA-512) 119 character a hash does not match" +) + +assert( + sha512( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) + == "fa9121c7b32b9e01733d034cfc78cbf67f926c7ed83e82200ef86818196921760b4beff48404df811b953828274461673c68d04e297b0eb7b2b4d60fc6b566a2", + "(SHA-512) 111 character a hash does not match" +) + +assert( + sha512( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ) + == "52c853cb8d907f3d4d6b889beb027985d7c273486d75f8baf26f80d24e90c74c6c3de3e22131582380a7d14d43f2941a31385439cd6ddc469f628015e50bf286", + "(SHA-512) 239 character a hash does not match" +) + if true then local e = string.rep("e", 199999) local a = string.rep("a", 1e6)