Skip to content

Commit dd92fd3

Browse files
HMAC added
1 parent 5d85cc4 commit dd92fd3

File tree

3 files changed

+158
-26
lines changed

3 files changed

+158
-26
lines changed

README.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ SHA-512/256 -- sha2.sha512_256()
2727
-- and a small bonus:
2828
MD5 -- sha2.md5()
2929
SHA-1 -- sha2.sha1()
30+
HMAC -- sha2.hmac()
3031
```
3132
---
3233
### Usage
@@ -49,8 +50,8 @@ See file "sha2_test.lua" for more examples.
4950
* **Q:** Does this module calculate SHA2 really fast?
5051
* **A:**
5152
Probably, this is the fastest pure Lua implementation of SHA2 you can find.
52-
For example, on x64 Lua 5.3 this module calculates SHA256 twice as fast as the implementation published at [lua-users.org](http://lua-users.org/wiki/SecureHashAlgorithmBw)
53-
This module has best performance on every Lua version because it contains several version-specific implementation branches:
53+
For example, on x64 Lua 5.3 this module calculates SHA256 twice as fast as the implementation published at [lua-users.org](http://lua-users.org/wiki/SecureHashAlgorithmBw)
54+
This module has best performance on every Lua version because it contains several version-specific implementation branches:
5455
- branch for **Lua 5.1** (emulating bitwise operators using look-up table)
5556
- branch for **Lua 5.2** (using **bit32** library), suitable also for **Lua 5.1** with external **bit** library
5657
- branch for **Lua 5.3 / 5.4** (using native **64**-bit bitwise operators)
@@ -62,12 +63,12 @@ Probably, this is the fastest pure Lua implementation of SHA2 you can find.
6263

6364
---
6465
* **Q:** How to get SHA2 digest as binary string instead of hexadecimal representation?
65-
* **A:**
66+
* **A:**
67+
Use function `sha2.hex2bin()` to convert hexadecimal to binary:
6668
```lua
6769
local sha2 = require("sha2")
68-
local your_hex_hash = sha2.sha256("your string")
69-
local your_binary_hash = your_hex_hash:gsub("%x%x", function(h) return h.char(tonumber(h, 16)) end)
70-
-- assert(your_binary_hash == "\209Mi\29\172p\234\218\20\217\242>\248\0\145\188\161\199\\\247|\241\205\\\242\208A\128\202\r\153\17")
70+
local binary_hash = sha2.hex2bin(sha2.sha256("your string"))
71+
-- assert(binary_hash == "\209Mi\29\172p\234\218\20\217\242>\248\0\145\188\161\199\\\247|\241\205\\\242\208A\128\202\r\153\17")
7172
```
7273

7374
---
@@ -83,6 +84,25 @@ local your_hash = append() -- and finally ask for the result
8384
-- assert(your_hash == "d14d691dac70eada14d9f23ef80091bca1c75cf77cf1cd5cf2d04180ca0d9911")
8485
```
8586

87+
---
88+
* **Q:** How to calculate HMAC-SHA1 ?
89+
* **A:**
90+
```lua
91+
local sha2 = require("sha2")
92+
local your_hmac = sha2.hmac(sha2.sha1, "your key", "your message")
93+
-- assert(your_hmac == "317d0dfd868a5c06c9444ac1328aa3e2bfd29fb2")
94+
```
95+
The same in chunk-by-chunk mode (for long messages):
96+
```lua
97+
local sha2 = require("sha2")
98+
local append = sha2.hmac(sha2.sha1, "your key")
99+
append("your")
100+
append(" mess")
101+
append("age")
102+
local your_hmac = append()
103+
-- assert(your_hmac == "317d0dfd868a5c06c9444ac1328aa3e2bfd29fb2")
104+
```
105+
86106
---
87107
### Backward-compatibility
88108
This module will always keep backward-compatibility.

sha2.lua

