使用 OpenResty 的 resty.aes 模块解密 Java Cipher.getInstance("AES/CBC/NoPadding") 失败

我正在开发一个基于 nginx(OpenResty)的 Lua 模块,其中一个要求是对由一款旧版 Java 程序生成的加密字符串进行解密。但我的 Lua 代码无法解密它,我在这里寻求帮助。

合适的 Java 加密和解密代码:

public class AesCbc {
    private static String PLAIN = "usr/passwd@bizdb:127.0.0.1:5432";

    public static void main(String[] args) throws Exception {
        Cipher aesCipher = Cipher.getInstance("AES/CBC/NoPadding");
        SecretKeySpec keySpec = new SecretKeySpec("1234567890ABCDEF".getBytes(), "AES");
        IvParameterSpec iv = new IvParameterSpec("fedcba0987654321".getBytes());

        aesCipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
        byte[] rawBytes = PLAIN.getBytes();
        byte[] aligned;
        int mod = rawBytes.length % 16; // prevent javax.crypto.IllegalBlockSizeException
        if (mod == 0) {
            aligned = new byte[rawBytes.length];
        } else {
            aligned = new byte[rawBytes.length + 16 - mod];
        }
        System.arraycopy(rawBytes, 0, aligned, 0, rawBytes.length);
        byte[] cipherBytes = aesCipher.doFinal(aligned);
        String base64Result = Base64.getEncoder().encodeToString(cipherBytes);
        System.out.println("cipher:[" + base64Result + "], rawBytes.length=" + rawBytes.length + ", mod=" + mod);

        aesCipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
        cipherBytes = Base64.getDecoder().decode(base64Result);
        aligned = aesCipher.doFinal(cipherBytes);
        int posNil;
        for (posNil = 0; posNil < aligned.length; posNil++) {
            if (aligned[posNil] == 0x00)
                break;
        }
        rawBytes = new byte[posNil];
        System.arraycopy(aligned, 0, rawBytes, 0, posNil);
        String plain = new String(rawBytes);
        System.out.println("plain:[" + plain + "], posNil=" + posNil + ", aligned.length=" + aligned.length);
    }
}

Java 代码的输出结果:

cipher:[l1buytGEL4RKa/RezInQ3dJxvMtL6nyE2wTi7VyoS4w=], rawBytes.length=31, mod=15
plain:[usr/passwd@bizdb:127.0.0.1:5432], posNil=31, aligned.length=32

我在 nginx.conf->http->server 段中声明了我的 Lua 测试文件:

            location /aescbc {
                    content_by_lua_file conf/aescbc.lua;
            }

conf/aescbc.lua 的内容:

-- aescbc.lua

local aes = require "resty.aes"
local str = require "resty.string"
local aes128Cbc = aes:new("1234567890ABCDEF", nil, aes.cipher(128, "cbc"), {iv="fedcba0987654321"})

-- 在我的测试中使用了 AesCbc.java 的结果
local BASE64CIPHER = "l1buytGEL4RKa/RezInQ3dJxvMtL6nyE2wTi7VyoS4w="

local cipherBytes = ngx.decode_base64(BASE64CIPHER)
if not cipherBytes then
    ngx.log(ngx.WARN, "decode base64 [" .. BASE64CIPHER .. "] failed")
    return
end

local aligned = aes128Cbc:decrypt(cipherBytes)
if not aligned then
    ngx.log(ngx.WARN, "decrypt cipherBytes [" .. str.to_hex(cipherBytes) .. "] failed")
    return
end

ngx.log(ngx.NOTICE, "aligned [" .. str.to_hex(aligned) .. "]")
return

当使用 "curl http://127.0.0.1:8080/aescbc" 进行测试时,nginx 的错误日志如下:

2017/08/03 11:28:26 [warn] 13799#0: *5 [lua] aescbc.lua:18: decrypt cipherBytes [9756eecad1842f844a6bf45ecc89d0ddd271bccb4bea7c84db04e2ed5ca84b8c] failed, client: 127.0.0.1, server: , request: "GET /aescbc HTTP/1.1", host: "127.0.0.1:8080"

我认为我的 resty.aes 的使用方式一定有问题,但是我该如何修复?

点赞
用户795969
用户795969

经过在网上搜索、阅读 man 页面并进行一些实验几天后,问题终于解决了。因此,我自己回答了这个问题:

resty.aes 模块使用 OpenSSL 的默认 PKCS7 填充算法,在当前版本的 resty.aes 中没有设置 NoPadding 选项的方法。因此,我对其进行了修补:(添加了一个 padding 参数并在 aes:new 中调用了 _EVP_CIPHER_CTX_set_padding()_)

$ diff ./lualib/resty/aes.lua.orig ./lualib/resty/aes.lua
79a80
> int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *x, int padding);
128c129
< function _M.new(self, key, salt, _cipher, _hash, hash_rounds)
---
> function _M.new(self, key, salt, _cipher, _hash, hash_rounds, padding)
177a179,184
>     end
>
>     if padding then
>         -- 0:NoPadding, 1:PKCS7(default), 2:ISO7816_4, 3:ANSI923, 4:ISO10126, 5:ZERO
>         C.EVP_CIPHER_CTX_set_padding(encrypt_ctx, padding);
>         C.EVP_CIPHER_CTX_set_padding(decrypt_ctx, padding);

并对 lua 测试代码进行了一些小修改:

$ cat ./nginx/conf/aescbc.lua
-- aescbc.lua

local aes = require "resty.aes"
local str = require "resty.string"
local aes128Cbc = aes:new("1234567890ABCDEF", nil, aes.cipher(128, "cbc"), {iv="fedcba0987654321"}, nil, 0)

-- result of AesCbc.java for my test
local BASE64CIPHER = "l1buytGEL4RKa/RezInQ3dJxvMtL6nyE2wTi7VyoS4w="

local cipherBytes = ngx.decode_base64(BASE64CIPHER)
if not cipherBytes then
    ngx.log(ngx.WARN, "decode base64 [" .. BASE64CIPHER .. "] failed")
    return
end

local aligned = aes128Cbc:decrypt(cipherBytes)
if not aligned then
    ngx.log(ngx.WARN, "decrypt cipherBytes [" .. str.to_hex(cipherBytes) .. "] failed")
    return
end

ngx.log(ngx.NOTICE, "aligned [" .. str.to_hex(aligned) .. "], len=" .. aligned:len())

local plain
local idx = aligned:find('\0')
if idx then
    plain = aligned:sub(1, idx - 1)
else
    plain = aligned
end

ngx.log(ngx.NOTICE, "plain [" .. plain .. "], len=" .. plain:len())

return

测试的 nginx 错误日志:

2017/08/07 15:17:55 [notice] 34632#0: *21 [lua] aescbc.lua:22: aligned [7573722f7061737377644062697a64623a3132372e302e302e313a3534333200], len=32, client: 127.0.0.1, server: , request: "GET /aescbc HTTP/1.1", host: "127.0.0.1:8080"
2017/08/07 15:17:55 [notice] 34632#0: *21 [lua] aescbc.lua:32: plain [usr/passwd@bizdb:127.0.0.1:5432], len=31, client: 127.0.0.1, server: , request: "GET /aescbc HTTP/1.1", host: "127.0.0.1:8080"
2017-08-07 07:26:48