diff --git a/README.markdown b/README.markdown index 887684b..2ec7a70 100644 --- a/README.markdown +++ b/README.markdown @@ -168,6 +168,15 @@ Synopsis ngx.say("AES 128 CBC (WITH IV) Encrypted HEX: ", str.to_hex(encrypted)) ngx.say("AES 128 CBC (WITH IV) Decrypted: ", aes_128_cbc_with_iv:decrypt(encrypted)) + + + -- example with HMAC-SHA256 + -- there are other algorithms supported as well: + -- sha1, sha224, sha256, sha384, sha512 + local resty_hmac_sha256 = require "resty.hmac" + local hmac_sha256 = resty_hmac_sha256:new() + local digest = hmac_sha256:digest("sha256","secret-key","Hello world") + ngx.say("hmac_sha256: ", digest) ``` [Back to TOC](#table-of-contents) diff --git a/lib/resty/hmac.lua b/lib/resty/hmac.lua new file mode 100644 index 0000000..0911f60 --- /dev/null +++ b/lib/resty/hmac.lua @@ -0,0 +1,94 @@ +-- Adds HMAC support to Lua with multiple algorithms, via OpenSSL and FFI +-- +-- Author: ddragosd@gmail.com +-- Date: 16/05/14 +-- + + +local ffi = require "ffi" +local ffi_new = ffi.new +local ffi_str = ffi.string +local C = ffi.C +local resty_string = require "resty.string" +local setmetatable = setmetatable +local error = error + + +local _M = { _VERSION = '0.09' } + + +local mt = { __index = _M } + +-- +-- EVP_MD is defined in openssl/evp.h +-- HMAC is defined in openssl/hmac.h +-- +ffi.cdef[[ +typedef struct env_md_st EVP_MD; +typedef struct env_md_ctx_st EVP_MD_CTX; +unsigned char *HMAC(const EVP_MD *evp_md, const void *key, int key_len, + const unsigned char *d, size_t n, unsigned char *md, + unsigned int *md_len); +const EVP_MD *EVP_sha1(void); +const EVP_MD *EVP_sha224(void); +const EVP_MD *EVP_sha256(void); +const EVP_MD *EVP_sha384(void); +const EVP_MD *EVP_sha512(void); +]] + +-- table definind the available algorithms and the length of each digest +-- for more information @see: http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf +local available_algorithms = { + sha1 = { alg = C.EVP_sha1(), length = 160/8 }, + sha224 = { alg = C.EVP_sha224(), length = 224/8 }, + sha256 = { alg = C.EVP_sha256(), length = 256/8 }, + sha384 = { alg = C.EVP_sha384(), length = 384/8 }, + sha512 = { alg = C.EVP_sha512(), length = 512/8 } +} + +-- 64 is the max lenght and it covers up to sha512 algorithm +local digest_len = ffi_new("int[?]", 64) +local buf = ffi_new("char[?]", 64) + + +function _M.new(self) + return setmetatable({}, mt) +end + +local function getDigestAlgorithm(dtype) + local md_name = available_algorithms[dtype] + if ( md_name == nil ) then + error("attempt to use unknown algorithm: '" .. dtype .. + "'.\n Available algorithms are: sha1,sha224,sha256,sha384,sha512") + end + return md_name.alg, md_name.length +end + +--- +-- Returns the HMAC digest. The hashing algorithm is defined by the dtype parameter. +-- The optional raw flag, defaulted to false, is a boolean indicating whether the output should be a direct binary +-- equivalent of the HMAC or formatted as a hexadecimal string (the default) +-- +-- @param self +-- @param dtype The hashing algorithm to use is specified by dtype +-- @param key The secret +-- @param msg The message to be signed +-- @param raw When true, it returns the binary format, else, the hex format is returned +-- +function _M.digest(self, dtype, key, msg, raw) + local evp_md, digest_length_int = getDigestAlgorithm(dtype) + if key == nil or msg == nil then + error("attempt to digest with a null key or message") + end + + C.HMAC(evp_md, key, #key, msg, #msg, buf, digest_len) + + if raw == true then + return ffi_str(buf,digest_length_int) + end + + return resty_string.to_hex(ffi_str(buf,digest_length_int)) +end + + +return _M diff --git a/t/hmac.t b/t/hmac.t new file mode 100644 index 0000000..5b147a7 --- /dev/null +++ b/t/hmac.t @@ -0,0 +1,195 @@ +# vi:ft= + +use Test::Nginx::Socket; + +repeat_each(2); + +plan tests => repeat_each() * (3 * blocks()); + +our $HttpConfig = <<'_EOC_'; + #lua_code_cache off; + lua_package_path 'lib/?.lua;;'; + lua_package_cpath 'lib/?.so;;'; +_EOC_ + +no_long_string(); + +run_tests(); + +__DATA__ + +=== TEST 1: hello HMAC-SHA-1 +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua ' + local resty_hmac_sha1 = require "resty.hmac" + local hmac_sha1 = resty_hmac_sha1:new() + + local digest = hmac_sha1:digest("sha1","secret-key","Hello world") + ngx.say("hmac_sha1: ", digest) + + --test with an empty string + digest = hmac_sha1:digest("sha1","secret-key","") + ngx.say("hmac_sha1: ", digest) + '; + } +--- request +GET /t +--- response_body +hmac_sha1: 3a20c85ba3af4c1b1eec24c672cfb3db803e3637 +hmac_sha1: 0877fcf3af864ddf56157f9f4e39eb48dedd74fd +--- no_error_log +[error] + +=== TEST 2: hello HMAC-SHA-224 +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua ' + local resty_hmac_sha224 = require "resty.hmac" + local hmac_sha224 = resty_hmac_sha224:new() + + local digest = hmac_sha224:digest("sha224","secret-key","Hello world") + ngx.say("hmac_sha224: ", digest) + + --test with an empty string + digest = hmac_sha224:digest("sha224","secret-key","") + ngx.say("hmac_sha224: ", digest) + '; + } +--- request +GET /t +--- response_body +hmac_sha224: a38aa774b3d7f49e6f3a7006cdfac9f8aeab4427dd3fb47123be5874 +hmac_sha224: a41ef5660a729abe83238c6921861ddd157a2314df03d98252a9ecac +--- no_error_log +[error] + + +=== TEST 3: hello HMAC-SHA-256 +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua ' + local resty_hmac_sha256 = require "resty.hmac" + local hmac_sha256 = resty_hmac_sha256:new() + + local digest = hmac_sha256:digest("sha256","secret-key","Hello world") + ngx.say("hmac_sha256: ", digest) + + --test with an empty string + digest = hmac_sha256:digest("sha256","secret-key","") + ngx.say("hmac_sha256: ", digest) + '; + } +--- request +GET /t +--- response_body +hmac_sha256: 902dd133c19fef9216f144694b1b9cc9e06c7be3252019f7e12909ff07122220 +hmac_sha256: 345fba21f06a4f75ed673fb93dc16cd47d8dc7a69f52e84e3016fcf69835fdb8 +--- no_error_log +[error] + + +=== TEST 4: hello HMAC-SHA-384 +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua ' + local resty_hmac_sha384 = require "resty.hmac" + local hmac_sha384 = resty_hmac_sha384:new() + + local digest = hmac_sha384:digest("sha384","secret-key","Hello world") + ngx.say("hmac_sha384: ", digest) + + --test with an empty string + digest = hmac_sha384:digest("sha384","secret-key","") + ngx.say("hmac_sha384: ", digest) + '; + } +--- request +GET /t +--- response_body +hmac_sha384: 7baab15202e53288e026c9b318c08527692ad27ef8903bcb405bcd097e4ed7611cb542d760234ef04536da3a16e906bf +hmac_sha384: 3e4c852a0ce874f8bef33bb899b7fa938f5ce8418bafc530e7c2df532b7be4ad49f5b57ca49c50d9080c16b74ef124dc +--- no_error_log +[error] + + + +=== TEST 5: hello HMAC-SHA-512 +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua ' + local resty_hmac_sha512 = require "resty.hmac" + local hmac_sha512 = resty_hmac_sha512:new() + + local digest = hmac_sha512:digest("sha512","secret-key","Hello world") + ngx.say("hmac_sha512: ", digest) + + --test with an empty string + digest = hmac_sha512:digest("sha512","secret-key","") + ngx.say("hmac_sha512: ", digest) + '; + } +--- request +GET /t +--- response_body +hmac_sha512: cbec52174d245ed147e57860e9317605895e1b4af43b080701bccb083f2194cd3ada40623420c2ab1b4c77ce6e6d26b149128867035eba259d495524fa230dee +hmac_sha512: 1560eeb87551d027de6007027af3faab5f644f8ef96519c4b519531a6620c755b61d210f179754f991607151b4b9a9db3377132b9e8587f803cdf8763499bcdc +--- no_error_log +[error] + +=== TEST 6: test with an invalid HMAC algorithm +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua ' + local resty_hmac_sha256 = require "resty.hmac" + local hmac_sha256 = resty_hmac_sha256:new() + + local digest = hmac_sha256:digest("INVALID_SHA","secret-key","Hello world") + ngx.say("hmac_sha256: ", digest) + + --test with an empty string + digest = hmac_sha256:digest("INVALID_SHA","secret-key","") + ngx.say("hmac_sha256: ", digest) + '; + } +--- request +GET /t +--- response_body_like +.*500 Internal Server Error.* +--- error_code: 500 +--- grep_error_log eval: qr/attempt to use unknown algorithm: 'INVALID_SHA'.*?/ +--- grep_error_log_out +attempt to use unknown algorithm: 'INVALID_SHA' + +=== TEST 7: test with null secret or message +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua ' + local resty_hmac_sha256 = require "resty.hmac" + local hmac_sha256 = resty_hmac_sha256:new() + + local digest = hmac_sha256:digest("sha256",nil,"Hello world") + ngx.say("hmac_sha256: ", digest) + + digest = hmac_sha256:digest("sha256",nil,"") + ngx.say("hmac_sha256: ", digest) + '; + } +--- request +GET /t +--- response_body_like +.*500 Internal Server Error.* +--- error_code: 500 +--- grep_error_log eval: qr/attempt to digest with a null key or message.*?/ +--- grep_error_log_out +attempt to digest with a null key or message + + +