Lines changed: 103 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
--------------------------------------------------------------------------------------------------------------------------
44
-- MODULE: sha2
55
--
6-
-- VERSION: 5 (2018-11-10)
6+
-- VERSION: 6 (2018-11-12)
77
--
88
-- DESCRIPTION:
99
-- This module contains functions to calculate SHA2 digest:
1010
-- SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256
11-
-- and a bonus: MD5, SHA-1
11+
-- and a bonus: MD5, SHA-1, HMAC
1212
-- Written in pure Lua.
1313
-- Compatible with:
1414
-- Lua 5.1, Lua 5.2, Lua 5.3, Lua 5.4, Fengari, LuaJIT 2.0/2.1 (any CPU endianness).
@@ -37,11 +37,12 @@
3737
-- CHANGELOG:
3838
-- version date description
3939
-- ------- ---------- -----------
40-
-- 1 2018-10-06 First release
41-
-- 2 2018-10-07 Decreased module loading time in Lua 5.1 implementation branch (thanks to Peter Melnichenko for giving a hint)
42-
-- 3 2018-11-02 Bug fixed: incorrect hashing of long (2 GByte) data streams on Lua 5.3/5.4 built with "int32" integers
43-
-- 4 2018-11-03 Bonus added: MD5
40+
-- 6 2018-11-12 HMAC added (applicable to any hash function from this module)
4441
-- 5 2018-11-10 One more bonus added: SHA-1
42+
-- 4 2018-11-03 Bonus added: MD5
43+
-- 3 2018-11-02 Bug fixed: incorrect hashing of long (2 GByte) data streams on Lua 5.3/5.4 built with "int32" integers
44+
-- 2 2018-10-07 Decreased module loading time in Lua 5.1 implementation branch (thanks to Peter Melnichenko for giving a hint)
45+
-- 1 2018-10-06 First release
4546
-----------------------------------------------------------------------------
4647

4748

@@ -185,7 +186,7 @@ end
185186
-- BASIC 32-BIT BITWISE FUNCTIONS
186187
--------------------------------------------------------------------------------
187188

188-
local AND, OR, XOR, SHL, SHR, ROL, ROR, NOT, NORM, HEX
189+
local AND, OR, XOR, SHL, SHR, ROL, ROR, NOT, NORM, HEX, XOR_BYTE
189190
-- Only low 32 bits of function arguments matter, high bits are ignored
190191
-- The result of all functions (except HEX) is an integer inside "correct range":
191192
-- for "bit" library: (-2^31)..(2^31-1)
@@ -205,6 +206,7 @@ if branch == "FFI" or branch == "LJ" or branch == "LIB32" then
205206
NORM = b.tobit -- only for LuaJIT
206207
HEX = b.tohex -- returns string of 8 lowercase hexadecimal digits
207208
assert(AND and OR and XOR and SHL and SHR and ROL and ROR and NOT, "Library '"..library_name.."' is incomplete")
209+
XOR_BYTE = XOR -- XOR of two bytes (only for HMAC), inputs and output are 0..255
208210

209211
elseif branch == "EMUL" then
210212

@@ -286,6 +288,10 @@ elseif branch == "EMUL" then
286288
return and_or_xor(x, y, 2)
287289
end
288290

291+
function XOR_BYTE(x, y)
292+
return x + y - 2 * AND_of_two_bytes[x + y * 256]
293+
end
294+
289295
end
290296

291297
HEX = HEX or
@@ -308,7 +314,8 @@ local sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64
308314
local sha2_K_lo, sha2_K_hi, sha2_H_lo, sha2_H_hi = {}, {}, {}, {}
309315
local sha2_H_ext256 = {[224] = {}, [256] = sha2_H_hi}
310316
local sha2_H_ext512_lo, sha2_H_ext512_hi = {[384] = {}, [512] = sha2_H_lo}, {[384] = {}, [512] = sha2_H_hi}
311-
local md5_K, md5_sha1_H, md5_next_shift = {}, {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0}, {0, 0, 0, 0, 0, 0, 0, 0, 28, 25, 26, 27, 0, 0, 10, 9, 11, 12, 0, 15, 16, 17, 18, 0, 20, 22, 23, 21}
317+
local md5_K, md5_sha1_H = {}, {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0}
318+
local md5_next_shift = {0, 0, 0, 0, 0, 0, 0, 0, 28, 25, 26, 27, 0, 0, 10, 9, 11, 12, 0, 15, 16, 17, 18, 0, 20, 22, 23, 21}
312319

313320
local HEX64, XOR64A5 -- defined only for branches that internally use 64-bit integers: "INT64" and "FFI"
314321
local common_W = {} -- temporary table shared between all calculations (to avoid creating new temporary table every time)
@@ -945,7 +952,7 @@ if branch == "INT64" then
945952

946953
hi_factor = 4294967296
947954

