Last active
October 12, 2019 21:56
-
-
Save groverburger/4a5034054f50d1f85c9ee92787a45ca5 to your computer and use it in GitHub Desktop.
SHA256 implementation in pure Lua: clean, readable, and commented
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Bit32 = require "bit" | |
K_CONSTANTS = { | |
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, | |
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, | |
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, | |
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, | |
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, | |
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, | |
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, | |
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, | |
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, | |
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, | |
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, | |
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, | |
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, | |
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, | |
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, | |
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, | |
} | |
H_CONSTANTS = { | |
0x6a09e667, | |
0xbb67ae85, | |
0x3c6ef372, | |
0xa54ff53a, | |
0x510e527f, | |
0x9b05688c, | |
0x1f83d9ab, | |
0x5be0cd19, | |
} | |
function Sha256(input) | |
-- change input length to be %64 | |
local len = #input | |
local extra = 64 - ((len + 9) % 64) | |
len = NumberToString(8 * len, 8) | |
input = input .. "\128" .. string.rep("\0", extra) .. len | |
assert(#input % 64 == 0) | |
-- first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19 | |
HRegister = CopyTable(H_CONSTANTS) | |
-- loop through the input 64 characters at a time | |
for blockIndex = 1, #input, 64 do | |
PopulateWRegister(input, blockIndex) | |
SixtyFourRounds() | |
end | |
-- convert the 8 H Registers to hexidecimal and concat them together | |
local ret = "" | |
for i=1, 8 do | |
ret = ret .. StringToHexadecimal(NumberToString(HRegister[i], 4)) | |
end | |
return ret | |
end | |
-- block is the total message being digested | |
-- i refers to the index of the 64 characters in block being currently digested | |
function PopulateWRegister(block, blockIndex) | |
WRegister = {} | |
-- split up 64 char long block into 16 4-char long chunks | |
-- each chunk run the following operation on the ascii values of its 4 characters | |
-- char1*256^3 + char2*256^2 + char3*256 + char4 | |
-- store the result in WRegister[j], where j is the index of the chunk from 1 to 16 | |
for j = 1, 16 do | |
local chunkIndex = blockIndex + (j - 1)*4 | |
local char1 = string.byte(block, chunkIndex) | |
local char2 = string.byte(block, chunkIndex+1) | |
local char3 = string.byte(block, chunkIndex+2) | |
local char4 = string.byte(block, chunkIndex+3) | |
WRegister[j] = (256^3)*char1 + (256^2)*char2 + 256*char3 + char4 | |
end | |
-- this next section of WRegister is from 17 to 64 | |
-- this part of WRegister is populated based on the previous values of WRegister | |
for j = 17, 64 do | |
-- these are small sigma operations, similar to sigma operations | |
-- three input bitwise xor returns 1 if: | |
-- only one input is 1 | |
-- or all three are one | |
-- NOTE: the last input is right shifted, not right rotated! | |
-- therefore the first three bits are 0 | |
local linePrevious15 = WRegister[j-15] | |
local smallSigmaZero = Bit32.bxor(Bit32.ror(linePrevious15, 7), Bit32.ror(linePrevious15, 18), Bit32.rshift(linePrevious15, 3)) | |
local linePrevious2 = WRegister[j-2] | |
local smallSigmaOne = Bit32.bxor(Bit32.ror(linePrevious2, 17), Bit32.ror(linePrevious2, 19), Bit32.rshift(linePrevious2, 10)) | |
WRegister[j] = WRegister[j - 16] + WRegister[j - 7] + smallSigmaZero + smallSigmaOne | |
end | |
end | |
function SixtyFourRounds() | |
-- create 8 letter variables | |
-- assign HRegister values onto them (not pointers) | |
local a, b, c, d, e, f, g, h = HRegister[1], HRegister[2], HRegister[3], HRegister[4], HRegister[5], HRegister[6], HRegister[7], HRegister[8] | |
for i = 1, 64 do | |
-- rotate variable a a bit to right | |
local sigmaZero = Bit32.bxor(Bit32.ror(a, 2), Bit32.ror(a, 13), Bit32.ror(a, 22)) | |
-- find the major bit on a,b,c | |
local major = Bit32.bxor(Bit32.band(a, b), Bit32.band(a, c), Bit32.band(b, c)) | |
-- rotate variable e a bit to the right | |
local sigmaOne = Bit32.bxor(Bit32.ror(e, 6), Bit32.ror(e, 11), Bit32.ror(e, 25)) | |
-- choose based on e: 1 -> f, 0 -> g | |
local choose = Bit32.bxor (Bit32.band(e, f), Bit32.band(Bit32.bnot(e), g)) | |
-- this is the only place that K_CONSTANTS and WRegister are used in actually constructing the hash digest | |
-- NOTE: the only part of the digest that changes between different inputs is the WRegister, compiled before the 64 rounds even start! | |
local modifiedH = h + sigmaOne + choose + K_CONSTANTS[i] + WRegister[i] | |
-- figure out what the new letter registers are going to be | |
-- the letter registers for the most part rotate one letter to the left | |
-- the only ones that change are a and e | |
local hNew = g | |
local gNew = f | |
local fNew = e | |
local eNew = d + modifiedH | |
local dNew = c | |
local cNew = b | |
local bNew = a | |
local aNew = modifiedH + sigmaZero + major | |
h, g, f, e, d, c, b, a = hNew, gNew, fNew, eNew, dNew, cNew, bNew, aNew | |
LastSigmaZero = sigmaZero | |
LastModifiedH = modifiedH | |
LastSigmaOne = sigmaOne | |
LastMajor = major | |
LastChoose = choose | |
end | |
-- and the original HRegister from before the 64 rounds with their newer counterparts | |
-- this creates the new eight HRegisters which are then transformed into hexadecimal and become the output of the hash | |
-- bitwise and is run on these registers to shorten them to exactly 4 bytes each, from however long they were | |
HRegister[1] = Bit32.band(HRegister[1] + a) | |
HRegister[2] = Bit32.band(HRegister[2] + b) | |
HRegister[3] = Bit32.band(HRegister[3] + c) | |
HRegister[4] = Bit32.band(HRegister[4] + d) | |
HRegister[5] = Bit32.band(HRegister[5] + e) | |
HRegister[6] = Bit32.band(HRegister[6] + f) | |
HRegister[7] = Bit32.band(HRegister[7] + g) | |
HRegister[8] = Bit32.band(HRegister[8] + h) | |
end | |
-------------------------------------------------------------------------------------------------------------- | |
-- PRINT AND CONVERSION FUNCTIONS | |
-------------------------------------------------------------------------------------------------------------- | |
function NumberToString(length, terminator) | |
local s = "" | |
for i = 1, terminator do | |
local remainder = length % 256 | |
s = string.char(remainder) .. s | |
length = (length - remainder) / 256 | |
end | |
return s | |
end | |
function StringToHexadecimal(s) | |
return (string.gsub(s, ".", function(c) return string.format("%02x", string.byte(c)) end)) | |
end | |
function CopyTable(orig) | |
local orig_type = type(orig) | |
local copy | |
if orig_type == 'table' then | |
copy = {} | |
for orig_key, orig_value in next, orig, nil do | |
copy[CopyTable(orig_key)] = CopyTable(orig_value) | |
end | |
setmetatable(copy, CopyTable(getmetatable(orig))) | |
else -- number, string, boolean, etc | |
copy = orig | |
end | |
return copy | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment