基于OpenSSL API实现指定参数AES-256-CBC解密的问题求助
问题描述
我想用OpenSSL API替代系统命令实现解密操作。此前通过命令openssl enc -aes-256-cbc -pbkdf2 -iter 310000 -md sha256 -salt -in file.run -out encrypted_data.enc -pass pass:Password!加密数据,原本用system调用对应解密命令能正常解密,但改用OpenSSL API编写的代码运行后,解密出的文件不正确,且生成的salt、密钥和IV与预期不符。以下是原系统命令解密代码和当前有问题的API代码,请求编写适配原加密参数的正确API解密代码。
原系统命令解密代码
bool CryptoProcessor::_decryptData(Any &response) { String tempPrefix = String("/tmp/aaaa-") + uuid(); std::ifstream input(_inputFile, std::ios::binary); // 输入文件是加密文件,头部包含签名和对称密钥 input.seekg(SIGNATURE_SIZE + KEY_SIZE, std::ios::beg); String encryptedDataFileName = tempPrefix + ".encrypted-data"; OutputFileStream encryptedDataFile; encryptedDataFile.open(encryptedDataFileName, std::ios::binary); char chunk[CHUNK_SIZE]; while (!input.eof()) { input.read(chunk, CHUNK_SIZE); int bytesRead = input.gcount(); encryptedDataFile.write(chunk, bytesRead); } encryptedDataFile.close(); input.close(); String runFileName = tempPrefix + ".run"; String command = String("openssl enc -aes-256-cbc -d -pbkdf2 -iter 310000 -p -md sha256 -salt -pass pass:") + _symmetricKey + " -in '" + encryptedDataFileName + "' -out '" + runFileName +"'"; // 加-p参数是为了查看salt、密钥和IV的值,对比API实现是否正确 logger(LOG_INFO) << command << endl; ANY_ASSERT(system(command.c_str()) == 0, "Decryption failed") return true; }
当前有问题的API解密代码
// Create an OpenSSL BIO object for input and output BIO *bio_in = BIO_new_file(encryptedDataFileName.c_str(), "rb"); BIO *bio_out = BIO_new_file(runFileName.c_str(), "wb"); if (!bio_in || !bio_out) { logger(LOG_ERROR) << "Failed to open input/output BIOs" << std::endl; return false; } // Create an EVP decryption context EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if (!ctx) { BIO_free_all(bio_in); BIO_free_all(bio_out); logger(LOG_ERROR) << "Failed to create cipher context" << std::endl; return false; } // Set the AES-256-CBC cipher if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, nullptr, nullptr) != 1) { EVP_CIPHER_CTX_free(ctx); BIO_free_all(bio_in); BIO_free_all(bio_out); logger(LOG_ERROR) << "Failed to initialize decryption context" << std::endl; return false; } EVP_CIPHER_CTX_set_padding(ctx, 0); // Disable padding // Set the symmetric key using PBKDF2 unsigned char derivedKey[32]; // 256 bits for AES-256 int iterCount = 310000; logger(LOG_INFO) << "Iterations: " << iterCount << std::endl; const char *passphrase = _symmetricKey.c_str(); logger(LOG_INFO) << "Passphrase: " << _symmetricKey << std::endl; unsigned char salt[PKCS5_SALT_LEN]; RAND_bytes(salt, sizeof(salt)); unsigned char iv[16]; RAND_bytes(iv, sizeof(iv)); if (PKCS5_PBKDF2_HMAC(passphrase, -1, salt, sizeof(salt), iterCount, EVP_sha256(), 32, derivedKey) != 1) { EVP_CIPHER_CTX_free(ctx); BIO_free_all(bio_in); BIO_free_all(bio_out); logger(LOG_ERROR) << "Failed to derive encryption key using PBKDF2" << std::endl; return false; } logger(LOG_INFO) << "Derived Key: "; for (int i = 0; i < 32; ++i) { logger(LOG_INFO) << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(derivedKey[i]); } logger(LOG_INFO) << std::dec << std::endl; // Set the key and IV for decryption if (EVP_DecryptInit_ex(ctx, nullptr, nullptr, derivedKey, nullptr) != 1) { EVP_CIPHER_CTX_free(ctx); BIO_free_all(bio_in); BIO_free_all(bio_out); logger(LOG_ERROR) << "Failed to set decryption key and IV" << std::endl; return false; }
修正后的API解密代码
原代码的核心问题包括:随机生成salt/IV而非从加密文件读取、错误禁用填充、未完成解密流程。以下是适配原加密参数的正确实现:
bool CryptoProcessor::_decryptData(Any &response) { String tempPrefix = String("/tmp/aaaa-") + uuid(); // 提取加密数据部分(和原代码逻辑一致) std::ifstream input(_inputFile, std::ios::binary); input.seekg(SIGNATURE_SIZE + KEY_SIZE, std::ios::beg); String encryptedDataFileName = tempPrefix + ".encrypted-data"; OutputFileStream encryptedDataFile; encryptedDataFile.open(encryptedDataFileName, std::ios::binary); char chunk[CHUNK_SIZE]; while (!input.eof()) { input.read(chunk, CHUNK_SIZE); int bytesRead = input.gcount(); encryptedDataFile.write(chunk, bytesRead); } encryptedDataFile.close(); input.close(); String runFileName = tempPrefix + ".run"; FILE *inFile = fopen(encryptedDataFileName.c_str(), "rb"); FILE *outFile = fopen(runFileName.c_str(), "wb"); if (!inFile || !outFile) { logger(LOG_ERROR) << "Failed to open input/output files" << std::endl; if (inFile) fclose(inFile); if (outFile) fclose(outFile); return false; } // 读取加密文件头部的Salt标识和salt值 unsigned char saltHeader[8]; unsigned char salt[PKCS5_SALT_LEN]; // PKCS5_SALT_LEN是8字节 if (fread(saltHeader, 1, 8, inFile) != 8 || memcmp(saltHeader, "Salted__", 8) != 0) { logger(LOG_ERROR) << "Invalid encrypted file header (missing Salted__)" << std::endl; fclose(inFile); fclose(outFile); return false; } if (fread(salt, 1, sizeof(salt), inFile) != sizeof(salt)) { logger(LOG_ERROR) << "Failed to read salt from encrypted file" << std::endl; fclose(inFile); fclose(outFile); return false; } // 创建解密上下文 EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if (!ctx) { logger(LOG_ERROR) << "Failed to create cipher context" << std::endl; fclose(inFile); fclose(outFile); return false; } // 派生密钥和IV:OpenSSL enc命令用PBKDF2生成32字节密钥+16字节IV,共48字节 unsigned char keyIv[48]; int iterCount = 310000; const char *passphrase = _symmetricKey.c_str(); if (PKCS5_PBKDF2_HMAC(passphrase, -1, salt, sizeof(salt), iterCount, EVP_sha256(), sizeof(keyIv), keyIv) != 1) { logger(LOG_ERROR) << "Failed to derive key and IV using PBKDF2" << std::endl; EVP_CIPHER_CTX_free(ctx); fclose(inFile); fclose(outFile); return false; } unsigned char *key = keyIv; unsigned char *iv = keyIv + 32; // 前32字节是密钥,后16字节是IV // 初始化AES-256-CBC解密上下文,保留默认PKCS#7填充 if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key, iv) != 1) { logger(LOG_ERROR) << "Failed to initialize decryption context" << std::endl; EVP_CIPHER_CTX_free(ctx); fclose(inFile); fclose(outFile); return false; } // 执行解密流程 unsigned char inBuf[1024]; unsigned char outBuf[1024 + EVP_MAX_BLOCK_LENGTH]; // 预留填充空间 int inLen, outLen; bool success = true; while ((inLen = fread(inBuf, 1, sizeof(inBuf), inFile)) > 0) { if (EVP_DecryptUpdate(ctx, outBuf, &outLen, inBuf, inLen) != 1) { success = false; break; } fwrite(outBuf, 1, outLen, outFile); } if (success) { // 处理最后一块的填充 if (EVP_DecryptFinal_ex(ctx, outBuf, &outLen) != 1) { success = false; } else { fwrite(outBuf, 1, outLen, outFile); } } // 清理资源 EVP_CIPHER_CTX_free(ctx); fclose(inFile); fclose(outFile); if (!success) { logger(LOG_ERROR) << "Decryption failed" << std::endl; return false; } logger(LOG_INFO) << "Decryption completed successfully" << std::endl; return true; }
关键修正点说明
- 读取文件头部的salt:
openssl enc -salt加密的文件开头是Salted__(8字节)+ 8字节salt,解密时必须读取这个salt,不能随机生成。 - 派生密钥和IV:原命令用PBKDF2生成48字节数据,前32字节是AES-256密钥,后16字节是CBC模式的IV,而非单独生成IV。
- 保留填充:原加密使用默认的PKCS#7填充,解密时不能禁用填充,否则会导致最后一块数据错误。
- 完整解密流程:实现了从文件读取数据、执行解密更新、处理最终填充块的完整逻辑。
内容的提问来源于stack exchange,提问作者Manoj Chavva
相关产品推荐
相关产品推荐

