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