openssl命令aes加密和解密

日期:2014-11-12 10:41:25

最后更新日期:2017-07-06 10:00:10

【技术】

man openssl查看openssl的功能:

[code lang="cpp"]

o Creation and management of private keys, public keys and parameters

o Public key cryptographic operations

o Creation of X.509 certificates, CSRs and CRLs

o Calculation of Message Digests

o Encryption and Decryption with Ciphers

o SSL/TLS Client and Server Tests

o Handling of S/MIME signed or encrypted mail

o Time Stamp requests, generation and verification

[/code]

openssl工具功能如上诉,有些功能也是在实际工作中慢慢积累。最后两项功能,笔者就没有使用过。

1.openssl aes加密和解密

这里着重与openssl c api和python 的api能够相互转化。说白了都是同一套库。原始的aes加密和解密,key的长度与block大小一致。openssl库默认的aes加密方式如下:

[code lang="cpp"]

AES_KEY aeskey;

unsigned char userKey[16];

AES_set_encrypt_key((uint8 *)userKey, bits=128, &aeskey);

AES_encrypt(unsigned char * in, unsigned char *out, &aeskey);

[/code]

这种块的分组加密模式为ecb模式,常见的分组模式为电子密码本模式(ECB)、加密分组链接模式(CBC)、加密反馈模式(CFB)和输出反馈模式(OFB)。实际上openssl封装了更高层的evp_*函数调用。給一个函数名称EVP_EncryptInit,便于查看man手册。

前面的userKey可以这样赋值:

[code lang="cpp"]

unsigned char userKey[16]={0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf};

[/code]

等效的openssl命令为:

[code lang="cpp"]

$ echo -ne 'hello world\0\0\0\0\0' | openssl enc -aes-128-ecb -nosalt -nopad -K '000102030405060708090A0B0C0D0E0F' | xxd

0000000: 1281 34b4 5a7c 03ad 6dc6 5a47 97a0 77f2 ..4.Z|..m.ZG..w.

[/code]

-K表示hex方式输入key,使用nosalt方式,否则会有前缀。另外若使用-k参数,表示的意思,即要将-k指定的passphrase(密码),按照-md指定的方式计算报文摘要作为key。

[code lang="cpp"]

$ echo -ne 'hello world\0\0\0\0\0' | openssl enc -aes-128-ecb -nosalt -nopad -k '0123456789abcdef' -md md5 | xxd

0000000: 54a7 f164 6051 c41e 2ca0 f777 d50f 68f5 T..d`Q..,..w..h.

[/code]

openssl命令-p参数可以打印实际使用的key,打印该key放到cpp文件里面,加密后值一样。

2.AES填充方式(padding)

AES是分块计算,当数据内容不足,16字节(128 bit aes),24字节(192 bit aes),32字节(256 bit aes),不足部分就需要填充。wiki上面列举填充方式有如下几种:

1.ANSI X.923

不足部分填充0,最后一字节为填充字节数。如下面8字节的块,需要填充4字节时:

... | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 04 |

2.ISO 10126

不足部分填充随机数字,最后一字节为填充字节数。如下面8字节的块,需要填充4字节时:

... | DD DD DD DD DD DD DD DD | DD DD DD DD BC DA EF 04 |

3.PKCS7与PKCS5

不足部分填充为需要填充字节数。若数据大小是分块大小N的倍数时,则增加一个全为N的分块。如下面8字节的块,需要填充4字节时:

... | DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |

4.ISO/IEC 7816-4

不足的部分,首先填充一个0x80,剩余部分全为0。如下面8字节的块,需要填充4字节时:

... | DD DD DD DD DD DD DD DD | DD DD DD DD 80 00 00 00 |

要求数据内容本身不包含0x80

4.Zero padding

不足部分全部填充0。如下面8字节的块,需要填充4字节时:

... | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 00 |

这种方法不能区分数据内容本身末尾包含0的情况,因而也不是标准的填充方式。

openssl aes加密时,若指定nopad,则要求数据本身是分块大小的整数倍。否则根据openssl手册描述,使用标准填充方式,验证这种方式即为PKCS7。

[code lang="cpp"]
[root@localhost ~]# echo -ne 'hello\xb\xb\xb\xb\xb\xb\xb\xb\xb\xb\xb' | openssl enc -aes-128-ecb -nosalt -k "012" -md md5 -nopad | xxd
0000000: c9c3 ee5a b1fa ed01 d91d 30cd 56e5 c2e7 ...Z......0.V...
[root@localhost ~]# echo -ne 'hello' | openssl enc -aes-128-ecb -nosalt -k "012" -md md5 | xxd
0000000: c9c3 ee5a b1fa ed01 d91d 30cd 56e5 c2e7 ...Z......0.V...
[/code]

3.nginx lua-resty-string-0.09 加密和解密

题外话,对于nginx的lua-resty-string-0.09模块,没有指定padding的参数,使用的也是PKCS7填充方式。nginx aes模块是把password的md5作为Key。与openssl相互转化示例:

[code lang="cpp"]
location /t1 {
content_by_lua '
local aes = require "resty.aes"
local str = require "resty.string"
local aes_default = aes:new("FUCKCODE",nil,
aes.cipher(128,"ecb"),aes.hash.md5,1)
local encrypted = aes_default:encrypt("hello world")
--ngx.say("AES-128 ECB md5: ", str.to_hex(encrypted))
local decrypted = aes_default:decrypt(encrypted)
ngx.say(encrypted)
--ngx.say(decrypted == "hello world")
';
}
[/code]

解密:

[code lang="cpp"]
[root@localhost ~]# curl "http://10.10.31.230:81/t1 >81.bin
[root@localhost ~]#dd if=81.bin of=81_16.bin bs=16 count=1
[root@localhost ~]# openssl enc -d -aes-128-ecb -nopad -nosalt -k "FUCKCODE" -iv "" -in 81_16.bin -out des_out_t1.bin
[root@localhost ~]# xxd des_out_t1.bin
0000000: 6865 6c6c 6f20 776f 726c 6405 0505 0505 hello world.....
[/code]

4.python aes加密和解密

实际工作中python的接口也比较常用,如下:

[code lang="cpp"]
from Crypto.Cipher import AES
import struct,binascii
aeskey = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
aes_tool = AES.new(aeskey , AES.MODE_ECB)
resp_data="hello world\0\0\0\0\0"
plain_txt = aes_tool.encrypt(resp_data)
print(binascii.b2a_hex(plain_txt))
[/code]

openssl功能后续补充,其它加密和解密方式类似。

参考:

1.aes填充方式