我试图了解 OpenSSL 的 EVP_EncryptUpdate 函数的工作原理,特别是为什么它在多次调用时为相同的明文、密钥和 IV(最初提供)返回不同的密文。据我了解,每次调用后都应该更改 IV,并且该函数在内部处理 IV。问题是 IV 内部到底是如何处理的?尝试获取 IV 返回与初始化上下文的 IV 相同的 IV。
#include <openssl/ssl.h>
void print_array(char *title, unsigned char *buff, int len)
{
printf("%s:\n", title);
for(int i = 0; i<len; ++i)
printf("%02x ", buff[i]);
printf("\n");
}
void handleErrors()
{
printf("Error in encryption/decryption\n");
}
void encrypt_EVP_aes_256_gcm_init(EVP_CIPHER_CTX **ctx, unsigned char *key, unsigned char *iv)
{
if(!(*ctx = EVP_CIPHER_CTX_new()))
handleErrors();
if(1 != EVP_EncryptInit_ex(*ctx, EVP_aes_256_gcm(), NULL, key, iv))
handleErrors();
}
void encrypt(EVP_CIPHER_CTX *ctx, unsigned char *plaintext, int plaintext_len, unsigned char *ciphertext, int *ciphertext_len)
{
int len;
if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
handleErrors();
*ciphertext_len = len;
}
void test(const unsigned char* key, const unsigned char* iv, const unsigned char *data, int iters)
{
unsigned char enc_data[32];
int32_t cipherLength;
EVP_CIPHER_CTX *encrypt_ctx;
encrypt_EVP_aes_256_gcm_init(&encrypt_ctx, key, iv);
for(int i = 0; i<iters; ++i)
{
print_array("iv", EVP_CIPHER_CTX_iv(encrypt_ctx), 12);
encrypt(encrypt_ctx, data, 32, enc_data, &cipherLength);
print_array("enc data", enc_data, cipherLength);
}
}
int main()
{
const unsigned char* key = "\x3c\x57\x5e\x25\x5f\x43\x41\x69\x3d\x5e\x48\x29\x72\x54\x27\x55\x3e\x29\x28\x65\x31\x34\x4a\x3e\x52\x2f\x7c\x6a\x7b\x25\x78\x52";
const unsigned char* iv = "\x75\x71\x71\x55\x36\x33\x59\x52\x2c\x22\x74\x7d\x6f\x36\x6d\x2e";
const unsigned char data[32] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
test(key, iv, data, 3);
return 0;
}
我尝试输出IV,但似乎没有显示正在运行的IV。我只拿回提供的静脉注射。
如果执行您的 C 代码,则三次迭代将产生以下密文结果:
i = 0: 0x7e81d12cb6cd69e538f709f69274f7f397c375b460cae4f6433b556bd0b0f839
i = 1: 0x97c4c7be1b67c61975fe97288599b1029984ba05633fefd942d98400d20829e1
i = 2: 0x861e3f9f54f44bce0d5afe3c9554bb503a7a675e398485693d00519111a152d9
这些发生如下:
0x75717155363359522c22747d
。0x00000001
:0x75717155363359522c22747d00000001
。这样确定的初始值是为以后确定GCM认证标签而保留的,并不用于加密本身。0x75717155363359522c22747d00000002
。0x75717155363359522c22747d00000004
。0x75717155363359522c22747d00000006
,依此类推。我不知道有任何 EVP 函数可以检索初始值或中间计数器值。然而,由于初始值的计算和增量都是自动进行的,所以实际上没有必要这样做(正如第一条评论中已经提到的)。
关于加密,GCM 的工作原理类似于 CTR,上面的计数器对应于 IV。因此,通过使用 CTR 和确定的计数器而不是 GCM 和 12 字节 IV 进行加密,可以轻松验证计数器的正确性。
C代码可以进行相应的修改(为了简单起见,没有异常处理):
void encrypt_with_CTR(unsigned char* key, unsigned char* iv, unsigned char* data)
{
int cipherLength;
unsigned char ciphertext[32];
EVP_CIPHER_CTX* encrypt_ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(encrypt_ctx, EVP_aes_256_ctr(), NULL, key, iv);
EVP_EncryptUpdate(encrypt_ctx, ciphertext, &cipherLength, data, 32);
print_array((char*)"ciphertext: ", ciphertext, cipherLength);
}
...
unsigned char data[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
unsigned char key[] = "\x3c\x57\x5e\x25\x5f\x43\x41\x69\x3d\x5e\x48\x29\x72\x54\x27\x55\x3e\x29\x28\x65\x31\x34\x4a\x3e\x52\x2f\x7c\x6a\x7b\x25\x78\x52";
unsigned char iv_1[] = "\x75\x71\x71\x55\x36\x33\x59\x52\x2c\x22\x74\x7d\x00\x00\x00\x02";
unsigned char iv_2[] = "\x75\x71\x71\x55\x36\x33\x59\x52\x2c\x22\x74\x7d\x00\x00\x00\x04";
unsigned char iv_3[] = "\x75\x71\x71\x55\x36\x33\x59\x52\x2c\x22\x74\x7d\x00\x00\x00\x06";
encrypt_with_CTR(key, iv_1, data);
encrypt_with_CTR(key, iv_2, data);
encrypt_with_CTR(key, iv_3, data);
或者,可以使用 CyberChef 进行验证:
0x75717155363359522c22747d00000002
:
0x7e81d12cb6cd69e538f709f69274f7f397c375b460cae4f6433b556bd0b0f839
0x75717155363359522c22747d00000004
:
0x97c4c7be1b67c61975fe97288599b1029984ba05633fefd942d98400d20829e1
0x75717155363359522c22747d00000006
:
0x861e3f9f54f44bce0d5afe3c9554bb503a7a675e398485693d00519111a152d9
正如预期的那样,这两者都对应于具有 12 字节 IV 的 GCM 加密的密文。
备注:
EVP_EncryptFinal_ex()
计算的,这就是为什么必须在加密过程结束时调用这个函数的原因(见第一条评论)。然后必须使用 EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag)
检索标签(请参阅第二条评论)。EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)
为 IV 指定与建议的 12 字节不同的长度。在这种情况下,计数器的初始值的确定方式有所不同,即使用GHASH算法。iv_len
)。