1. 背景
プロジェクトの要件とファイルの暗号化機能があり、評価の結果、最終的に AES-256-CBC 暗号化を使用することにしました。この種の暗号化を C++ で実装するには、多くの中国のメソッドやサードパーティのライブラリがありますが、動作環境の制限により、選択できるライブラリがあまりないため、最終的に openssl を使用することにしました。
AES 暗号化に関する関連知識は Baidu で直接見つけることができるので、ここでは詳しく説明しません。
2. 暗号化
XuFile.h
//
// Created by zhengqiuxu on 2021/10/15.
//
#ifndef VIS_ADOS_I7_XUFILE_H
#define VIS_ADOS_I7_XUFILE_H
#include <vector>
#include <string>
#include <openssl/evp.h>
#include <openssl/aes.h>
#include <openssl/rand.h>
#include <iostream>
#include <openssl/err.h>
class XuFile {
public:
/**
* 用AES-256-CBC的方式加密一个文件
* @param inputFile : 输入文件(源文件)地址
* @param outputFile : 输出文件(加密后的文件)地址
* @param key : 密钥
* @param iv : 初始向量
* @return 0:成功 其他:失败
*/
int encryptFile_AES_256_CBC(const std::string &inputFile, const std::string &outputFile, const uint8_t *key, const uint8_t *iv);
private:
};
#endif //VIS_ADOS_I7_XUFILE_H
XuFile.cpp
/**
* 用AES-256-CBC的方式加密一个文件
* @param inputFile : 输入文件(源文件)地址
* @param outputFile : 输出文件(加密后的文件)地址
* @param key : 密钥
* @param iv : 初始向量
* @return 0:成功 其他:失败
*/
int XuFile::encryptFile_AES_256_CBC(const std::string &inputFile, const std::string &outputFile, const uint8_t *key,
const uint8_t *iv) {
int ret = -1;
try {
/* 源文件的输入流 */
std::ifstream srcIn(inputFile.c_str(), std::ios_base::in | std::ios_base::binary | std::ios_base::ate);
/* 加密后的文件的输出流 */
std::ofstream decOut(outputFile.c_str(), std::ios_base::out | std::ios_base::binary);
/* 如果都打开了才能进行解密 */
if (srcIn.is_open() && decOut.is_open()) {
/* 初始化OpenSSL库 */
OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \
| OPENSSL_INIT_ADD_ALL_DIGESTS, NULL);
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
/* 初始化下加密工具 */
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX_init(ctx);
EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv);
EVP_CIPHER_CTX_set_padding(ctx, EVP_PADDING_PKCS7);
/* 确定文件的大小 */
uint64_t inputFileLen = srcIn.tellg();
/* 重新将文件流指针置于文件开始的位置 */
srcIn.seekg(0, std::ios_base::beg);
/* 用来读数据的缓冲区 */
char readBuf[8192] = {0x00};
/* 用来进行加密然后写数据的缓冲区(比读取大32个字节是为了放填充数据) */
uint8_t writeBuf[8192 + 32] = {0x00};
/* 记录读了多少长度的变量 */
uint64_t allReadLen = 0;
/* 循环从流里读出文件 */
while (allReadLen < inputFileLen) {
/* 当前应该读多长的数据 */
int curRead = sizeof(readBuf);
if (inputFileLen - allReadLen < sizeof(readBuf)) {
curRead = inputFileLen - allReadLen;
}
/* 读出原文 */
srcIn.read(readBuf, curRead);
/* 记录一下长度 */
allReadLen = allReadLen + curRead;
/* 对原文一包包读出来进行加密 */
int toEnBufLen = 0;
int curEnLen = 0;
if (!EVP_EncryptUpdate(ctx, writeBuf, &toEnBufLen, reinterpret_cast<const unsigned char *>(readBuf),
curRead)) {
printf("EVP_EncryptUpdate failed! err:%s \n", ERR_error_string(ERR_get_error(), NULL));
break;
}
/* 如果是最后一包了,那么就调用一下结束,同时拿到填充出来的数据,只有调用了结束,才会加填充,这样加密一整个文件的时候就只是尾部有填充字节 */
if (curRead < sizeof(readBuf)) {
if (!EVP_EncryptFinal_ex(ctx, writeBuf + toEnBufLen, &curEnLen)) {
printf("EVP_EncryptFinal_ex failed! err:%s \n", ERR_error_string(ERR_get_error(), NULL));
break;
}
toEnBufLen += curEnLen;
}
// /* 将密文写入文件 */
decOut.write(reinterpret_cast<const char *>(writeBuf), toEnBufLen);
// printf("allReadLen=%llu inputFileLen=%llu toEnBufLen=%d curEnLen=%d curRead=%d\n",allReadLen,inputFileLen,toEnBufLen,curEnLen,curRead);
}
/* 关闭流 */
srcIn.close();
decOut.close();
/* 关闭加密工具 */
EVP_CIPHER_CTX_reset(ctx);
EVP_CIPHER_CTX_free(ctx);
ret = 0;
} else {
printf("%s or %s can not open! \n", inputFile.c_str(), outputFile.c_str());
}
} catch (...) {
printf("encryptFile_AES_256_CBC failed! err:%s \n", strerror(errno));
}
return ret;
}
3. 復号化
XuFile.h
//
// Created by zhengqiuxu on 2021/10/15.
//
#ifndef VIS_ADOS_I7_XUFILE_H
#define VIS_ADOS_I7_XUFILE_H
#include <vector>
#include <string>
#include <openssl/evp.h>
#include <openssl/aes.h>
#include <openssl/rand.h>
#include <iostream>
#include <openssl/err.h>
class XuFile {
public:
/**
* 用AES-256-CBC的方式解密一个文件
* @param inputFile : 输入文件(源文件)地址
* @param outputFile : 输出文件(解密后的文件)地址
* @param key : 密钥
* @param iv : 初始向量
* @return 0:成功 其他:失败
*/
int decryptFile_AES_256_CBC(const std::string& inputFile, const std::string& outputFile, const uint8_t *key, const uint8_t *iv);
private:
};
#endif //VIS_ADOS_I7_XUFILE_H
XuFile.cpp
/**
* 用AES-256-CBC的方式解密一个文件
* @param inputFile : 输入文件(源文件)地址
* @param outputFile : 输出文件(解密后的文件)地址
* @param key : 密钥
* @param iv : 初始向量
* @return 0:成功 其他:失败
*/
int XuFile::decryptFile_AES_256_CBC(const std::string &inputFile, const std::string &outputFile, const uint8_t *key, const uint8_t *iv) {
int ret = -1;
try {
/* 源文件的输入流 */
std::ifstream srcIn(inputFile.c_str(),std::ios_base::in | std::ios_base::binary | std::ios_base::ate);
/* 解密后的文件的输出流 */
std::ofstream decOut(outputFile.c_str(),std::ios_base::out | std::ios_base::binary | std::ios_base::trunc);
/* 如果都打开了才能进行解密 */
if(srcIn.is_open() && decOut.is_open()){
/* 初始化OpenSSL库 */
OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \
| OPENSSL_INIT_ADD_ALL_DIGESTS, NULL);
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
/* 初始化下解密工具 */
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX_init(ctx);
int rr = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv);
EVP_CIPHER_CTX_set_padding(ctx,EVP_PADDING_PKCS7);
/* 确定文件的大小 */
uint64_t inputFileLen = srcIn.tellg();
/* 重新将文件流指针置于文件开始的位置 */
srcIn.seekg(0, std::ios_base::beg);
/* 用来读数据的缓冲区 */
char readBuf[8192] = {0x00};
/* 用来进行解密然后写数据的缓冲区(比读取大32个字节是为了放填充数据) */
uint8_t writeBuf[8192+32] = {0x00};
/* 记录读了多少长度的变量 */
uint64_t allReadLen = 0;
/* 循环从流里读出文件 */
while (allReadLen < inputFileLen){
/* 当前应该读多长的数据 */
int curRead = sizeof(readBuf);
if(inputFileLen - allReadLen < sizeof(readBuf)){
curRead = inputFileLen - allReadLen;
}
/* 读出密文 */
srcIn.read(readBuf, curRead);
/* 记录一下长度 */
allReadLen = allReadLen + curRead;
/* 对密文一包包读取数据的数据进行解密 */
int toEnBufLen = 0;
int curEnLen = 0;
if(!EVP_DecryptUpdate(ctx, writeBuf, &toEnBufLen,reinterpret_cast<const unsigned char *>(readBuf), curRead)){
printf("EVP_DecryptUpdate failed! err:%s \n", ERR_error_string(ERR_get_error(),NULL));
break;
}
/* 如果是最后一包了,那么就调用一下结束 */
if(curRead < sizeof(readBuf)){
if(!EVP_DecryptFinal(ctx, writeBuf + toEnBufLen, &curEnLen)){
printf("EVP_DecryptFinal failed! err:%s \n", ERR_error_string(ERR_get_error(),NULL));
break;
}
toEnBufLen += curEnLen;
}
// /* 将解密后的数据写入文件 */
decOut.write(reinterpret_cast<const char *>(writeBuf), toEnBufLen);
printf("decryptFile_AES_256_CBC allReadLen=%llu inputFileLen=%llu toEnBufLen=%d curEnLen=%d curRead=%d\n",allReadLen,inputFileLen,toEnBufLen,curEnLen,curRead);
}
/* 关闭流 */
srcIn.close();
decOut.close();
/* 关闭解密工具 */
EVP_CIPHER_CTX_reset(ctx);
EVP_CIPHER_CTX_free(ctx);
ret = 0;
}else{
printf("%s or %s can not open! \n", inputFile.c_str(),outputFile.c_str());
}
} catch (...) {
printf("decryptFile_AES_256_CBC failed! errstr:%s \n", ERR_error_string(ERR_get_error(),NULL));
}
return ret;
}
4. その他
開発途中でいくつかトラブルが発生し、最初は不慣れで遅れてしまったので記録しておきます。
1. 暗号化する場合、データのセグメントがストリームから読み取られるため、EVP_EncryptUpdate が呼び出されるたびに EVP_EncryptFinal_ex が呼び出され、その結果、データの各セグメントの後にパディング データが追加されるため、後ですべてのデータが暗号化されます。パディングデータ。
2. C++ を使用して暗号化し、javax.crypto.Cipher クラスを使用して Java を使用して復号化します。キーまたは IV エラーの場合、パディング データ エラー、ブロック エラーなどのさまざまなプロンプトが表示される場合がありますが、理由はすべてキーまたは IV エラーであるため、復号化が失敗した場合は、キーと IV エラーが存在するかどうかを確認する必要があります。 IV はできるだけ早く正しくなります。
3. 暗号化時には PKCS7 パディングまたは PKCS5 パディングを選択できますが、PKCS7 は PKCS5 と互換性があるため、復号化時には PKCS7 を使用します。