3

This has been bothering be for a while now, I cannot seem to find a pure Lua implementation of a popular hashing method like SHA256, SHA512, or Whirlpool. I need it as I will be hashing the password client side before sending it of to a server. Speed isn't a worry here, I don't care if it takes 10 or so seconds to hash 10,000 times, I will be running it on a thread.

I have tried a couple before, which seemed like they worked perfectly fine at first, but when I tried a different input strings (usually longer ones), the hash comes out as a totally incorrect hash output.

I am using the LuaJIT version of Love2D, so it already has the BitOp library implemented. If any of you know any good implementations of these hashing methods or any similar secure ones then please let me know!

Thank you!

UPDATE: Here are some results!

Firstly this is the test code I am using.

https://github.com/JustAPerson/LuaCrypt

INPUT: Test string
OUTPUT:   a3e49d843df13c2e2a7786f6ecd7e0d184f45d718d1ac1a8a63e570466e489dd
EXPECTED: a3e49d843df13c2e2a7786f6ecd7e0d184f45d718d1ac1a8a63e570466e489dd

INPUT: This is a test string to hash
OUTPUT:   05b4ac920d4130cb9d9bb046cac7476f35d7404cf116dc8d6d4a113c3c79d904
EXPECTED: f70b476ff948472f8e4e52793a5a2779e636c20dd5336d3a8a4455374318db35

https://bitbucket.org/Boolsheet/sil/raw/tip/hash.lua

INPUT: Test string
OUTPUT:   8f1a5b37fbe986953c343d5b839b14843c6c29d47a6a7e52f263cd82ad6141a3
EXPECTED: a3e49d843df13c2e2a7786f6ecd7e0d184f45d718d1ac1a8a63e570466e489dd

INPUT: This is a test string to hash
OUTPUT:   167bf7b9000442419b3016a6e1edfcc7c8d40b5f0b80518a31ddb0bbd388e87ac
EXPECTED: f70b476ff948472f8e4e52793a5a2779e636c20dd5336d3a8a4455374318db35
greatwolf
  • 20,287
  • 13
  • 71
  • 105
Bicentric
  • 1,263
  • 3
  • 12
  • 12
  • Very officially, asking for a lib is off topic. If you cannot find it with Google, chances are that it does not exist. You said you've tried a couple, which ones did you try and which data led to incorrect hashes? – Maarten Bodewes Nov 14 '13 at 18:08
  • Note that client side password hashing does not remove the need for server side hashing. – ntoskrnl Nov 14 '13 at 18:20
  • 1
    https://github.com/JustAPerson/LuaCrypt and http://lua-users.org/wiki/SecureHashAlgorithm are the two libraries I have been using. And @ntoskrnl what do you mean client side hashing doesn't remove the need for server side hashing? I am not going to send their input as a plain string to the server, someone could intercept it and easily get their password. – Bicentric Nov 14 '13 at 18:44
  • 1
    But the hash is as good as the password if clients log in with that. You're going to need [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security) to prevent eavesdropping and other attacks. – ntoskrnl Nov 14 '13 at 19:05
  • Ahh I see! Thank you! I've added some results for @owlstead. – Bicentric Nov 14 '13 at 19:18
  • 1
    @Bicentric Here is an excellent article on the intricacies of password security and what's necessary on the server. [Password hashing](https://crackstation.net/hashing-security.htm). Its long but worth the read. – Macattack Nov 14 '13 at 19:30
  • @Macattack Ahhh! I see what you mean now! That makes sense. How about if I use a different authentication key each time? – Bicentric Nov 14 '13 at 20:44
  • Unfortunately speed is always an issue with password hashing. The slower your code, the fewer iteration you can afford. – CodesInChaos Nov 15 '13 at 09:56

2 Answers2

2

I would recommend against using SHA256 for passwords. They are easy to bruteforce nowadays, and the way you are using them is vulnerable to replay attacks.

Also if you must use SHA256, use the version from OpenSSL if possible (especially if your program already depends on OpenSSL.)

But if you must use it (and cannot link with OpenSSL, but can use FFI) here is a LuaJIT version of SHA256 (only) that I am using in one of my projects.

local bit = require 'bit'
local ffi = require 'ffi'

local type = type

local band, bnot, bswap, bxor, rol, ror, rshift, tobit =
  bit.band, bit.bnot, bit.bswap, bit.bxor, bit.rol, bit.ror, bit.rshift, bit.tobit

local min, max = math.min, math.max

