前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >openresty LUA的ase加密的坑

openresty LUA的ase加密的坑

作者头像
richard.xia_志培
发布2022-06-14 14:29:02
3.3K0
发布2022-06-14 14:29:02
举报
文章被收录于专栏:PT运维技术PT运维技术

背景/现象:

在使用openresty(1.13.6.2)中使用lua对业务方的token进行加解密的时候,发现AES加密出来的结果和java/python有一定的出入,openresty lua 通过AES加密得到的结果比java/python的多出一串字符串。反之,正常加密串无法解密。

python版的AES加密:

代码语言:javascript
复制
#cat aes.py
from Crypto.Cipher import AES
import base64

import binascii
# -*- coding: UTF-8 -*-

class prpcrypt():
    def __init__(self, key):
        self.key = "aaaaaaaaaaaaaaaa"
        self.mode = AES.MODE_CBC


    def encrypt(self, text):
        cryptor = AES.new(self.key, self.mode, self.key)
        length = 16
        count = len(text)
        add = length - (count % length)
        text = text + ('\0' * add)
        print(text)
        self.ciphertext = cryptor.encrypt(text)
        return binascii.b2a_hex(self.ciphertext)
        #return base64.b64encode(self.ciphertext)

crpt=prpcrypt("aaaaaaaaaaaaaaaa")
str1=str(crpt.encrypt("7614463 1574189821 175.168.224.225"))
print str(str1)

#python aes.py
7614463 1574189821 175.168.224.225
0056cc0b278e6123afe0784a940b45525fd95c10585809f5197ac3aed2f9ec60635e59f5008f3c6ecbca932811a85cc0

openresty lua aes加密:

代码语言:javascript
复制
        location /t {
        rewrite_by_lua_block  {
        
            local aes = require "resty.aes"
            local str = require "resty.string"
            local key = "aaaaaaaaaaaaaaaa"
            local text  = "7614463 1574189821 175.168.224.225"
            local  length = 16
            local count = string.len(text)
            local add = length - (count % length)
            local tail_str='\0'
            for i=1,add-1 do 
                 tail_str=tail_str .. '\0'
            end
            text = text .. tail_str
            ngx.say(text)
            local cript = aes:new(key,nil, aes.cipher(128,"cbc"), {iv=key})
            local encrypted = cript:encrypt(text)
            ngx.say(str.to_hex(encrypted))
        }
    }

[root@dev_bao_shops vhosts]# curl http://localhost/t
7614463 1574189821 175.168.224.225
0056cc0b278e6123afe0784a940b45525fd95c10585809f5197ac3aed2f9ec60635e59f5008f3c6ecbca932811a85cc0a0fe3cea8ef9cde779e3a927f7c2b121

前后对比,lua加密后 多出了a0fe3cea8ef9cde779e3a927f7c2b121。

原因分析:

单独通过系统自带的lua库,编写测试程序,发现没有问题,然后翻看了一下openresty lua库 中aes的实现,发现aes底层仍然使用的是openssl底层库【通过 LuaJIT的FFI库 ,FFI是LUA调用外部C函数的库】。前后通过python,java测试程序,都无此问题。问题出现在CBC的pading模式上, AES -CBC-128加密算法下,数据必须以16字节为固定长度进行对齐(参见:https://github.com/openresty/lua-resty-string/issues/59)。Python和JAVA版都是使用ZeroPadding, 而openresty Lua ase默认使用的是 PKCS7Padding模式,

导致以下以下问题: 即使程序中通过对齐方式补齐了\0,进行填充,但PKCS7Padding仍然要填充一个长度为块大小的数据[16字节), 最终导致解密后多出了一串。

解决方法:

方法1: 直接在openresty content_by_lua_block中调用外部c函数,显示使用ZeroPadding模式

代码语言:javascript
复制
location = /t {
content_by_lua_block {
local b64 = require("ngx.base64")
local aes = require "resty.aes"
local function set_padding(aes, pad)
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);
]]
local encrypt_ctx, decrypt_ctx = aes._encrypt_ctx, aes._decrypt_ctx
if encrypt_ctx == nil or decrypt_ctx == nil then
return nil, "the aes instance doesn't existed"
end

方法1, 每次请求都会通过luajit调用外部openssl 的库函数,在高并发情况下,容易崩溃,所以方法1适合访问量很小的场景

