关于nginx:使用OpenResty的resty.aes模块,Java Cipher.getInstance(” AES / CBC / NoPadding”)的解密结果失败

Decrypt result of Java Cipher.getInstance(“AES/CBC/NoPadding”) failed with OpenResty's resty.aes module

我正在开发nginx(openresty)Lua模块,其中一项要求是解密由旧版Java程序生成的加密字符串。但是我的Lua代码无法解密它,因此我在这里寻求帮助。

可以的Java加密和解密代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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代码的输出:

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

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

1
2
3
            location /aescbc {
                    content_by_lua_file conf/aescbc.lua;
            }

conf / aescbc.lua的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- aescbc.lua

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

-- 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) .."]")
return

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

1
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一定有问题,但是如何解决?


在网上搜索,阅读手册页并进行了几天的实验后,问题得以解决。所以我自己回答:

resty.aes模块使用OpenSSL的默认PKCS7填充算法,并且在当前版本的resty.aes中没有设置NoPadding选项的方法。所以我对此做了一个补丁:(添加填充参数并在aes:new中调用EVP_CIPHER_CTX_set_padding())

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ 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测试代码做了一些小的修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ 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错误日志:

1
2
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"