|
| 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