diff --git a/README.md b/README.md index daf08c7..613ad25 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,44 @@ # WXBizMsgCrypt -模仿[微信公众平台加密解密技术方案](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318482&lang=zh_CN) -写了一个openresty 版的加密解密。 +[the WeChat Message Cryptography](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318482&lang=zh_CN) in the openresty lua verison. -注意点: -aes.lua不是openresty 主版本的,而是用了这个[no padding](https://github.com/openresty/lua-resty-string/pull/35) +## aes_pad.lua + +extend the [aes.lua](https://github.com/openresty/lua-resty-string) with a `set_padding` method, [discussing here](https://github.com/openresty/lua-resty-string/pull/35) + +## crypt.lua + +`encrypt(text, timestamp, nonce)` + +`decrypt(text_encrypted)` + +`get_sha1({token, timestamp, nonce, text_encrypted})` + +## Synopsis + +```lua +# nginx.conf: + +server { + location = /test { + content_by_lua_block { + local token = "pamtest" + local aesKey = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG" + local timestamp = "1409304348" + local nonce = "xxxxxx" + local appId = "wxb11529c136998cb6" + local sample = "1407743423" + + local crypt = require "resty.crypt" + local wxmc = crypt:new(token, aesKey, appId) + local encrypted = wxmc:encrypt(sample, timestamp, nonce) + ngx.say("the sample encrypted: ", encrypted) + + local gsub = ngx.re.gsub + sample = gsub(encrypted, [=[.*(?:)?.*]=], "$1") + ngx.say("the sample decrypted: ", wxmc:decrypt(sample)) + } + } +} +``` diff --git a/lib/resty/aes_pad.lua b/lib/resty/aes_pad.lua new file mode 100644 index 0000000..17bb107 --- /dev/null +++ b/lib/resty/aes_pad.lua @@ -0,0 +1,30 @@ +-- patched +-- @see https://github.com/openresty/lua-resty-string/pull/35 + +local aes = require "resty.aes" +local ffi = require "ffi" +local C = ffi.C +ffi.cdef[[ +typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX; + +int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *ctx, int pad); +]] + +-- @link https://github.com/openssl/openssl/blob/master/crypto/evp/evp_enc.c#L569-L576 +function aes.set_padding(self, pad) + local encrypt_ctx, decrypt_ctx = self._encrypt_ctx, self._decrypt_ctx + + if encrypt_ctx == nil or decrypt_ctx == nil then + return nil, "the aes instance doesn't existed" + end + + -- @link https://github.com/openssl/openssl/blob/master/crypto/evp/evp_enc.c#L402-L410 + C.EVP_CIPHER_CTX_set_padding(encrypt_ctx, pad) + + -- @link https://github.com/openssl/openssl/blob/master/crypto/evp/evp_enc.c#L515-L523 + C.EVP_CIPHER_CTX_set_padding(decrypt_ctx, pad) + + return 1 +end + +return aes diff --git a/lib/resty/crypt.lua b/lib/resty/crypt.lua index 6b7d602..85a313c 100644 --- a/lib/resty/crypt.lua +++ b/lib/resty/crypt.lua @@ -1,67 +1,67 @@ local random = require "resty.random" local str = require "resty.string" -local aes = require "resty.aes" +local aes = require "resty.aes_pad" local bit = require "bit" local resty_sha1 = require "resty.sha1" +local setmetatable, assert = setmetatable, assert +local str_char, str_sub, str_byte, str_format = string.char, string.sub, string.byte, string.format +local decode_base64, encode_base64 = ngx.decode_base64, ngx.encode_base64 -local _M = {} +local _M = { _VERSION = '0.2' } local mt = { __index = _M } local function pack_text_len(text_len) - return string.char( - bit.band(bit.rshift(text_len, 24), 0xff), - bit.band(bit.rshift(text_len, 16), 0xff), - bit.band(bit.rshift(text_len, 8), 0xff), - bit.band(text_len, 0xff) + local band, rshift, mask = bit.band, bit.rshift, 0xff + return str_char( + band(rshift(text_len, 24), mask), + band(rshift(text_len, 16), mask), + band(rshift(text_len, 8), mask), + band(text_len, mask) ) end local function unpack_text_len(text_len) - local a, b, c, d = string.byte(text_len, 1, 4) - return bit.bor(bit.lshift(a, 24), bit.lshift(b, 16), bit.lshift(c, 8), d), 1 + 4 + local bor, lshift = bit.bor, bit.lshift + local a, b, c, d = str_byte(text_len, 1, 4) + return bor(lshift(a, 24), lshift(b, 16), lshift(c, 8), d), 1 + 4 end local function pkcs7_encode(text) - local amount_to_pad = 32 - (#text % 32) + local PAD_BLOCK_LENGTH = 32 + local amount_to_pad = PAD_BLOCK_LENGTH - (#text % PAD_BLOCK_LENGTH) if amount_to_pad == 0 then - amount_to_pad = 32 + amount_to_pad = PAD_BLOCK_LENGTH end + local str_pad = str_char(amount_to_pad):rep(amount_to_pad) - local padding = "" - - local pad = string.char(amount_to_pad) - - for i = 1, amount_to_pad do - padding = padding .. pad - end - - return text .. padding + return text .. str_pad end local function pkcs7_decode(text) - local pad = string.byte(text, #text - 1) + local pad = str_byte(text, #text - 1) if (pad < 1 or pad > 32) then pad = 0 end - return string.sub(text, 1, #text - pad); + return str_sub(text, 1, #text - pad); end function _M.new (self, token, aes_key, app_id) - local aes_key = ngx.decode_base64(aes_key .. "=") - return setmetatable({token = token, aes_key = aes_key, app_id = app_id}, mt) + local aes_key = decode_base64(aes_key .. "=") + local cipher = aes.cipher(256, "cbc") + local iv = str_sub(aes_key, 0, 16) + + return setmetatable({token = token, aes_key = aes_key, app_id = app_id, cipher = cipher, iv = iv}, mt) end function _M.get_sha1 (self, sha1_table) - table.sort(sha1_table) + local tb_sort, tb_join = table.sort, table.concat + tb_sort(sha1_table) - local to_sha1 = "" - for k,v in pairs(sha1_table) do - to_sha1 = to_sha1 .. v - end + local to_sha1 = tb_join(sha1_table) local sha1 = resty_sha1:new() sha1:update(to_sha1) @@ -70,42 +70,41 @@ function _M.get_sha1 (self, sha1_table) end function _M.decrypt (self, encrypted) - local ciphertext_dec = ngx.decode_base64(encrypted) + local ciphertext_dec = decode_base64(encrypted) if ciphertext_dec == nil then return nil end - local iv = string.sub(self.aes_key, 0, 16) local aes_crypt = assert( - aes:new(self.aes_key, nil, aes.cipher(256,"cbc"), {iv=iv}, nil, 0) + aes:new(self.aes_key, nil, self.cipher, {iv = self.iv}) ) + aes_crypt:set_padding(0) local text = aes_crypt:decrypt(ciphertext_dec) - text = pkcs7_decode(string.sub(text, 17, #text)) + text = pkcs7_decode(str_sub(text, 17, #text)) - local xml_len = unpack_text_len(string.sub(text, 1, 4)) + local xml_len = unpack_text_len(str_sub(text, 1, 4)) - return string.sub(text, 4 + 1, xml_len + 4) + return str_sub(text, 4 + 1, xml_len + 4) end - function _M.encrypt (self, text, timestamp, nonce) - local random = str.to_hex(random.bytes(8, true)) - local iv = string.sub(self.aes_key, 0, 16) + local prefix = str.to_hex(random.bytes(8, true)) - text = random .. pack_text_len(#text) .. text .. self.app_id + text = prefix .. pack_text_len(#text) .. text .. self.app_id text = pkcs7_encode(text) local aes_crypt = assert( - aes:new(self.aes_key, nil, aes.cipher(256,"cbc"), {iv=iv}, nil, 0) + aes:new(self.aes_key, nil, self.cipher, {iv = self.iv}) ) + aes_crypt:set_padding(0) - local encrypted = ngx.encode_base64(aes_crypt:encrypt(text)) + local encrypted = encode_base64(aes_crypt:encrypt(text)) local signature = self:get_sha1({self.token, timestamp, nonce, encrypted}) local xml_content = "%s"; - return string.format(xml_content, encrypted, signature, timestamp, nonce) + return str_format(xml_content, encrypted, signature, timestamp, nonce) end return _M