Skip to content

Instantly share code, notes, and snippets.

@groverburger
Last active October 12, 2019 21:56
Show Gist options
  • Save groverburger/4a5034054f50d1f85c9ee92787a45ca5 to your computer and use it in GitHub Desktop.
Save groverburger/4a5034054f50d1f85c9ee92787a45ca5 to your computer and use it in GitHub Desktop.
SHA256 implementation in pure Lua: clean, readable, and commented
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