948-
HEX64, XOR64A5, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64 = load[[
955+
HEX64, XOR64A5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64 = load[[
949956
local md5_next_shift = ...
950957
local string_format, string_unpack = string.format, string.unpack
951958
@@ -957,6 +964,10 @@ if branch == "INT64" then
957964
return x ~ 0xa5a5a5a5a5a5a5a5
958965
end
959966
967+
local function XOR_BYTE(x, y)
968+
return x ~ y
969+
end
970+
960971
local common_W = {}
961972
962973
local function sha256_feed_64(H, K, str, offs, size)
@@ -1140,7 +1151,7 @@ if branch == "INT64" then
11401151
H[1], H[2], H[3], H[4], H[5] = h1, h2, h3, h4, h5
11411152
end
11421153
1143-
return HEX64, XOR64A5, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64
1154+
return HEX64, XOR64A5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64
11441155
]](md5_next_shift)
11451156

11461157
end
@@ -1156,14 +1167,18 @@ if branch == "INT32" then
11561167
return string_format("%08x", x)
11571168
end
11581169

1159-
XOR32A5, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64 = load[[
1170+
XOR32A5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64 = load[[
11601171
local md5_next_shift = ...
11611172
local string_unpack, floor = string.unpack, math.floor
11621173
11631174
local function XOR32A5(x)
11641175
return x ~ 0xA5A5A5A5
11651176
end
11661177
1178+
local function XOR_BYTE(x, y)
1179+
return x ~ y
1180+
end
1181+
11671182
local common_W = {}
11681183
11691184
local function sha256_feed_64(H, K, str, offs, size)
@@ -1383,7 +1398,7 @@ if branch == "INT32" then
13831398
H[1], H[2], H[3], H[4], H[5] = h1, h2, h3, h4, h5
13841399
end
13851400
1386-
return XOR32A5, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64
1401+
return XOR32A5, XOR_BYTE, sha256_feed_64, sha512_feed_128, md5_feed_64, sha1_feed_64
13871402
]](md5_next_shift)
13881403

13891404
end
@@ -1728,7 +1743,7 @@ local function sha256ext(width, text)
17281743
tail = tail..sub(text_part, #text_part + 1 - size_tail)
17291744
return partial
17301745
else
1731-
error("Adding more chunks is not allowed after receiving the final result", 2)
1746+
error("Adding more chunks is not allowed after receiving the result", 2)
17321747
end
17331748
else
17341749
if tail then
@@ -1787,7 +1802,7 @@ local function sha512ext(width, text)
17871802
tail = tail..sub(text_part, #text_part + 1 - size_tail)
17881803
return partial
17891804
else
1790-
error("Adding more chunks is not allowed after receiving the final result", 2)
1805+
error("Adding more chunks is not allowed after receiving the result", 2)
17911806
end
17921807
else
17931808
if tail then
@@ -1852,7 +1867,7 @@ local function md5(text)
18521867
tail = tail..sub(text_part, #text_part + 1 - size_tail)
18531868
return partial
18541869
else
1855-
error("Adding more chunks is not allowed after receiving the final result", 2)
1870+
error("Adding more chunks is not allowed after receiving the result", 2)
18561871
end
18571872
else
18581873
if tail then
@@ -1908,7 +1923,7 @@ local function sha1(text)
19081923
tail = tail..sub(text_part, #text_part + 1 - size_tail)
19091924
return partial
19101925
else
1911-
error("Adding more chunks is not allowed after receiving the final result", 2)
1926+
error("Adding more chunks is not allowed after receiving the result", 2)
19121927
end
19131928
else
19141929
if tail then
@@ -1944,17 +1959,87 @@ local function sha1(text)
19441959
end
19451960

19461961

1962+
local function hex2bin(hex_string)
1963+
return (gsub(hex_string, "%x%x",
1964+
function (hh)
1965+
return char(tonumber(hh, 16))
1966+
end
1967+
))
1968+
end
1969+
1970+
1971+
local block_size_for_HMAC -- a table, will be defined at the end of the module
1972+
1973+
local function pad_and_xor(str, result_length, byte_for_xor)
1974+
return gsub(str, ".",
1975+
function(c)
1976+
return char(XOR_BYTE(byte(c), byte_for_xor))
1977+
end
1978+
)..string_rep(char(byte_for_xor), result_length - #str)
1979+
end
1980+
1981+
local function hmac(hash_func, key, message)
1982+
1983+
-- Create an instance (private objects for current calculation)
1984+
local block_size = block_size_for_HMAC[hash_func]
1985+
if not block_size then
1986+
error("Unknown hash function", 2)
1987+
end
1988+
if #key > block_size then
1989+
key = hex2bin(hash_func(key))
1990+
end
1991+
local append = hash_func()(pad_and_xor(key, block_size, 0x36))
1992+
local result
1993+
1994+
local function partial(message_part)
1995+
if not message_part then
1996+
result = result or hash_func(pad_and_xor(key, block_size, 0x5C)..hex2bin(append()))
1997+
return result
1998+
elseif result then
1999+
error("Adding more chunks is not allowed after receiving the result", 2)
2000+
else
2001+
append(message_part)
2002+
return partial
2003+
end
2004+
end
2005+
2006+
if message then
2007+
-- Actually perform calculations and return the HMAC of a message
2008+
return partial(message)()
2009+
else
2010+
-- Return function for chunk-by-chunk loading of a message
2011+
-- User should feed every chunk of the message as single argument to this function and finally get HMAC by invoking this function without an argument
2012+
return partial
2013+
end
2014+
2015+
end
2016+
2017+
19472018
local sha2 = {
1948-
-- SHA2 functions:
2019+
-- SHA2 hash functions:
19492020
sha256 = function (text) return sha256ext(256, text) end, -- SHA-256
19502021
sha224 = function (text) return sha256ext(224, text) end, -- SHA-224
19512022
sha512 = function (text) return sha512ext(512, text) end, -- SHA-512
19522023
sha384 = function (text) return sha512ext(384, text) end, -- SHA-384
19532024
sha512_224 = function (text) return sha512ext(224, text) end, -- SHA-512/224
19542025
sha512_256 = function (text) return sha512ext(256, text) end, -- SHA-512/256
1955-
-- bonus:
2026+
-- other hash functions:
19562027
md5 = md5, -- MD5
19572028
sha1 = sha1, -- SHA-1
2029+
-- misc utilities:
2030+
hmac = hmac, -- HMAC (applicable to any hash function from this module)
2031+
hex2bin = hex2bin, -- converts hexadecimal representation to binary string
2032+
}
2033+
2034+
block_size_for_HMAC = {
2035+
[sha2.sha256] = 64, -- SHA-256
2036+
[sha2.sha224] = 64, -- SHA-224
2037+
[sha2.sha512] = 128, -- SHA-512
2038+
[sha2.sha384] = 128, -- SHA-384
2039+
[sha2.sha512_224] = 128, -- SHA-512/224
2040+
[sha2.sha512_256] = 128, -- SHA-512/256
2041+
[sha2.md5] = 64, -- MD5
2042+
[sha2.sha1] = 64, -- SHA-1
19582043
}
19592044

19602045
return sha2

sha2_test.lua

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ local function test_sha256()
2323
append_next_chunk(" jumps ")
2424
append_next_chunk("") -- chunk may be an empty string
2525
append_next_chunk("over the lazy dog")
26-
assert(append_next_chunk() == "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592") -- invocation without an argument means "give me the final result"
26+
assert(append_next_chunk() == "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592") -- invocation without an argument means "give me the result"
2727
assert(append_next_chunk() == "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592") -- you can ask the same result multiple times if needed
28-
assert(not pcall(append_next_chunk, "more text")) -- no more chunks are allowed after receiving the final result, append_next_chunk("more text") will fail
28+
assert(not pcall(append_next_chunk, "more text")) -- no more chunks are allowed after receiving the result, append_next_chunk("more text") will fail
2929

3030
-- one-liner is possible due to "append_next_chunk(chunk)" returns the function "append_next_chunk"
3131
assert(sha256()("The quick brown fox")(" jumps ")("")("over the lazy dog")() == "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592")
@@ -300,6 +300,31 @@ local function test_sha1()
300300
end
301301

302302

303+
local function test_hmac()
304+
305+
local hmac = sha2.hmac
306+
307+
assert(hmac(sha2.sha1, "your key", "your message") == "317d0dfd868a5c06c9444ac1328aa3e2bfd29fb2")
308+
assert(hmac(sha2.sha512, "your key", "your message") == "2f5ddcdbd062a5392f07b0cd0262bf52c21bfb3db513296240cca8d5accc09d18d96be0a94995be4494c032f1eda946ad549fb61ccbe985d160f0b2f9588d34b")
309+
assert(hmac(sha2.md5, "", "") == "74e6f7298a9c2d168935f58c001bad88")
310+
assert(hmac(sha2.sha256, "", "") == "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad")
311+
assert(hmac(sha2.sha1, "", "") == "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d")
312+
assert(hmac(sha2.md5, "key", "The quick brown fox jumps over the lazy dog") == "80070713463e7749b90c2dc24911e275")
313+
assert(hmac(sha2.sha256, "key", "The quick brown fox jumps over the lazy dog") == "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8")
314+
assert(hmac(sha2.sha1, "key", "The quick brown fox jumps over the lazy dog") == "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9")
315+
316+
-- chunk-by-chunk mode
317+
local append = hmac(sha2.sha1, "key")
318+
append("The quick brown fox")
319+
append("") -- empty string is allowed as a valid chunk
320+
append(" jumps over the lazy dog")
321+
assert(append() == "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9") -- invocation without an argument receives the result
322+
323+
assert(not pcall(hmac, function(x) return sha2.sha256(x) end, "key", "message")) -- must generate "unknown hash function" error
324+
325+
end
326+
327+
303328
local function test_all()
304329

305330
test_sha256()
@@ -322,6 +347,8 @@ local function test_all()
322347

323348
test_sha1()
324349

350+
test_hmac()
351+
325352
print"All tests passed"
326353

327354
end

0 commit comments

Comments
 (0)