基于OpenSSL的C/S加解密故障求助:EVP_DecryptFinal_ex解密报错
问题排查:OpenSSL AES-256-CBC解密会话密钥时EVP_DecryptFinal_ex报错
我基于OpenSSL实现客户端-服务器加解密模型:服务器用已建立的密钥skey,通过AES-256-CBC加密会话密钥字节数组,同时用该会话密钥加密验证消息"Verification Message SessionKey",将IV与密文拼接后分两条发送给客户端。客户端接收后,先用skey解密第一条消息(对应服务器的会话密钥密文),但执行EVP_DecryptFinal_ex步骤时报错。调试发现解密文本长度应为32字节,但decryptedText1占用48字节。此前仅加密验证消息时逻辑正常,现在因加密对象为字节数组出现问题,已卡两周,求排查。
服务端代码
// Generate an initialization vector (IV) for CBC mode const EVP_CIPHER* cipher = EVP_aes_256_cbc(); // Use the desired cipher algorithm EVP_CIPHER_CTX* ctx_encrypt = EVP_CIPHER_CTX_new(); const unsigned char* plaintext = sessionKey; size_t plaintext_len = sessionKeyLen; const int MAX_TEXT_LENGTH = 256; unsigned char iv[EVP_MAX_IV_LENGTH]; if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) != 1) { fprintf(stderr, "Error generating IV\n"); exit(1); } // Set the IV for the encryption operation if (EVP_EncryptInit_ex(ctx_encrypt, EVP_aes_256_cbc(), NULL, skey, iv) != 1) { fprintf(stderr, "Error setting encryption IV\n"); exit(1); } // Encrypt the plaintext (session key) unsigned char ciphertext[MAX_TEXT_LENGTH + EVP_MAX_BLOCK_LENGTH]; int ciphertext_len; if (EVP_EncryptUpdate(ctx_encrypt, ciphertext, &ciphertext_len, plaintext, plaintext_len) != 1) { fprintf(stderr, "Error encrypting data\n"); exit(1); } // Finalize the encryption int final_len; if (EVP_EncryptFinal_ex(ctx_encrypt, ciphertext + ciphertext_len, &final_len) != 1) { fprintf(stderr, "Error finalizing encryption\n"); exit(1); } ciphertext_len += final_len; const size_t ivLength = EVP_CIPHER_iv_length(EVP_aes_256_cbc()); // Get the length of the IV const size_t concatenatedMessageLength = ivLength + ciphertext_len; unsigned char concatenatedMessage[concatenatedMessageLength]; memcpy(concatenatedMessage, iv, ivLength); memcpy(concatenatedMessage + ivLength, ciphertext, ciphertext_len); printf("Concatenated Message 1:\n"); for (size_t i = 0; i < concatenatedMessageLength; i++) { printf("%02x", concatenatedMessage[i]); } printf("\n"); EVP_CIPHER_CTX_free(ctx_encrypt); // Preparing for Bob's share // Generate an initialization vector (IV) for CBC mode plaintext = (const unsigned char*)"Verification Message SessionKey"; plaintext_len = strlen((const char*)plaintext); EVP_CIPHER_CTX* ctx_encrypt2 = EVP_CIPHER_CTX_new(); if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) != 1) { fprintf(stderr, "Error generating IV\n"); exit(1); } // Set the IV for the encryption operation if (EVP_EncryptInit_ex(ctx_encrypt2, EVP_aes_256_cbc(), NULL, sessionKey, iv) != 1) { fprintf(stderr, "Error setting encryption IV\n"); exit(1); } // Encrypt the plaintext (Bob's share) if (EVP_EncryptUpdate(ctx_encrypt2, ciphertext, &ciphertext_len, plaintext, plaintext_len) != 1) { fprintf(stderr, "Error encrypting data\n"); exit(1); } // Finalize the encryption if (EVP_EncryptFinal_ex(ctx_encrypt2, ciphertext + ciphertext_len, &final_len) != 1) { fprintf(stderr, "Error finalizing encryption\n"); exit(1); } ciphertext_len += final_len; const size_t concatenatedMessageLength2 = ivLength + ciphertext_len; unsigned char concatenatedMessage2[concatenatedMessageLength2]; memcpy(concatenatedMessage2, iv, ivLength); memcpy(concatenatedMessage2 + ivLength, ciphertext, ciphertext_len); printf("Concatenated Message 2:\n"); for (size_t i = 0; i < concatenatedMessageLength2; i++) { printf("%02x", concatenatedMessage2[i]); } printf("\n"); EVP_CIPHER_CTX_free(ctx_encrypt2); // Sending Server Share and Verification Message if (concatenatedMessageLength > 0) { send(client_sock_Bob, concatenatedMessage, concatenatedMessageLength, 0); // Send the encrypted hash to the server usleep(100); if (concatenatedMessageLength2 > 0) { send(client_sock_Bob, concatenatedMessage2, concatenatedMessageLength2, 0); } else { printf("Server Verification message is Null: %s\n"); } } else { printf("Session key is Null : %s\n"); }
客户端代码
// Receive concatenatedMessage2 char receivedMessage1[4096]; ssize_t concatenatedMessageLength1 = recv(sock, receivedMessage1, sizeof(receivedMessage1), 0); if (concatenatedMessageLength1 > 0) { memcpy(concatenatedMessage1, receivedMessage1, concatenatedMessageLength1); } else if (concatenatedMessageLength1 == 0) { // Connection closed by the server fprintf(stderr, "Connection closed by the server.\n"); exit(1); } else { // Error receiving data fprintf(stderr, "Error receiving concatenatedMessage2.\n"); exit(1); } // Receive concatenatedMessage1 char receivedMessage2[4096]; ssize_t concatenatedMessageLength2 = recv(sock, receivedMessage2, sizeof(receivedMessage2), 0); if (concatenatedMessageLength2 > 0) { memcpy(concatenatedMessage2, receivedMessage2, concatenatedMessageLength2); } else if (concatenatedMessageLength2 == 0) { // Connection closed by the server fprintf(stderr, "Connection closed by the server.\n"); exit(1); } else { // Error receiving data fprintf(stderr, "Error receiving concatenatedMessage1.\n"); exit(1); } bzero(buffer, 1024); const EVP_CIPHER* cipher = EVP_aes_256_cbc(); // Use the desired cipher algorithm EVP_CIPHER_CTX* ctx_decrypt = EVP_CIPHER_CTX_new(); unsigned char decryptedText1[MAX_TEXT_LENGTH + EVP_MAX_BLOCK_LENGTH]; unsigned char decryptedText2[MAX_TEXT_LENGTH + EVP_MAX_BLOCK_LENGTH]; int decryptedText1_len; int decryptedText2_len; memset(decryptedText1, 0, sizeof(decryptedText1)); // Extract the IV from the concatenated message 2 memcpy(iv, concatenatedMessage1, EVP_MAX_IV_LENGTH); // Set the IV for the decryption operation if (EVP_DecryptInit_ex(ctx_decrypt, EVP_aes_256_cbc(), NULL, skey, iv) != 1) { fprintf(stderr, "Error setting decryption IV\n"); exit(1); } // Decrypt the ciphertext if (EVP_DecryptUpdate(ctx_decrypt, decryptedText1, &decryptedText1_len, concatenatedMessage1 + EVP_MAX_IV_LENGTH, concatenatedMessageLength1 - EVP_MAX_IV_LENGTH) != 1) { fprintf(stderr, "Error decrypting data\n"); exit(1); } // Finalize the decryption int final_len1; if (EVP_DecryptFinal_ex(ctx_decrypt, decryptedText1 + decryptedText1_len, &final_len1) != 1) { fprintf(stderr, "Error finalizing decryption\n"); exit(1); } decryptedText1_len += final_len1; // Print the decrypted plaintext printf("Decrypted Text: %.*s\n", decryptedText1_len, decryptedText1); EVP_CIPHER_CTX_free(ctx_decrypt); EVP_CIPHER_CTX* ctx_decrypt2 = EVP_CIPHER_CTX_new(); // Initialize the encryption/decryption context if (EVP_CipherInit_ex(ctx_decrypt2, cipher, NULL, NULL, NULL, 1) != 1) { fprintf(stderr, "Error initializing the cipher context\n"); EVP_CIPHER_CTX_free(ctx_decrypt2); return; } // Extract the IV from the concatenated message 1 memcpy(iv, concatenatedMessage2, EVP_MAX_IV_LENGTH); // Set the IV for the decryption operation if (EVP_DecryptInit_ex(ctx_decrypt2, EVP_aes_256_cbc(), NULL, decryptedText2, iv) != 1) { fprintf(stderr, "Error setting decryption IV\n"); exit(1); } // Decrypt the ciphertext if (EVP_DecryptUpdate(ctx_decrypt2, decryptedText2, &decryptedText2_len, concatenatedMessage2 + EVP_MAX_IV_LENGTH, concatenatedMessageLength2 - EVP_MAX_IV_LENGTH) != 1) { fprintf(stderr, "Error decrypting data\n"); exit(1); } // Finalize the decryption int final_len2; if (EVP_DecryptFinal_ex(ctx_decrypt2, decryptedText2 + decryptedText2_len, &final_len2) != 1) { fprintf(stderr, "Error finalizing decryption\n"); exit(1); } decryptedText2_len += final_len2; // Print the decrypted plaintext printf("Decrypted Text: %.*s\n", decryptedText2_len, decryptedText2); // Check if the decrypted message matches the expected message const unsigned char* expectedMessage2 = (const unsigned char*)"Verification Message SessionKey"; if (memcmp(decryptedText2, expectedMessage2, decryptedText2_len) == 0) { printf("Message verified\n"); const unsigned char* DoneMessage = (const unsigned char*)"Bob Hello Done"; ssize_t sent = send(sock, DoneMessage, strlen((const char*)DoneMessage), MSG_NOSIGNAL); if (sent < 0) { perror("[-]Error sending done message"); exit(1); } printf("Done message sent\n"); } else { printf("Message unverified\n"); } bzero(buffer, 1024);
报错代码行
// Finalize the decryption int final_len1; if (EVP_DecryptFinal_ex(ctx_decrypt, decryptedText1 + decryptedText1_len, &final_len1) != 1) { fprintf(stderr, "Error finalizing decryption\n"); exit(1); }
核心问题排查与修复
- 消息接收顺序完全颠倒:服务器先发送加密会话密钥的
concatenatedMessage,再发送加密验证消息的concatenatedMessage2,但客户端把第一条收到的消息当成验证消息,第二条当成会话密钥消息,导致解密用错IV和密文,触发EVP_DecryptFinal_ex失败。 - 第二次解密密钥参数错误:客户端解密验证消息时,
EVP_DecryptInit_ex传入的密钥是未初始化的decryptedText2,正确应该是刚解密出的会话密钥decryptedText1。 - 冗余的上下文初始化:客户端中
EVP_CipherInit_ex(ctx_decrypt2, cipher, NULL, NULL, NULL, 1)属于冗余操作,且设置为加密模式,后续调用解密初始化会导致上下文混乱,直接删除即可。 - 缓冲区未明确初始化:客户端中
concatenatedMessage1和concatenatedMessage2未提前声明并初始化,直接memcpy存在越界或垃圾数据风险,建议声明为unsigned char concatenatedMessage1[4096] = {0};这类明确的缓冲区。
修复后的关键代码片段
客户端接收部分修正
// 先接收加密会话密钥消息 unsigned char concatenatedMessage1[4096] = {0}; char receivedMessage1[4096]; ssize_t concatenatedMessageLength1 = recv(sock, receivedMessage1, sizeof(receivedMessage1), 0); if (concatenatedMessageLength1 > 0) { memcpy(concatenatedMessage1, receivedMessage1, concatenatedMessageLength1); } else if (concatenatedMessageLength1 == 0) { fprintf(stderr, "服务器关闭连接。\n"); exit(1); } else { fprintf(stderr, "接收加密会话密钥消息失败。\n"); exit(1); } // 再接收加密验证消息 unsigned char concatenatedMessage2[4096] = {0}; char receivedMessage2[4096]; ssize_t concatenatedMessageLength2 = recv(sock, receivedMessage2, sizeof(receivedMessage2), 0); if (concatenatedMessageLength2 > 0) { memcpy(concatenatedMessage2, receivedMessage2, concatenatedMessageLength2); } else if (concatenatedMessageLength2 == 0) { fprintf(stderr, "服务器关闭连接。\n"); exit(1); } else { fprintf(stderr, "接收加密验证消息失败。\n"); exit(1); }
客户端第二次解密修正
EVP_CIPHER_CTX* ctx_decrypt2 = EVP_CIPHER_CTX_new(); memcpy(iv, concatenatedMessage2, EVP_MAX_IV_LENGTH); // 使用解密得到的会话密钥作为解密验证消息的密钥 if (EVP_DecryptInit_ex(ctx_decrypt2, EVP_aes_256_cbc(), NULL, decryptedText1, iv) != 1) { fprintf(stderr, "设置解密IV失败\n"); exit(1); } if (EVP_DecryptUpdate(ctx_decrypt2, decryptedText2, &decryptedText2_len, concatenatedMessage2 + EVP_MAX_IV_LENGTH, concatenatedMessageLength2 - EVP_MAX_IV_LENGTH) != 1) { fprintf(stderr, "解密验证消息数据失败\n"); exit(1); } int final_len2; if (EVP_DecryptFinal_ex(ctx_decrypt2, decryptedText2 + decryptedText2_len, &final_len2) != 1) { fprintf(stderr, "完成验证消息解密失败\n"); exit(1); } decryptedText2_len += final_len2;
内容的提问来源于stack exchange,提问作者Waleed Ahmed
相关产品推荐
相关产品推荐

