openssl3.2 - exp - aes-128-cbc

文章目录

    • [openssl3.2 - exp - aes-128-cbc](#openssl3.2 - exp - aes-128-cbc)
    • 概述
    • 笔记
    • [openssl 命令行实现](#openssl 命令行实现)
    • 简单直白的实现
      • [简单直白的实现 - 测试效果](#简单直白的实现 - 测试效果)
      • [简单直白的实现 - 测试工程](#简单直白的实现 - 测试工程)
    • 周全灵活的实现
      • [周全灵活的实现 - 测试效果](#周全灵活的实现 - 测试效果)
      • [周全灵活的实现 - 测试工程](#周全灵活的实现 - 测试工程)
    • 清晰一些的版本
    • END

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;
}

END

相关推荐
许野平9 天前
OpenSSL:生成 DER 格式的 RSA 密钥对
服务器·网络·openssl·rsa·pem·der
Xnah_10 天前
ubuntu 20.4 安装 openssl 3.x
ubuntu·openssl
redwingz15 天前
openssl签名报错
openssl·random
Anlige25 天前
PHP实现OPENSSL的EVP_BytesToKey
开发语言·php·openssl·evp
cooldream20091 个月前
升级 OpenSSL 的详细步骤(解决 SSH 漏洞的前提)
运维·ssh·openssl
年薪丰厚1 个月前
如何手动安装libcrypto.so.10和libssl.so.10这两个库?
openssl·libcrypto.so.10·libssl.so.10
恋喵大鲤鱼2 个月前
openssl(1) command
openssl
pzs02212 个月前
openssl的使用
openssl
小亦小亦_空中接力2 个月前
openssl+keepalived安装部署
openssl·keepalived
摸鱼手会滑2 个月前
源码编译安装python3.12没有ssl模块,python3.12 ModuleNotFoundError: No module named ‘_ssl‘
ssl·openssl·python3