local C = ffi.C
local istype, new, fill, copy, cast, sizeof, ffi_string =
  ffi.istype, ffi.new, ffi.fill, ffi.copy, ffi.cast, ffi.sizeof, ffi.string

local sha256 = {}

ffi.cdef [[
  void *malloc(size_t size);
  void free(void *ptr);
]]

local ctHashState = ffi.typeof 'uint32_t[8]'
local cbHashState = ffi.sizeof(ctHashState)
local ctBlock = ffi.typeof 'uint32_t[64]'
local cbBlock = ffi.sizeof(ctBlock)
local ctpu8 = ffi.typeof 'uint8_t *'
local ctpcu8 = ffi.typeof 'const uint8_t *'
local ctpu32 = ffi.typeof 'uint32_t *'
local ctpu64 = ffi.typeof 'uint64_t *'

-- This struct is used by the 'preprocess' iterator function.  It keeps track
-- of the end of the input string + the total input length in bits + a pointer
-- to the block buffer (where expansion takes place.)
local ctBlockIter
local cmtBlockIter = {}
function cmtBlockIter.__sub(a, b)
  if istype(ctBlockIter, a) then a = a.limit end
  if istype(ctBlockIter, b) then b = b.limit end
  return a - b
end
function cmtBlockIter:__tostring()
  return string.format("<ctBlockIter: limit=%s; keyLength=%s>",
                       tostring(self.base), tostring(self.keyLength))
end
ctBlockIter = ffi.metatype([[
  struct {
    const uint8_t *limit;
    uint32_t *blockBuffer;
    uint64_t keyLength;
  }
]], cmtBlockIter)

-- Initial state of the hash
local init_h = new('const uint32_t[8]', {
   0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
   0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
})

-- Constants used in the add step of the compression function
local k = new('const uint32_t[64]', {
   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
})

-- Expand block from 512 to 2048 bits
local function expand(w)
  for i = 16, 63 do
    local s0 = bxor(ror(w[i-15], 7), ror(w[i-15], 18), rshift(w[i-15], 3))
    local s1 = bxor(ror(w[i-2], 17), ror(w[i-2], 19), rshift(w[i-2], 10))
    w[i] = w[i-16] + s0 + w[i-7] + s1
  end
end

-- Process one expanded block and update the hash state
local function compress(hh, w)
  local a, b, c, d, e, f, g, h =
    hh[0],hh[1],hh[2],hh[3],hh[4],hh[5],hh[6],hh[7]
  for i = 0, 63 do
    local S1 = bxor(ror(e, 6), ror(e, 11), ror(e, 25))
    local ch = bxor(band(e, f), band(bnot(e), g))
    local t = tobit(h + S1 + ch + k[i] + w[i])
    local S0 = bxor(ror(a, 2), ror(a, 13), ror(a, 22))
    local maj = bxor(band(a, bxor(b, c)), band(b, c))
    a, b, c, d, e, f, g, h =
      tobit(t + S0 + maj),
      a, b, c,
      tobit(d + t),
      e, f, g
  end
  hh[0],hh[1],hh[2],hh[3],hh[4],hh[5],hh[6],hh[7] =
    hh[0]+a, hh[1]+b, hh[2]+c, hh[3]+d,
    hh[4]+e, hh[5]+f, hh[6]+g, hh[7]+h
end

-- Take a 512-bit chunk from the input.
-- If it is the final chunk, also add padding
local keyLengthOfs = ffi.offsetof(ctBlockIter, 'keyLength')
local function nextBlock(state, input)
  local w = state.blockBuffer
  local cLen = min(state - input, 64)
  if cLen < -8 then return nil end
  fill(w, 256, 0)
  copy(w, input, max(0, cLen))
  if 0 <= cLen and cLen < 64 then
    copy(cast(ctpu8, w)+cLen, '\128', 1)
  end
  for i = 0, 15 do w[i] = bswap(w[i]) end
  if cLen <= (64-8-1) then
    copy(cast(ctpu64, w) + 7, cast(ctpu8, state) + keyLengthOfs, 8)
    w[14], w[15] = w[15], w[14]
  end
  input = input + 64
  return input
end

