Skip to content

Commit dafa5ba

Browse files
committed
Add a Lua implementation for MD5 checksum calculation
This script computes the MD5 checksum for files using Lua. It includes core MD5 transformations, padding, and file processing logic. The implementation also displays the checksum along with the file name or provides error handling for invalid inputs. The output mimics GNU coreutils version of `md5sum` being ran without any flags.
1 parent 8320f3f commit dafa5ba

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed

demo/extra/md5sum.lua

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#!/usr/bin/env lua
2+
3+
local T = {} -- Predefined MD5 constants of sine-based shifts (T values)
4+
for i = 1, 64 do
5+
T[i] = math.floor(2 ^ 32 * math.abs(math.sin(i)))
6+
end
7+
8+
--- Read a files hash from it's current point to the end
9+
--- @param file file The file to read to the end
10+
--- @return string The md5 checksum result of the file
11+
function md5_file(file)
12+
local function Md5Transform(chunk, A, B, C, D) -- Core MD5 transformation
13+
local F = function(x, y, z)
14+
return (x & y) | (~x & z)
15+
end
16+
17+
local G = function(x, y, z)
18+
return (x & z) | (y & ~z)
19+
end
20+
21+
local H = function(x, y, z)
22+
return x ~ y ~ z
23+
end
24+
25+
local I = function(x, y, z)
26+
return y ~ (x | ~z)
27+
end
28+
29+
local function LShift32(x, n)
30+
return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF
31+
end
32+
33+
local words, shifts = {}, { { 7, 12, 17, 22 },
34+
{ 5, 9, 14, 20 },
35+
{ 4, 11, 16, 23 },
36+
{ 6, 10, 15, 21 }
37+
}
38+
39+
for i = 0, 15 do -- Break the chunk into 16 little-endian 32-bit words
40+
local offset = i * 4 + 1
41+
words[i + 1] = string.byte(chunk, offset) |
42+
(string.byte(chunk, offset + 1) << 8) |
43+
(string.byte(chunk, offset + 2) << 16) |
44+
(string.byte(chunk, offset + 3) << 24)
45+
end
46+
47+
local a, b, c, d = A, B, C, D
48+
49+
for i = 1, 64 do -- Main loop: 64 rounds of transformations
50+
local f, g
51+
local round = math.floor((i - 1) / 16) + 1
52+
local shift = shifts[round][(i - 1) % 4 + 1]
53+
54+
if round == 1 then
55+
f = F(b, c, d)
56+
g = (i - 1) % 16
57+
elseif round == 2 then
58+
f = G(b, c, d)
59+
g = (5 * (i - 1) + 1) % 16
60+
elseif round == 3 then
61+
f = H(b, c, d)
62+
g = (3 * (i - 1) + 5) % 16
63+
elseif round == 4 then
64+
f = I(b, c, d)
65+
g = (7 * (i - 1)) % 16
66+
end
67+
68+
local temp = d
69+
d = c
70+
c = b
71+
b = (b + LShift32((a + f + words[g + 1] +
72+
T[i]) & 0xFFFFFFFF, shift)) & 0xFFFFFFFF
73+
a = temp
74+
end
75+
76+
-- Add chunk's hash to the result so far
77+
A = (A + a) & 0xFFFFFFFF
78+
B = (B + b) & 0xFFFFFFFF
79+
C = (C + c) & 0xFFFFFFFF
80+
D = (D + d) & 0xFFFFFFFF
81+
82+
return A, B, C, D
83+
end
84+
85+
local function Md5Pad(messageLength)
86+
local msg_len = messageLength * 8 -- Message length in bits
87+
local padding = "\128" -- Initial padding
88+
local pad_len = (56 - (messageLength % 64)) -- 448 mod 512
89+
90+
if pad_len <= 0 then
91+
pad_len = pad_len + 64
92+
end
93+
94+
padding = padding .. string.rep("\0", pad_len - 1)
95+
96+
-- Append the original message length as a 64-bit little-endian integer
97+
for i = 0, 7 do
98+
padding = padding .. string.char((msg_len >> (8 * i)) & 0xFF)
99+
end
100+
101+
return padding
102+
end
103+
104+
local A, B, C, D, len = 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0
105+
106+
while true do
107+
local chunk = file:read(64) -- Read in chunks of 64 bytes (512 bits)
108+
109+
if not chunk then break end
110+
len = len + #chunk
111+
112+
-- If it's the final chunk, apply padding
113+
if #chunk < 64 then chunk = chunk .. Md5Pad(len) end
114+
A, B, C, D = Md5Transform(chunk, A, B, C, D)
115+
116+
-- Stop processing after the padded block
117+
if #chunk > 64 then break end
118+
end
119+
120+
-- Format output as a hexadecimal string in little-endian order
121+
local function to_hex(x)
122+
return string.format("%02x%02x%02x%02x",
123+
x & 0xFF, (x >> 8) & 0xFF, (x >> 16) & 0xFF, (x >> 24) & 0xFF)
124+
end
125+
126+
return to_hex(A) .. to_hex(B) .. to_hex(C) .. to_hex(D)
127+
end
128+
129+
if #arg < 1 then
130+
-- Show accurate Lua binary and script location
131+
print((arg[-1] or "?") .. " " .. (arg[0] or "?") .. " [FILE]...")
132+
os.exit(1)
133+
else
134+
for i = 1, #arg do
135+
local file, err = io.open(arg[i], "rb")
136+
137+
if file then
138+
local sum = md5_file(file)
139+
file:close()
140+
141+
if sum then
142+
print(sum .. " " .. arg[i])
143+
else
144+
print(arg[i] .. ": " .. "Unknown error")
145+
os.exit(-1)
146+
end
147+
else
148+
print(arg[i] .. ": " .. err)
149+
os.exit(1)
150+
end
151+
end
152+
end

0 commit comments

Comments
 (0)