文章目录
openssl3.2 - exp - aes-128-cbc
概述
想将工程中用到的字符串明文用openssl的对称加密算法加密一下, 防止逆向静态分析, 只作为字符串编码/解码的作用.
只是为了防止逆向静态分析, 起到一个编码/解码作用, 不介意别人知道密钥是啥.
看了一下openssl性能(AES性能大概是2000MB/秒, AES256比AES128稍低)
bash
D:\my_dev\my_local_git_prj\study\openSSL\exp\exp029_enc\aes-128-cbc\doc>openssl speed -mlock -seconds 1 -bytes 4096 aes-128-cbc
Doing aes-128-cbc ops for 1s on 4096 size blocks: 567655 aes-128-cbc ops in 1.02s
version: 3.2.0
built on: Sun Feb 25 02:20:27 2024 UTC
options: bn(64,64)
compiler: cl /Zi /Fdossl_static.pdb /Gs0 /GF /Gy /MDd /W3 /wd4090 /nologo /Od -DLIBZ=".\\\\my_zlib_1d3.dll" -DL_ENDIAN -DOPENSSL_PIC -D"OPENSSL_BUILDING_OPENSSL" -D"ZLIB" -D"ZLIB_SHARED" -D"OPENSSL_SYS_WIN32" -D"WIN32_LEAN_AND_MEAN" -D"UNICODE" -D"_UNICODE" -D"_CRT_SECURE_NO_DEPRECATE" -D"_WINSOCK_DEPRECATED_NO_WARNINGS" -D"DEBUG" -D"_DEBUG" -I"D:\\my_dev\\lib\\zlib_1d3"
CPUINFO: OPENSSL_ia32cap=0x7ffaf3ffffebffff:0x18c07fcef3bfa7eb
The 'numbers' are in 1000s of bytes per second processed. // 处理的"数字"以每秒1000字节为单位
type 4096 bytes
aes-128-cbc 2289343.88k => 2289343.88 * 1000 / 1024 / 1024 = 2183 MB/秒
D:\my_dev\my_local_git_prj\study\openSSL\exp\exp029_enc\aes-128-cbc\doc>openssl speed -mlock -seconds 1 -bytes 4096 aes-256-cbc
Doing aes-256-cbc ops for 1s on 4096 size blocks: 414597 aes-256-cbc ops in 1.02s
version: 3.2.0
built on: Sun Feb 25 02:20:27 2024 UTC
options: bn(64,64)
compiler: cl /Zi /Fdossl_static.pdb /Gs0 /GF /Gy /MDd /W3 /wd4090 /nologo /Od -DLIBZ=".\\\\my_zlib_1d3.dll" -DL_ENDIAN -DOPENSSL_PIC -D"OPENSSL_BUILDING_OPENSSL" -D"ZLIB" -D"ZLIB_SHARED" -D"OPENSSL_SYS_WIN32" -D"WIN32_LEAN_AND_MEAN" -D"UNICODE" -D"_UNICODE" -D"_CRT_SECURE_NO_DEPRECATE" -D"_WINSOCK_DEPRECATED_NO_WARNINGS" -D"DEBUG" -D"_DEBUG" -I"D:\\my_dev\\lib\\zlib_1d3"
CPUINFO: OPENSSL_ia32cap=0x7ffaf3ffffebffff:0x18c07fcef3bfa7eb
The 'numbers' are in 1000s of bytes per second processed.
type 4096 bytes
aes-256-cbc 1672063.32k
用openssl speed 列出的算法(openssl3.2 - exp - openssl speed test), 想选其他强度更低(速度更快)的其他对称加密算法, 好像没有啊. 非主流的算法, 用openssl speed都不能测试
那就拿AES128来弄吧.
笔记
openssl 命令行实现
bash
// enc
openssl enc -aes-128-cbc -e -in pt.txt -out pt.txt.enc -k my_pwd_for_enc -pbkdf2
// dec
openssl enc -aes-128-cbc -d -in pt.txt.enc -out pt.txt.enc.dec -k my_pwd_for_enc -pbkdf2
将命令行作为参数代进入, 跟一下openssl命令行工程, 就可以得到C实现(基于openssl API)
简单直白的实现
在查资料过程中, 发现官方测试代码中有一个直白的实现, 自己先弄一个工程试试. 然后再跟官方命令行的实现.
在openssl源码中, 看到了 D:\3rd_prj\crypt\openssl-3.2.0\test\afalgtest.c, 测试了aes-128-cbc, 实现特别简单.
简单直白的实现 - 测试效果
bash
before enc:
0000 - 00 01 02 03 04 05 06 07-08 09 0a 0b 0c 0d 0e 0f ................
0010 - 10 11 12 13 14 15 16 17-18 19 1a 1b 1c 1d 1e 1f ................
0020 - 20 21 22 23 24 25 26 27-28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./
0030 - 30 31 32 33 34 35 36 37-38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>?
0040 - 40 41 42 43 44 45 46 47-48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO
0050 - 50 51 52 53 54 55 56 57-58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_
0060 - 60 61 62 63 64 65 66 67-68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno
0070 - 70 71 72 73 74 75 76 77-78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~.
0080 - 80 81 82 83 84 85 86 87-88 89 8a 8b 8c 8d 8e 8f ................
0090 - 90 91 92 93 94 95 96 97-98 99 9a 9b 9c 9d 9e 9f ................
00a0 - a0 a1 a2 a3 a4 a5 a6 a7-a8 a9 aa ab ac ad ae af ................
00b0 - b0 b1 b2 b3 b4 b5 b6 b7-b8 b9 ba bb bc bd be bf ................
00c0 - c0 c1 c2 c3 c4 c5 c6 c7-c8 c9 ca cb cc cd ce cf ................
00d0 - d0 d1 d2 d3 d4 d5 d6 d7-d8 d9 da db dc dd de df ................
00e0 - e0 e1 e2 e3 e4 e5 e6 e7-e8 e9 ea eb ec ed ee ef ................
00f0 - f0 f1 f2 f3 f4 f5 f6 f7-f8 f9 fa fb fc .............
after enc:
0000 - c6 a1 3b 37 87 8f 5b 82-6f 4f 81 62 a1 c8 d8 79 ..;7..[.oO.b...y
0010 - 35 d9 dc db 82 9f ec 33-52 e7 bf 10 b8 4b e4 a5 5......3R....K..
0020 - 7b 30 46 46 05 f0 2a 09-4c 0a f7 ad 98 4f 61 fc {0FF..*.L....Oa.
0030 - d8 84 34 a5 59 1d bc 8f-d9 63 08 12 d3 a2 7b 87 ..4.Y....c....{.
0040 - 25 cc 26 ee 46 93 13 40-c1 a9 f1 0d 82 2e aa e3 %.&.F..@........
0050 - 69 f3 ff d0 fc b1 a1 5e-a0 dc d6 c1 75 07 9d 45 i......^....u..E
0060 - 37 d1 e1 58 ec 3d 2f 67-07 de 48 12 5f c4 a4 cb 7..X.=/g..H._...
0070 - 22 62 2b 9d d6 ea 3d 1e-ec c9 c1 5e 53 ea 33 8b "b+...=....^S.3.
0080 - 92 3a b9 cc 0f cc 8d 8d-da 45 57 a7 09 e5 3e e1 .:.......EW...>.
0090 - 24 0d fb bb 4e 93 18 85-7e 4b 84 2a 9d ae 3c 2d $...N...~K.*..<-
00a0 - 72 7a 39 39 db 64 24 ed-ba 80 e6 f8 98 d8 35 a7 rz99.d$.......5.
00b0 - 43 13 b6 bd aa ca e7 03-11 e9 97 63 c6 f2 36 7f C..........c..6.
00c0 - ab 39 b5 fd 1b 4c 34 1f-49 99 b3 06 ff 3e 77 2a .9...L4.I....>w*
00d0 - df 3f e7 db 00 43 48 99-6f bc b9 43 34 66 50 8e .?...CH.o..C4fP.
00e0 - f6 0a 2d d3 1a cc 9e d1-36 98 89 40 60 40 3a 48 ..-.....6..@`@:H
00f0 - c6 f1 c5 38 48 65 68 06-b5 df ac ed 84 74 d7 be ...8Heh......t..
after dec:
0000 - 00 01 02 03 04 05 06 07-08 09 0a 0b 0c 0d 0e 0f ................
0010 - 10 11 12 13 14 15 16 17-18 19 1a 1b 1c 1d 1e 1f ................
0020 - 20 21 22 23 24 25 26 27-28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./
0030 - 30 31 32 33 34 35 36 37-38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>?
0040 - 40 41 42 43 44 45 46 47-48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO
0050 - 50 51 52 53 54 55 56 57-58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_
0060 - 60 61 62 63 64 65 66 67-68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno
0070 - 70 71 72 73 74 75 76 77-78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~.
0080 - 80 81 82 83 84 85 86 87-88 89 8a 8b 8c 8d 8e 8f ................
0090 - 90 91 92 93 94 95 96 97-98 99 9a 9b 9c 9d 9e 9f ................
00a0 - a0 a1 a2 a3 a4 a5 a6 a7-a8 a9 aa ab ac ad ae af ................
00b0 - b0 b1 b2 b3 b4 b5 b6 b7-b8 b9 ba bb bc bd be bf ................
00c0 - c0 c1 c2 c3 c4 c5 c6 c7-c8 c9 ca cb cc cd ce cf ................
00d0 - d0 d1 d2 d3 d4 d5 d6 d7-d8 d9 da db dc dd de df ................
00e0 - e0 e1 e2 e3 e4 e5 e6 e7-e8 e9 ea eb ec ed ee ef ................
00f0 - f0 f1 f2 f3 f4 f5 f6 f7-f8 f9 fa fb fc .............
enc / dec all ok
free map, g_mem_hook_map.size() = 0
D:\my_dev\my_local_git_prj\study\openSSL\exp\exp029_enc\aes-128-cbc\prj-aes-128-cbc-simple\x64\Debug\prj-aes-128-cbc-simple.exe (进程 276104)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用"工具"->"选项"->"调试"->"调试停止时自动关闭控制台"。
按任意键关闭此窗口. . .
简单直白的实现 - 测试工程
c
/*!
* \file prj-aes-128-cbc-simple
*/
#include "my_openSSL_lib.h"
#include <openssl/crypto.h>
#include <openssl/bio.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "CMemHookRec.h"
#include <openssl/evp.h>
void my_openssl_app();
bool aes_128_cbc_EncDec(
bool isEnc,
IN const UCHAR* pszBufIn, IN int lenBufIn,
IN const UCHAR* key, IN int lenKey,
IN const UCHAR* iv, IN int lenIv,
OUT UCHAR*& pOutBuf, OUT int& lenOutBuf);
int main(int argc, char** argv)
{
setvbuf(stdout, NULL, _IONBF, 0); // 清掉stdout缓存, 防止调用printf时阻塞
mem_hook();
my_openssl_app();
mem_unhook();
return 0;
}
void my_openssl_app()
{
UCHAR ucBuf[0x100 - 3];
int lenBuf = sizeof(ucBuf);
int i = 0;
UCHAR* pEncBuf = NULL;
int lenEncBuf = 0;
UCHAR* pDecBuf = NULL;
int lenDecBuf = 0;
// 可以在EVP_CipherInit_ex()之后, 用EVP_CIPHER_CTX_get_key_length()/EVP_CIPHER_CTX_get_iv_length()看长度
UCHAR key[0x10]; // aes-128-cbc's key len = 0x10
UCHAR iv[0x10]; // aes-128-cbc's iv len = 0x10
for (i = 0; i < 0x10; i++)
{
key[i] = (UCHAR)i;
iv[i] = (UCHAR)i;
}
for (i = 0; i < lenBuf; i++)
{
ucBuf[i] = (UCHAR)i;
}
do {
printf("before enc:\n");
BIO_dump_fp(stdout, ucBuf, lenBuf);
// enc
//
// 如果输入不是0x10对齐, 加密后, 就会自动0x10对齐(多出几个字节)
// 所以要自己记录加密前的长度, 且加密时, 要将输出(加密后)buffer 16对齐(或直接比输入的长度多16字节)
// 且加密后, 要自己记录加密后的长度
if (!aes_128_cbc_EncDec(true, ucBuf, lenBuf, (UCHAR*)key, sizeof(key), (UCHAR*)iv, sizeof(iv), pEncBuf, lenEncBuf))
{
assert(false);
break;
}
// printf("enc before lenBuf = %d, enc after lenEncBuf = %d\n", lenBuf, lenEncBuf);
// enc before lenBuf = 253, enc after lenEncBuf = 256
printf("after enc:\n");
BIO_dump_fp(stdout, pEncBuf, lenEncBuf);
// dec
if (!aes_128_cbc_EncDec(false, pEncBuf, lenEncBuf, (UCHAR*)key, sizeof(key), (UCHAR*)iv, sizeof(iv), pDecBuf, lenDecBuf))
{
assert(false);
break;
}
// 解密后的数据长度和解密前一样了
printf("after dec:\n");
BIO_dump_fp(stdout, pDecBuf, lenDecBuf);
// 比较明文和解密后的明文是否相同
if ((lenDecBuf != lenBuf) || (0 != memcmp(ucBuf, pDecBuf, lenBuf)))
{
assert(false);
break;
}
printf("enc / dec all ok\n");
} while (false);
if (NULL != pEncBuf)
{
OPENSSL_free(pEncBuf);
pEncBuf = NULL;
}
if (NULL != pDecBuf)
{
OPENSSL_free(pDecBuf);
pDecBuf = NULL;
}
}
bool aes_128_cbc_EncDec(
bool isEnc,
IN const UCHAR* pszBufIn, IN int lenBufIn,
IN const UCHAR* key, IN int lenKey,
IN const UCHAR* iv, IN int lenIv,
OUT UCHAR*& pOutBuf, OUT int& lenOutBuf)
{
bool b_rc = false;
int i_rc = 0;
int i_tmp = 0;
const EVP_CIPHER* _evp_chipher = NULL;
EVP_CIPHER_CTX* _evp_cipher_ctx = NULL;
do {
if ((NULL == pszBufIn) || (lenBufIn <= 0) ||
(NULL == key) || (lenKey <= 0) ||
(NULL == iv) || (lenIv <= 0))
{
break;
}
_evp_chipher = EVP_aes_128_cbc();
_evp_cipher_ctx = EVP_CIPHER_CTX_new();
if (NULL == _evp_cipher_ctx)
{
break;
}
i_rc = EVP_CipherInit_ex(_evp_cipher_ctx, _evp_chipher, NULL, key, iv, (isEnc ? 1 : 0));
if (1 != i_rc)
{
break;
}
i_tmp = EVP_CIPHER_CTX_get_key_length(_evp_cipher_ctx);
if (i_tmp != lenKey)
{
break;
}
i_tmp = EVP_CIPHER_CTX_get_iv_length(_evp_cipher_ctx);
if (i_tmp != lenIv)
{
break;
}
lenOutBuf = 0;
pOutBuf = (UCHAR*)OPENSSL_malloc(lenBufIn + 0x10); // 输出必须比输入大0x10
if (NULL == pOutBuf)
{
break;
}
i_tmp = 0;
lenOutBuf = 0;
// 如果lenBufIn不是16整除, update 还剩下一个尾巴的数据(len < 0x10)
i_rc = EVP_CipherUpdate(_evp_cipher_ctx, pOutBuf, &i_tmp, pszBufIn, lenBufIn);
if (1 != i_rc)
{
break;
}
lenOutBuf += i_tmp;
i_tmp = 0;
i_rc = EVP_CipherFinal_ex(_evp_cipher_ctx, pOutBuf + lenOutBuf, &i_tmp);
if (1 != i_rc)
{
break;
}
lenOutBuf += i_tmp;
b_rc = true;
} while (false);
if (NULL != _evp_cipher_ctx)
{
EVP_CIPHER_CTX_free(_evp_cipher_ctx);
_evp_cipher_ctx = NULL;
}
return b_rc;
}
周全灵活的实现
单步openssl 命令行(openssl enc -aes-128-cbc ...)的实现, 如果口令给的强度不够, 会加沙.
用BIO链的方式加密, 不用自己分别调用具体的加密API, 用起来简单.
不过, 这个实现的前提是知道上一个简单直白的方法(最直接的调用openssl API), 知道对应算法的key/iv的数据长度
跟了一下官方实现, 没全用.
官方考虑了弱密码的情况, 会根据参数加沙和用pbkdf2(KDF是基于弱密码生成新密码), 然后再用BIO链条完成加密.
我自己用时, 知道每种算法的key, iv长度, 会自己用随机数填满, 就没必要基于KDF来从输入的弱口令生成新的实际加密的密码.
我只用了BIO链的方式加密.
BIO链干活的好处:
- 算法都是针对BIO链头操作, e.g. 加密时, 只需要向BIO链头写输入数据, 等全部写完(执行BIO_flush(BIO链头)), 只需要再从BIO链头读东西, 就是加密完成的内容. 用起来方便.
- 如果对输入数据的操作不是一个加密操作, 而是很多操作(e.g. 对称加密, 做hash...), 只需要加入新的BIO节点(通过BIO_push()来控制BIO链中的BIO对象执行顺序), 其他代码不用动, 对于维护友好.
如果只是干一个简单的活(e.g. 针对一块数据加密), 那就用第一种那样直白的方法.
等以后有机会找找官方实现中, 是否有BIO链条大于2个节点的实现, 也继续学习一下.
官方实现中, 是文件到文件的加/解密, 我改成了针对buffer的加/解密.
周全灵活的实现 - 测试效果
bash
before enc:
0000 - 00 01 02 03 04 05 06 07-08 09 0a 0b 0c 0d 0e 0f ................
0010 - 10 11 12 13 14 15 16 17-18 19 1a 1b 1c 1d 1e 1f ................
0020 - 20 21 22 23 24 25 26 27-28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./
0030 - 30 31 32 33 34 35 36 37-38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>?
0040 - 40 41 42 43 44 45 46 47-48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO
0050 - 50 51 52 53 54 55 56 57-58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_
0060 - 60 61 62 63 64 65 66 67-68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno
0070 - 70 71 72 73 74 75 76 77-78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~.
0080 - 80 81 82 83 84 85 86 87-88 89 8a 8b 8c 8d 8e 8f ................
0090 - 90 91 92 93 94 95 96 97-98 99 9a 9b 9c 9d 9e 9f ................
00a0 - a0 a1 a2 a3 a4 a5 a6 a7-a8 a9 aa ab ac ad ae af ................
00b0 - b0 b1 b2 b3 b4 b5 b6 b7-b8 b9 ba bb bc bd be bf ................
00c0 - c0 c1 c2 c3 c4 c5 c6 c7-c8 c9 ca cb cc cd ce cf ................
00d0 - d0 d1 d2 d3 d4 d5 d6 d7-d8 d9 da db dc dd de df ................
00e0 - e0 e1 e2 e3 e4 e5 e6 e7-e8 e9 ea eb ec ed ee ef ................
00f0 - f0 f1 f2 f3 f4 f5 f6 f7-f8 f9 fa fb fc .............
after enc:
0000 - c6 a1 3b 37 87 8f 5b 82-6f 4f 81 62 a1 c8 d8 79 ..;7..[.oO.b...y
0010 - 35 d9 dc db 82 9f ec 33-52 e7 bf 10 b8 4b e4 a5 5......3R....K..
0020 - 7b 30 46 46 05 f0 2a 09-4c 0a f7 ad 98 4f 61 fc {0FF..*.L....Oa.
0030 - d8 84 34 a5 59 1d bc 8f-d9 63 08 12 d3 a2 7b 87 ..4.Y....c....{.
0040 - 25 cc 26 ee 46 93 13 40-c1 a9 f1 0d 82 2e aa e3 %.&.F..@........
0050 - 69 f3 ff d0 fc b1 a1 5e-a0 dc d6 c1 75 07 9d 45 i......^....u..E
0060 - 37 d1 e1 58 ec 3d 2f 67-07 de 48 12 5f c4 a4 cb 7..X.=/g..H._...
0070 - 22 62 2b 9d d6 ea 3d 1e-ec c9 c1 5e 53 ea 33 8b "b+...=....^S.3.
0080 - 92 3a b9 cc 0f cc 8d 8d-da 45 57 a7 09 e5 3e e1 .:.......EW...>.
0090 - 24 0d fb bb 4e 93 18 85-7e 4b 84 2a 9d ae 3c 2d $...N...~K.*..<-
00a0 - 72 7a 39 39 db 64 24 ed-ba 80 e6 f8 98 d8 35 a7 rz99.d$.......5.
00b0 - 43 13 b6 bd aa ca e7 03-11 e9 97 63 c6 f2 36 7f C..........c..6.
00c0 - ab 39 b5 fd 1b 4c 34 1f-49 99 b3 06 ff 3e 77 2a .9...L4.I....>w*
00d0 - df 3f e7 db 00 43 48 99-6f bc b9 43 34 66 50 8e .?...CH.o..C4fP.
00e0 - f6 0a 2d d3 1a cc 9e d1-36 98 89 40 60 40 3a 48 ..-.....6..@`@:H
00f0 - c6 f1 c5 38 48 65 68 06-b5 df ac ed 84 74 d7 be ...8Heh......t..
after dec:
0000 - 00 01 02 03 04 05 06 07-08 09 0a 0b 0c 0d 0e 0f ................
0010 - 10 11 12 13 14 15 16 17-18 19 1a 1b 1c 1d 1e 1f ................
0020 - 20 21 22 23 24 25 26 27-28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./
0030 - 30 31 32 33 34 35 36 37-38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>?
0040 - 40 41 42 43 44 45 46 47-48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO
0050 - 50 51 52 53 54 55 56 57-58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_
0060 - 60 61 62 63 64 65 66 67-68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno
0070 - 70 71 72 73 74 75 76 77-78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~.
0080 - 80 81 82 83 84 85 86 87-88 89 8a 8b 8c 8d 8e 8f ................
0090 - 90 91 92 93 94 95 96 97-98 99 9a 9b 9c 9d 9e 9f ................
00a0 - a0 a1 a2 a3 a4 a5 a6 a7-a8 a9 aa ab ac ad ae af ................
00b0 - b0 b1 b2 b3 b4 b5 b6 b7-b8 b9 ba bb bc bd be bf ................
00c0 - c0 c1 c2 c3 c4 c5 c6 c7-c8 c9 ca cb cc cd ce cf ................
00d0 - d0 d1 d2 d3 d4 d5 d6 d7-d8 d9 da db dc dd de df ................
00e0 - e0 e1 e2 e3 e4 e5 e6 e7-e8 e9 ea eb ec ed ee ef ................
00f0 - f0 f1 f2 f3 f4 f5 f6 f7-f8 f9 fa fb fc .............
enc / dec all ok
free map, g_mem_hook_map.size() = 0
D:\my_dev\my_local_git_prj\study\openSSL\exp\exp029_enc\aes-128-cbc\prj-aes-128-cbc\x64\Debug\prj-aes-128-cbc.exe (进程 413000)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用"工具"->"选项"->"调试"->"调试停止时自动关闭控制台"。
按任意键关闭此窗口. . .
周全灵活的实现 - 测试工程
c
/*!
* \file prj-aes-128-cbc.cpp
*/
#include "my_openSSL_lib.h"
#include <openssl/crypto.h>
#include <openssl/bio.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "CMemHookRec.h"
#include <openssl/evp.h>
#include <openssl/bioerr.h>
#include <openssl/err.h>
void my_openssl_app();
bool aes_128_cbc_EncDec_v1(
bool isEnc,
IN const UCHAR* pszBufIn, IN int lenBufIn,
IN const UCHAR* key, IN int lenKey,
IN const UCHAR* iv, IN int lenIv,
OUT UCHAR*& pOutBuf, OUT int& lenOutBuf);
int main(int argc, char** argv)
{
setvbuf(stdout, NULL, _IONBF, 0); // 清掉stdout缓存, 防止调用printf时阻塞
mem_hook();
my_openssl_app();
mem_unhook();
return 0;
}
void my_openssl_app()
{
UCHAR ucBuf[0x100 - 3];
int lenBuf = sizeof(ucBuf);
int i = 0;
UCHAR* pEncBuf = NULL;
int lenEncBuf = 0;
UCHAR* pDecBuf = NULL;
int lenDecBuf = 0;
// 可以在EVP_CipherInit_ex()之后, 用EVP_CIPHER_CTX_get_key_length()/EVP_CIPHER_CTX_get_iv_length()看长度
UCHAR key[0x10]; // aes-128-cbc's key len = 0x10
UCHAR iv[0x10]; // aes-128-cbc's iv len = 0x10
for (i = 0; i < 0x10; i++)
{
key[i] = (UCHAR)i;
iv[i] = (UCHAR)i;
}
for (i = 0; i < lenBuf; i++)
{
ucBuf[i] = (UCHAR)i;
}
do {
printf("before enc:\n");
BIO_dump_fp(stdout, ucBuf, lenBuf);
// enc
//
// 如果输入不是0x10对齐, 加密后, 就会自动0x10对齐(多出几个字节)
// 所以要自己记录加密前的长度, 且加密时, 要将输出(加密后)buffer 16对齐(或直接比输入的长度多16字节)
// 且加密后, 要自己记录加密后的长度
if (!aes_128_cbc_EncDec_v1(true, ucBuf, lenBuf, (UCHAR*)key, sizeof(key), (UCHAR*)iv, sizeof(iv), pEncBuf, lenEncBuf))
{
assert(false);
break;
}
// printf("enc before lenBuf = %d, enc after lenEncBuf = %d\n", lenBuf, lenEncBuf);
// enc before lenBuf = 253, enc after lenEncBuf = 256
printf("after enc:\n");
BIO_dump_fp(stdout, pEncBuf, lenEncBuf);
// dec
if (!aes_128_cbc_EncDec_v1(false, pEncBuf, lenEncBuf, (UCHAR*)key, sizeof(key), (UCHAR*)iv, sizeof(iv), pDecBuf, lenDecBuf))
{
assert(false);
break;
}
// 解密后的数据长度和解密前一样了
printf("after dec:\n");
BIO_dump_fp(stdout, pDecBuf, lenDecBuf);
// 比较明文和解密后的明文是否相同
if ((lenDecBuf != lenBuf) || (0 != memcmp(ucBuf, pDecBuf, lenBuf)))
{
assert(false);
break;
}
printf("enc / dec all ok\n");
} while (false);
if (NULL != pEncBuf)
{
OPENSSL_free(pEncBuf);
pEncBuf = NULL;
}
if (NULL != pDecBuf)
{
OPENSSL_free(pDecBuf);
pDecBuf = NULL;
}
}
bool aes_128_cbc_EncDec_v1(
bool isEnc,
IN const UCHAR* pszBufIn, IN int lenBufIn,
IN const UCHAR* key, IN int lenKey,
IN const UCHAR* iv, IN int lenIv,
OUT UCHAR*& pOutBuf, OUT int& lenOutBuf)
{
bool b_rc = false;
const EVP_CIPHER* c = NULL;
const EVP_MD* dgst = NULL;
BIO* bio_in = NULL;
BIO* bio_out = NULL;
BIO* bio_filter = NULL;
int i_tmp = 0;
int i_rc = 0;
int i_out_len_all = 0;
size_t sz_rd = 0;
size_t sz_wt = 0;
const char* psz = NULL;
EVP_CIPHER_CTX* _evp_cipher_ctx = NULL;
UCHAR ucBufRd[1024];
do {
lenOutBuf = 0;
if ((NULL == pszBufIn) || (lenBufIn <= 0) ||
(NULL == key) || (lenKey <= 0) ||
(NULL == iv) || (lenIv <= 0))
{
break;
}
c = EVP_aes_128_cbc(); // 这是最初的加密, 没有任何保护的代码, 不用EVP_CIPHER_fetch()来暴露算法名称字符串
// 可以从算法对象得到算法名称
//psz = EVP_CIPHER_get0_name(c);
//printf("EVP_aes_128_cbc()'s alg name is : %s\n", psz);
// EVP_aes_128_cbc()'s alg name is : AES-128-CBC
i_tmp = EVP_CIPHER_get_key_length(c);
if (i_tmp != lenKey)
{
break;
}
i_tmp = EVP_CIPHER_get_iv_length(c);
if (i_tmp != lenIv)
{
break;
}
dgst = EVP_sha256(); // 这是最初的加密, 没有任何保护的代码, 不用EVP_MD_fetch()来暴露算法名称字符串
bio_in = BIO_new_mem_buf(pszBufIn, lenBufIn);
if (NULL == bio_in)
{
break;
}
bio_filter = BIO_new(BIO_f_cipher());
// BIO_set_cipher(bio_out, c, key, iv, (isEnc ? 1 : 0));
// 由于要改变算法的上下文, 所以要调用BIO_get_cipher_ctx, 而不是调用BIO_set_cipher
// 此时 _evp_cipher_ctx 是 NULL
// !!! _evp_cipher_ctx 是从bio_filter取出来的, 不能自己新建ctx, 否则向bio_filter写东西时, 就不会加密了, 因为上下文不对
BIO_get_cipher_ctx(bio_filter, &_evp_cipher_ctx); // 这里将bio和算法上下文关联了
// 此时 _evp_cipher_ctx 不为空, 是bio_filter要用到的算法ctx地址
// 向ctx中单独设置加密算法/key/iv
// 官方原版实现是分成2步(先设置算法, 再设置key/iv, 有点脱裤子放屁的感觉)
if (!EVP_CipherInit_ex(_evp_cipher_ctx, c, NULL, key, iv, (isEnc ? 1 : 0))) {
// ERR_print_errors(bio_err);
ERR_print_errors_fp(stderr);
break;
}
bio_out = BIO_new(BIO_s_mem());
if (NULL == bio_out)
{
break;
}
// BIO_push返回的就是参数1 bio_out
// 释放时, 只需要 BIO_free_all(bio_filter), 不用管bio_out, 因为 bio_out已经加入bio_filter链
// !!! 必须向 bio_filter中显势写入从明文bio_in读到的内容, 而不能直接读取bio_filter或者bio_out, 否则报错
BIO_push(bio_filter, bio_out); // BIO链, 将明文写入bio_filter, 等全部写完了, 再读biofilter, 就是加密好的密文了
// bio_in = BIO_new_mem_buf(pszBufIn, lenBufIn);
i_out_len_all = lenBufIn + 0x10; // 加密时, 如果明文长度不是0x10对齐, 那么加密后的长度可能比明文长最多0x10个字节
pOutBuf = (UCHAR*)OPENSSL_malloc(i_out_len_all);
if (NULL == pOutBuf)
{
break;
}
lenOutBuf = 0;
do {
i_rc = BIO_read_ex(bio_in, ucBufRd, sizeof(ucBufRd), &sz_rd);
if ((1 != i_rc) || (0 == sz_rd))
{
// 如果将东西读完了, 也是失败, 这里不错算.
i_rc = BIO_flush(bio_filter); // 相当于EVP_CipherFinal_ex()
if (1 != i_rc)
{
ERR_print_errors_fp(stderr);
goto END;
}
break;
}
// 必须向链条的顶部写(write to bio_filter)
// 等全部写完(BIO_flush(bio_filter)), 再从链头(bio_filter)读取时, 就已经是加密完的密文了
i_rc = BIO_write_ex(bio_filter, ucBufRd, sz_rd, &sz_wt); // 相当于 EVP_CipherUpdate()
if (1 != i_rc)
{
ERR_print_errors_fp(stderr);
break;
}
} while (true);
lenOutBuf = 0;
do {
i_rc = BIO_read_ex(bio_out, ucBufRd, sizeof(ucBufRd), &sz_rd);
// 这里读最后一块的时候
// 加密时, 已经是0x10对齐了
// 解密时, 已经是实际的size了
if (1 != i_rc)
{
// 如果将东西读完了, 也是失败, 这里不错算.
// ERR_print_errors_fp(stderr);
break;
}
memcpy(pOutBuf + lenOutBuf, ucBufRd, sz_rd);
lenOutBuf += sz_rd;
} while (true);
b_rc = true;
} while (false);
END:
if (NULL != bio_in)
{
BIO_free(bio_in);
}
if (NULL != bio_filter)
{
BIO_free_all(bio_filter); // bio_filter是BIO链, 释放时要用 BIO_free_all()
}
// 不用释放 _evp_cipher_ctx, 因为 _evp_cipher_ctx属于 bio_filter
if (!b_rc)
{
if (NULL != pOutBuf)
{
OPENSSL_free(pOutBuf);
pOutBuf = NULL;
}
}
return b_rc;
}
清晰一些的版本
对BIO_push()有了新的认识, 修正了一个版本, 优化了一下, 去掉了没用的代码.
c
bool aes_128_cbc_v2(
bool isEnc,
IN const UCHAR* pszBufIn, IN int lenBufIn,
IN const UCHAR* key, IN int lenKey,
IN const UCHAR* iv, IN int lenIv,
OUT UCHAR*& pOutBuf, OUT int& lenOutBuf)
{
bool b_rc = false;
const EVP_CIPHER* _evp_cipher = NULL;
BIO* bio_container = NULL;
BIO* bio_cipher = NULL;
BIO* bio_header = NULL;
BIO* bio_tail = NULL;
BIO* bio_write_to = NULL;
BIO* bio_read_from = NULL;
int i_tmp = 0;
int i_rc = 0;
size_t sz_rd = 0;
size_t sz_wt = 0;
const char* psz = NULL;
EVP_CIPHER_CTX* _evp_cipher_ctx = NULL;
int len = 0;
do {
lenOutBuf = 0;
if ((NULL == pszBufIn) || (lenBufIn <= 0) ||
(NULL == key) || (lenKey <= 0) ||
(NULL == iv) || (lenIv <= 0))
{
break;
}
_evp_cipher = EVP_aes_128_cbc(); // 这是最初的加密, 没有任何保护的代码, 不用EVP_CIPHER_fetch()来暴露算法名称字符串
// 可以从算法对象得到算法名称
//psz = EVP_CIPHER_get0_name(c);
//printf("EVP_aes_128_cbc()'s alg name is : %s\n", psz);
// EVP_aes_128_cbc()'s alg name is : AES-128-CBC
i_tmp = EVP_CIPHER_get_key_length(_evp_cipher);
if (i_tmp != lenKey)
{
break;
}
i_tmp = EVP_CIPHER_get_iv_length(_evp_cipher);
if (i_tmp != lenIv)
{
break;
}
//bio_in = BIO_new_mem_buf(pszBufIn, lenBufIn);
//if (NULL == bio_in)
//{
// break;
//}
bio_cipher = BIO_new(BIO_f_cipher());
// BIO_set_cipher(bio_out, c, key, iv, (isEnc ? 1 : 0));
// 由于要改变算法的上下文, 所以要调用BIO_get_cipher_ctx, 而不是调用BIO_set_cipher
// 此时 _evp_cipher_ctx 是 NULL
// !!! _evp_cipher_ctx 是从bio_filter取出来的, 不能自己新建ctx, 否则向bio_filter写东西时, 就不会加密了, 因为上下文不对
BIO_get_cipher_ctx(bio_cipher, &_evp_cipher_ctx); // 这里将bio和算法上下文关联了
// 此时 _evp_cipher_ctx 不为空, 是bio_filter要用到的算法ctx地址
if (NULL == _evp_cipher_ctx)
{
break;
}
// 向ctx中单独设置加密算法/key/iv
// 官方原版实现是分成2步(先设置算法, 再设置key/iv, 有点脱裤子放屁的感觉)
if (!EVP_CipherInit_ex(_evp_cipher_ctx, _evp_cipher, NULL, key, iv, (isEnc ? 1 : 0))) {
// ERR_print_errors(bio_err);
ERR_print_errors_fp(stderr);
break;
}
bio_container = BIO_new(BIO_s_mem());
if (NULL == bio_container)
{
break;
}
// BIO_push返回的就是参数1 bio_out
// 释放时, 只需要 BIO_free_all(bio_filter), 不用管bio_out, 因为 bio_out已经加入bio_filter链
// !!! 必须向 bio_filter中显势写入从明文bio_in读到的内容, 而不能直接读取bio_filter或者bio_out, 否则报错
bio_header = BIO_push(bio_cipher, bio_container); // BIO链, 将明文写入bio_filter, 等全部写完了, 再读biofilter, 就是加密好的密文了
bio_tail = bio_container;
// !!! 对于aes加解密, 都是从bio_header写, 从 bio_tail读
if (isEnc)
{
// 加密时
bio_write_to = bio_header;
bio_read_from = bio_tail;
}
else {
// 解密时
bio_write_to = bio_header;
bio_read_from = bio_tail;
}
lenOutBuf = 0;
do {
// 必须向链条的顶部写(write to bio_filter)
// 等全部写完(BIO_flush(bio_filter)), 再从链头(bio_filter)读取时, 就已经是加密完的密文了
i_rc = BIO_write_ex(bio_write_to, pszBufIn, lenBufIn, &sz_wt); // 相当于 EVP_CipherUpdate()
if ((1 != i_rc) && (lenBufIn != sz_wt))
{
goto END;
break;
}
} while (false);
BIO_flush(bio_write_to);
len = BIO_pending(bio_read_from);
// 加密时, 在这里读到的数据长度是加密后的长度
// 解密时, 在这里读到的数据长度还是密文长度. 不过可以拿这个len开buffer, 因为明文比密文短
// bio_in = BIO_new_mem_buf(pszBufIn, lenBufIn);
// 加密时, 如果明文长度不是0x10对齐, 那么加密后的长度可能比明文长最多0x10个字节
pOutBuf = (UCHAR*)OPENSSL_malloc(len + 1); // 多留一个字节(可选)
if (NULL == pOutBuf)
{
break;
}
pOutBuf[len] = '\0';
lenOutBuf = 0;
do {
i_rc = BIO_read_ex(bio_read_from, pOutBuf + lenOutBuf, len - lenOutBuf, &sz_rd);
// 这里读最后一块的时候
// 加密时, 已经是0x10对齐了
// 解密时, 已经是实际的size了
if (1 != i_rc)
{
// 如果将东西读完了, 也是失败, 这里不错算.
// ERR_print_errors_fp(stderr);
break;
}
lenOutBuf += sz_rd;
if (lenOutBuf >= len)
{
break;
}
} while (true);
b_rc = true;
} while (false);
END:
if (NULL != bio_header)
{
BIO_free_all(bio_header); // bio_filter是BIO链, 释放时要用 BIO_free_all()
bio_header = NULL;
}
// 不用释放 _evp_cipher_ctx, 因为 _evp_cipher_ctx属于 bio_filter
if (!b_rc)
{
if (NULL != pOutBuf)
{
OPENSSL_free(pOutBuf);
pOutBuf = NULL;
}
}
return b_rc;
}