方法2: 直接修改openresty 自带的aes.lua库文件,禁止使用PKCS7Padding

代码语言:javascript
复制
ffi.cdef[[
typedef struct engine_st ENGINE;

typedef struct evp_cipher_st EVP_CIPHER;
typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX;

typedef struct env_md_ctx_st EVP_MD_CTX;
typedef struct env_md_st EVP_MD;

const EVP_MD *EVP_md5(void);
const EVP_MD *EVP_sha(void);
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);

const EVP_CIPHER *EVP_aes_128_ecb(void);
const EVP_CIPHER *EVP_aes_128_cbc(void);
const EVP_CIPHER *EVP_aes_128_cfb1(void);
const EVP_CIPHER *EVP_aes_128_cfb8(void);
const EVP_CIPHER *EVP_aes_128_cfb128(void);
const EVP_CIPHER *EVP_aes_128_ofb(void);
const EVP_CIPHER *EVP_aes_128_ctr(void);
const EVP_CIPHER *EVP_aes_192_ecb(void);
const EVP_CIPHER *EVP_aes_192_cbc(void);
const EVP_CIPHER *EVP_aes_192_cfb1(void);
const EVP_CIPHER *EVP_aes_192_cfb8(void);
const EVP_CIPHER *EVP_aes_192_cfb128(void);
const EVP_CIPHER *EVP_aes_192_ofb(void);
const EVP_CIPHER *EVP_aes_192_ctr(void);
const EVP_CIPHER *EVP_aes_256_ecb(void);
const EVP_CIPHER *EVP_aes_256_cbc(void);
const EVP_CIPHER *EVP_aes_256_cfb1(void);
const EVP_CIPHER *EVP_aes_256_cfb8(void);
const EVP_CIPHER *EVP_aes_256_cfb128(void);
const EVP_CIPHER *EVP_aes_256_ofb(void);

EVP_CIPHER_CTX *EVP_CIPHER_CTX_new();
void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *a);

int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher,
        ENGINE *impl, unsigned char *key, const unsigned char *iv);

int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *ctx, int pad);

int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
        const unsigned char *in, int inl);

int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher,
        ENGINE *impl, unsigned char *key, const unsigned char *iv);

int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
        const unsigned char *in, int inl);

int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);

int EVP_BytesToKey(const EVP_CIPHER *type,const EVP_MD *md,
        const unsigned char *salt, const unsigned char *data, int datal,
        int count, unsigned char *key,unsigned char *iv);
]]


function _M.encrypt(self, s)
    local s_len = #s
    local max_len = s_len + 16
    local buf = ffi_new("unsigned char[?]", max_len)
    local out_len = ffi_new("int[1]")
    local tmp_len = ffi_new("int[1]")
    local ctx = self._encrypt_ctx
    C.EVP_CIPHER_CTX_set_padding(ctx, 0)

    if C.EVP_EncryptInit_ex(ctx, nil, nil, nil, nil) == 0 then
        return nil
    end

    if C.EVP_EncryptUpdate(ctx, buf, out_len, s, s_len) == 0 then
        return nil
    end

    if C.EVP_EncryptFinal_ex(ctx, buf + out_len[0], tmp_len) == 0 then
        return nil
    end

    return ffi_str(buf, out_len[0] + tmp_len[0])
end


function _M.decrypt(self, s)
    local s_len = #s
    local buf = ffi_new("unsigned char[?]", s_len)
    local out_len = ffi_new("int[1]")
    local tmp_len = ffi_new("int[1]")
    local ctx = self._decrypt_ctx
    C.EVP_CIPHER_CTX_set_padding(ctx, 0)

    if C.EVP_DecryptInit_ex(ctx, nil, nil, nil, nil) == 0 then
      return nil
    end

    if C.EVP_DecryptUpdate(ctx, buf, out_len, s, s_len) == 0 then
      return nil
    end

    if C.EVP_DecryptFinal_ex(ctx, buf + out_len[0], tmp_len) == 0 then
        return nil
    end

    return ffi_str(buf, out_len[0] + tmp_len[0])
end

方法2的好处是效率高,缺点是硬核修改了openresty lua底层库, 对其他pading类型的AES非CBC加密算法计算有一定的风险。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PT运维技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档