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

相关推荐
深耕AI9 天前
Win64OpenSSL-3_5_2.exe【安装步骤】
openssl
看那山瞧那水10 天前
DELPHI 利用OpenSSL实现加解密,证书(X.509)等功能
delphi·openssl
洋哥网络科技20 天前
openssl升级
openssl
Lazy Dave1 个月前
gmssl私钥文件格式
网络安全·ssl·openssl
沉在嵌入式的鱼2 个月前
RK3588移植Openssl库
linux·rk3588·openssl
黑屋里的马2 个月前
ssl相关命令生成证书
服务器·网络·ssl·openssl·gmssl
fangeqin2 个月前
ubuntu源码安装python3.13遇到Could not build the ssl module!解决方法
linux·python·ubuntu·openssl
API开发3 个月前
苹果芯片macOS安装版Homebrew(亲测) ,一键安装node、python、vscode等,比绿色软件还干净、无污染
vscode·python·docker·nodejs·openssl·brew·homebrew
码农不惑3 个月前
Rust使用tokio(二)HTTPS相关
https·rust·web·openssl
liulilittle3 个月前
通过高级处理器硬件指令集AES-NI实现AES-256-CFB算法并通过OPENSSL加密验证算法正确性。
linux·服务器·c++·算法·安全·加密·openssl