-- Iterator that yields one block (possibly padded) at a time from the input
local function preprocess(input, len, w)
  len = len or (type(input) == 'string' and #input or sizeof(input))
  input = cast(ctpu8, input)
  local it = new(ctBlockIter)
  it.blockBuffer = w
  it.limit = input+len
  it.keyLength = len*8
  return nextBlock, it, input
end

-- Compute a binary hash (32-byte binary string) from the input
function sha256.binFromBin(input, len)
  local h = new(ctHashState)
  local w = cast(ctpu32, C.malloc(cbBlock))
  copy(h, init_h, cbHashState)
  for _ in preprocess(input, len, w) do
    expand(w)
    compress(h, w)
  end
  for i = 0, 7 do h[i] = bswap(h[i]) end
  C.free(w)
  return ffi_string(h, 32)
end

local hexDigits = new('char[16]', "0123456789abcdef")
local hexOut = new('char[65]')

-- Compute the hash and convert to hexadecimal
function sha256.hexFromBin(input, len)
  local h = new(ctHashState)
  local w = cast(ctpu32, C.malloc(cbBlock))
  copy(h, init_h, cbHashState)
  for _ in preprocess(input, len, w) do
    expand(w)
    compress(h, w)
  end
  for i = 0, 7 do
    local w = h[i]
    for j = 0, 3 do
      w = rol(w, 8)
      hexOut[i*8 + j*2] = hexDigits[band(rshift(w, 4), 15)]
      hexOut[i*8 + j*2 + 1] = hexDigits[band(w, 15)]
    end
  end
  C.free(w)
  return ffi_string(hexOut, 64)
end

return sha256
finnw
  • 47,861
  • 24
  • 143
  • 221
  • This looks very promising! However I am lost on how to use it, can you provide an example please? This was the code I was using for the first library I tried. http://pastebin.com/VMgixggy Apologies, I haven't been programming that long, only three years, and I never really got into that really complicated stuff involving bits and all. – Bicentric Nov 14 '13 at 21:56
  • @Bicentric, Example: `sha256.hexFromBin("This is a test string to hash")` returns `"f70b476ff948472f8e4e52793a5a2779e636c20dd5336d3a8a4455374318db35"` – finnw Nov 14 '13 at 22:33
  • Ahh okay I was doing it right in the first place then >.< As I got this error before: http://pbrd.co/188ALAP And I am still getting it now. – Bicentric Nov 14 '13 at 22:50
  • I cannot update my LuaJIT version since it's built into the application I am using, it's an open source application but I am pretty sure I would mess something up if I try to update it myself, that's why I don't want to implement a hashing library written in C / C++ to this application, as I may mess something up. Anyway, it seems to have fixed it (partly), now it gives this error after hashing a few times... http://pbrd.co/1iesSxs – Bicentric Nov 15 '13 at 01:59
  • @Bicentric, What version of LuaJIT do you have? You can find it with `print(jit.version)` – finnw Nov 15 '13 at 03:17
  • 1
    Funny, I have written something similar some time ago: https://github.com/catwell/cw-lua/tree/master/sha256 – catwell Nov 15 '13 at 10:48
  • @catwell yes very similar :-) – finnw Nov 15 '13 at 15:50
  • 'jit.version' is returning 'LuaJIT 2.0.0-beta9'. – Bicentric Nov 15 '13 at 16:01
  • @finnw - Why do you use `local cLen = min(state - input, 64)` with `cmtBlockIter.__sub` instead of direct `local cLen = min(state.limit - input, 64)`? It it faster? – Egor Skriptunoff Jun 13 '18 at 11:32
  • @EgorSkriptunoff I don't remember but it's unlikely to be faster. I think it may be because the C code that it was based on contained a pointer subtraction at that point and I was trying to keep the higher-level functions as similar to the C versions as possible, I don't recommend using this code anymore (but I cannot delete this answer.) – finnw Jun 19 '18 at 15:08
  • @finnw - Could you please explain why your code doesn't work correctly on 32-bit LuaJIT? Tested on Windows and Linux, OK for 64-bit LuaJIT, wrong results for 32-bit LuaJIT. – Egor Skriptunoff Aug 13 '18 at 12:15
  • @finnw - The bug could be fixed by appending `+ 0LL` to the line `w[i] = ...` – Egor Skriptunoff Aug 21 '18 at 14:24
1

There is an implementation of SHA256 at the Lua User's Wiki. The page observes it is Lua 5.2. I would imagine that it would be practical to make that work in LuaJIT without too much trouble.

Do pay attention to the larger security issues surrounding passwords and authentication. The usual advice applies; rolling your own security rather than using an existing tested and supported implementation is not something to be done lightly.

Since you are using LuaJIT, you should be able to leverage its very powerful FFI capabilities to use crypto supplied on your native platform. That will likely require writing some FFI-flavored Lua that is platform specific to each platform on which your client expects to run, but from what I've seen by lurking in the LuaJIT mailing list that shouldn't be too painful.

RBerteig
  • 41,948
  • 7
  • 88
  • 128