C++使用openssl的EVP对文件进行AES-256-CBC加解密

1、背景

    有项目需求,有对文件进行加密的功能,经过评估,最终决定使用AES-256-CBC加密。在C++中要实现这种加密有很多中方式和第三方库,由于运行环境的限制,可选择的库不多,最终决定使用openssl来进行。

    关于AES加密的相关知识直接百度就可以百度到了,这里就不赘述了。

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,导致每一段数据后面都被添加了填充数据,所以后面改成所有的数据加密完后再调用EVP_EncryptFinal_ex获取填充数据。

2、使用C++加密后再使用Java解密,用的是javax.crypto.Cipher这个类。在key或者IV错误的情况,它可能有各种提示,比如说:填充数据错误、块错误等,但是原因都是key或者IV错误,所以解密失败第一时间还是要检查key跟IV对不对。

3、加密的时候可以选择PKCS7填充或者PKCS5填充,但是解密的时候要使用PKCS7,因为PKCS7兼容PKCS5。

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
EVP_aes_256_cbcOpenSSL库提供的一种加密算法,可以用于对大文件进行加密和解密。下面是c++实现EVP_aes_256_cbc文件加密与解密的示例代码: ```c++ #include <openssl/evp.h> #include <iostream> #include <fstream> using namespace std; int aes_encrypt(const char *key, const char *iv, const char *infile, const char *outfile) { EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if (!ctx) { cerr << "Error: EVP_CIPHER_CTX_new() failed" << endl; return -1; } // 初始化加密上下文 if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (const unsigned char *)key, (const unsigned char *)iv) != 1) { cerr << "Error: EVP_EncryptInit_ex() failed" << endl; EVP_CIPHER_CTX_free(ctx); return -1; } // 打开输入文件 ifstream fin(infile, ios::binary); if (!fin.is_open()) { cerr << "Error: Cannot open input file" << endl; EVP_CIPHER_CTX_free(ctx); return -1; } // 打开输出文件 ofstream fout(outfile, ios::binary); if (!fout.is_open()) { cerr << "Error: Cannot create output file" << endl; fin.close(); EVP_CIPHER_CTX_free(ctx); return -1; } // 读取输入文件并加密 const int BUFSIZE = 1024 * 1024; unsigned char inbuf[BUFSIZE]; unsigned char outbuf[BUFSIZE + EVP_CIPHER_block_size(EVP_aes_256_cbc())]; int inlen, outlen; while (!fin.eof()) { fin.read((char *)inbuf, BUFSIZE); inlen = fin.gcount(); if (EVP_EncryptUpdate(ctx, outbuf, &outlen, inbuf, inlen) != 1) { cerr << "Error: EVP_EncryptUpdate() failed" << endl; fin.close(); fout.close(); EVP_CIPHER_CTX_free(ctx); return -1; } fout.write((char *)outbuf, outlen); } // 结束加密 if (EVP_EncryptFinal_ex(ctx, outbuf, &outlen) != 1) { cerr << "Error: EVP_EncryptFinal_ex() failed" << endl; fin.close(); fout.close(); EVP_CIPHER_CTX_free(ctx); return -1; } fout.write((char *)outbuf, outlen); // 关闭文件和加密上下文 fin.close(); fout.close(); EVP_CIPHER_CTX_free(ctx); return 0; } int aes_decrypt(const char *key, const char *iv, const char *infile, const char *outfile) { EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if (!ctx) { cerr << "Error: EVP_CIPHER_CTX_new() failed" << endl; return -1; } // 初始化解密上下文 if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (const unsigned char *)key, (const unsigned char *)iv) != 1) { cerr << "Error: EVP_DecryptInit_ex() failed" << endl; EVP_CIPHER_CTX_free(ctx); return -1; } // 打开输入文件 ifstream fin(infile, ios::binary); if (!fin.is_open()) { cerr << "Error: Cannot open input file" << endl; EVP_CIPHER_CTX_free(ctx); return -1; } // 打开输出文件 ofstream fout(outfile, ios::binary); if (!fout.is_open()) { cerr << "Error: Cannot create output file" << endl; fin.close(); EVP_CIPHER_CTX_free(ctx); return -1; } // 读取输入文件解密 const int BUFSIZE = 1024 * 1024 + EVP_CIPHER_block_size(EVP_aes_256_cbc()); unsigned char inbuf[BUFSIZE]; unsigned char outbuf[BUFSIZE]; int inlen, outlen; while (!fin.eof()) { fin.read((char *)inbuf, BUFSIZE); inlen = fin.gcount(); if (EVP_DecryptUpdate(ctx, outbuf, &outlen, inbuf, inlen) != 1) { cerr << "Error: EVP_DecryptUpdate() failed" << endl; fin.close(); fout.close(); EVP_CIPHER_CTX_free(ctx); return -1; } fout.write((char *)outbuf, outlen); } // 结束解密 if (EVP_DecryptFinal_ex(ctx, outbuf, &outlen) != 1) { cerr << "Error: EVP_DecryptFinal_ex() failed" << endl; fin.close(); fout.close(); EVP_CIPHER_CTX_free(ctx); return -1; } fout.write((char *)outbuf, outlen); // 关闭文件解密上下文 fin.close(); fout.close(); EVP_CIPHER_CTX_free(ctx); return 0; } int main() { const char *key = "0123456789abcdef0123456789abcdef"; const char *iv = "0123456789abcdef"; const char *infile = "input.txt"; const char *outfile = "output.txt"; // 加密 if (aes_encrypt(key, iv, infile, outfile) != 0) { cerr << "Error: Encryption failed" << endl; return -1; } cout << "Encryption succeeded" << endl; // 解密 if (aes_decrypt(key, iv, outfile, "input_decrypted.txt") != 0) { cerr << "Error: Decryption failed" << endl; return -1; } cout << "Decryption succeeded" << endl; return 0; } ``` 这个示例代码中,我们使用OpenSSL库提供的EVP_aes_256_cbc加密算法,对给定的输入文件进行加密和解密。在实际使用中,你需要根据自己的需求修改参数,比如加密算法、密钥、向量、输入文件和输出文件等。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值