使用c ++ / openssl使用密码解密文件

时间:2019-07-10 22:37:21

标签: c++ linux encryption openssl

我正在尝试解密先前使用c ++和openssl中的相同代码加密的文件。不幸的是,我遇到了段错误,无法找出确切的问题。

我能够加密图像文件,但是在尝试对其解密时会出现段错误。

似乎问题出在此功能上:

void file_encrypt_decrypt(cipher_params_t *params, FILE *ifp, FILE *ofp)
{
    /* Allow enough space in output buffer for additional block */
    int cipher_block_size = EVP_CIPHER_block_size(params->cipher_type);
    unsigned char in_buf[BUFSIZE], out_buf[BUFSIZE + cipher_block_size];

    int num_bytes_read =  0, out_len = 0;
    EVP_CIPHER_CTX *ctx;

    ctx = EVP_CIPHER_CTX_new();
    if(ctx == NULL){
        fprintf(stderr, "ERROR: EVP_CIPHER_CTX_new failed. OpenSSL error: %s\n", 
                ERR_error_string(ERR_get_error(), NULL));
        cleanup(params, ifp, ofp, ERR_EVP_CTX_NEW);
    }

    /* Don't set key or IV right away; we want to check lengths */
    if(!EVP_CipherInit_ex(ctx, params->cipher_type, NULL, NULL, NULL, params->encrypt)){
        fprintf(stderr, "ERROR: EVP_CipherInit_ex failed. OpenSSL error: %s\n", 
                ERR_error_string(ERR_get_error(), NULL));
        cleanup(params, ifp, ofp, ERR_EVP_CIPHER_INIT);
    }

    OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == AES_256_KEY_SIZE);
    OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == AES_BLOCK_SIZE);

    /* Now we can set key and IV */
    if(!EVP_CipherInit_ex(ctx, NULL, NULL, params->key, params->iv, params->encrypt)){
        fprintf(stderr, "ERROR: EVP_CipherInit_ex failed. OpenSSL error: %s\n", 
                ERR_error_string(ERR_get_error(), NULL));
        EVP_CIPHER_CTX_cleanup(ctx);
        cleanup(params, ifp, ofp, ERR_EVP_CIPHER_INIT);
    }

    while(1){
        // Read in data in blocks until EOF. Update the ciphering with each read.
        num_bytes_read = fread(in_buf, sizeof(unsigned char), BUFSIZE, ifp);

        if (ferror(ifp)){
            fprintf(stderr, "ERROR: fread error: %s\n", strerror(errno));
            EVP_CIPHER_CTX_cleanup(ctx);
            cleanup(params, ifp, ofp, errno);
        }
        if(!EVP_CipherUpdate(ctx, out_buf, &out_len, in_buf, num_bytes_read)){
            fprintf(stderr, "ERROR: EVP_CipherUpdate failed. OpenSSL error: %s\n", 
                    ERR_error_string(ERR_get_error(), NULL));
            EVP_CIPHER_CTX_cleanup(ctx);
            cleanup(params, ifp, ofp, ERR_EVP_CIPHER_UPDATE);
        }
        fwrite(out_buf, sizeof(unsigned char), out_len, ofp);
        if (ferror(ofp)) {
            fprintf(stderr, "ERROR: fwrite error: %s\n", strerror(errno));
            EVP_CIPHER_CTX_cleanup(ctx);
            cleanup(params, ifp, ofp, errno);
        }
        if (num_bytes_read < BUFSIZE) {
            /* Reached End of file */
            break;
        }
    }

    /* Now cipher the final block and write it out to file */
    if(!EVP_CipherFinal_ex(ctx, out_buf, &out_len)){
        fprintf(stderr, "ERROR: EVP_CipherFinal_ex failed. OpenSSL error: %s\n", 
                ERR_error_string(ERR_get_error(), NULL));
        EVP_CIPHER_CTX_cleanup(ctx);
        cleanup(params, ifp, ofp, ERR_EVP_CIPHER_FINAL);
    }
    fwrite(out_buf, sizeof(unsigned char), out_len, ofp);
    if (ferror(ofp)) {
        fprintf(stderr, "ERROR: fwrite error: %s\n", strerror(errno));
        EVP_CIPHER_CTX_cleanup(ctx);
        cleanup(params, ifp, ofp, errno);
    }
    EVP_CIPHER_CTX_cleanup(ctx);
}

我怀疑密钥长度不一样,问题出在这些方面:

OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == AES_256_KEY_SIZE);
OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == AES_BLOCK_SIZE);

输入的密码在main()中使用(请参见下文):

if (EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha256(), (unsigned char*)salt, (unsigned char*)pwd, sizeof(pwd), 1, params->key, params->iv) == 0) {
        perror("Error: EVP_BytesToKey\n");
    }

为了完整起见,这是一个加密文件的有效示例(不包括上面的函数(void file_encrypt_decrypt)):

#define _XOPEN_SOURCE
#include <crypt.h>
#include <errno.h>
#include <unistd.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <string>
#include <sstream>
#include <iomanip>
#include <fstream>
#include <vector>
#include <cstdint>

#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/aes.h>
#include <openssl/rand.h>
#include <openssl/sha.h>

#define ERR_EVP_CIPHER_INIT     -1
#define ERR_EVP_CIPHER_UPDATE   -2
#define ERR_EVP_CIPHER_FINAL    -3
#define ERR_EVP_CTX_NEW         -4

#define AES_256_KEY_SIZE        32 
#define AES_BLOCK_SIZE          16
#define BUFSIZE                 1024

typedef struct _cipher_params_t {
    unsigned char *key;
    unsigned char *iv;
    unsigned int encrypt;
    const EVP_CIPHER *cipher_type;
} cipher_params_t;

void cleanup(cipher_params_t *params, FILE *ifp, FILE *ofp, int rc)
{
    free(params);
    fclose(ifp);
    fclose(ofp);
    exit(rc);
}

std::string sha256(const std::string str)
{
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256_CTX sha256;
    SHA256_Init(&sha256);
    SHA256_Update(&sha256, str.c_str(), str.size());
    SHA256_Final(hash, &sha256);
    std::stringstream ss;
    for(int i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
        ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
    }
    return ss.str();
}

char *take_pwd(char *salt)
{
    int MAX_SIZE = 80;
    char password[MAX_SIZE];
    char *encrypted_password;
    int i = 0;
    char c;
    printf("Enter password: ");
    while ((c = getchar()) != '\n' && i < MAX_SIZE) {
        password[i++] = c;
    }
    std::string salt_str = sha256(salt);
    encrypted_password = crypt(password, const_cast<char *>(salt_str.c_str()));
    encrypted_password[MAX_SIZE] = '\0';
    return encrypted_password;
}

int main(int argc, char *argv[]) 
{
    FILE *f_input, *f_enc, *f_dec;

    /* Make sure user provides the input file */
    if (argc != 3) {
        printf("Usage: %s /path/to/file + 0 (decrypt) or 1 (encrypt)\n", argv[0]);
        return -1;
    }

    cipher_params_t *params = (cipher_params_t *)malloc(sizeof(cipher_params_t));
    if (!params) {
        /* Unable to allocate memory on heap*/
        fprintf(stderr, "ERROR: malloc error: %s\n", strerror(errno));
        return errno;
    }

    /* Key to use for encrpytion and decryption */
    unsigned char key[AES_256_KEY_SIZE];

    /* Initialization Vector */
    unsigned char iv[AES_BLOCK_SIZE];

    char salt[] = "$1$........";
    char *pwd = take_pwd(salt);

    if (EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha256(), (unsigned char*)salt, (unsigned char*)pwd, sizeof(pwd), 1, params->key, params->iv) == 0) {
        perror("Error: EVP_BytesToKey\n");
    }

    params->key = key;
    params->iv = iv;

    char *file_name = argv[1];

    if (*argv[2] == '1') { 

        printf("\nEncrypting File\n");

        /* Indicate that we want to encrypt */
        params->encrypt = 1;

        /* Set the cipher type you want for encryption-decryption */
        params->cipher_type = EVP_aes_256_cbc();

        /* Open the input file for reading in binary ("rb" mode) */
        f_input = fopen(argv[1], "rb");
        if (!f_input) {
            /* Unable to open file for reading */
            fprintf(stderr, "ERROR: fopen error: %s\n", strerror(errno));
            return errno;
        }

        /* Open and truncate file to zero length or create ciphertext file for writing */
        std::string name_of_file = file_name;
        std::string encrypted = "_encrypted_";
        std::size_t position_of_dot = name_of_file.find_last_of(".");
        if (position_of_dot != std::string::npos) {
            name_of_file.insert(position_of_dot, encrypted);
        }

        f_enc = fopen(name_of_file.c_str(), "wb");
        if (!f_enc) {
            /* Unable to open file for writing */
            fprintf(stderr, "ERROR: fopen error: %s\n", strerror(errno));
            return errno;
        }

        /* Encrypt the given file */
        file_encrypt_decrypt(params, f_input, f_enc);

        /* Encryption done, close the file descriptors */
        fclose(f_input);
        fclose(f_enc);
    } else if (*argv[2] == '0') {

        printf("\nDecrypting File\n");

        /* Decrypt the file */
        /* Indicate that we want to decrypt */
        params->encrypt = 0;

        /* Open the encrypted file for reading in binary ("rb" mode) */

        printf("name_encrypted: %s\n", file_name);
        f_input = fopen(file_name, "rb");

        if (!f_input) {
            /* Unable to open file for reading */
            fprintf(stderr, "ERROR: fopen error: %s\n", strerror(errno));
            return errno;
        }

        /* Open and truncate file to zero length or create decrypted file for writing */
        f_dec = fopen("decrypted_file", "wb");
        if (!f_dec) {
            /* Unable to open file for writing */
            fprintf(stderr, "ERROR: fopen error: %s\n", strerror(errno));
            return errno;
        }   

        /* Decrypt the given file */
        file_encrypt_decrypt(params, f_input, f_dec);

        /* Close the open file descriptors */
        fclose(f_input);
        fclose(f_dec);
    }
    /* Free the memory allocated to our structure */
    free(params);
    return 0;
}

我这样编译并执行了用于加密的代码(arg 1用于加密):

g++ -Wall -std=c++11 encrypt.cpp -o encrypt -lssl -lcrypto -lcrypt
./encrypt "~/path/to/image/file.jpg" 1

并用于解密(0):

./encrypt "~/path/to/image/file_encrypted_.jpg" 0

segfault似乎出现在void file_encrypt_decrypt函数的第一行:

EVP_CIPHER_block_size () from target:/usr/local/lib/libcrypto.so.1.1

1 个答案:

答案 0 :(得分:1)

在解密过程中,这是您在GDB下的程序:

(gdb) r NEWS 0
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Enter password: test

Decrypting File
name_encrypted: NEWS

Program received signal SIGSEGV, Segmentation fault.
0x000000000040ee20 in EVP_CIPHER_block_size ()
(gdb) bt full
#0  0x000000000040ee20 in EVP_CIPHER_block_size ()
No symbol table info available.
#1  0x000000000040447d in file_encrypt_decrypt (params=0x67be70, ifp=0x685e50,
    ofp=0x686080) at test.c:81
        cipher_block_size = 0x0
        in_buf = "\022\000;9Z\310\333T\354\251bH\361\253:\203Q\001\312\063ZL\225\003p2f\000\000\000\000\000\340\322\377\377\377\177", '\000' <repeats 18 times>, "\240Ec", '\000' <repeats 13 times>, "\347\026\376\367\377\177\000\000\001", '\000' <repeats 15 times>, "\360\226\243T9\035\215\066\bQ\243\367\377\177\000\000\320\321\377\377\377\177\000\000\236\203\376\367\377\177\000\000\021\327\377\377\377\177\000\000\000\000\000\000\000\000\000\000@ע\367\377\177\000\000GrZ\000\000\000\000\000\021\327\377\377\377\177\000\000\377\377\377\377\000\000\000\000\025\000\000\000\000\000\000\000p\001\001\000\000\000\000\000\320,j\000\000\000\000\000`"...
        out_buf = <error reading variable out_buf (value requires 6840449 bytes, which is more than max-value-size)>
        num_bytes_read = 0x0
        out_len = 0xf6af024b
        ctx = 0x686080
#2  0x0000000000404e1f in main (argc=0x3, argv=0x7fffffffd3c8) at test.c:257
        f_input = 0x685e50
        f_enc = 0x7fffffffd3e8
        f_dec = 0x686080

然后,检出第1帧。只是OpenSSL库代码因错误的参数而崩溃:

(gdb) frame 1
#1  0x000000000040447d in file_encrypt_decrypt (params=0x67be70, ifp=0x685e50,
    ofp=0x686080) at test.c:81
81          int cipher_block_size = EVP_CIPHER_block_size(params->cipher_type);
(gdb) p params
$1 = (cipher_params_t *) 0x67be70
(gdb) p params->cipher_type
$2 = (const EVP_CIPHER *) 0x0

您的密码对象为NULL。您需要创建一个密码对象并将其分配给params。或者您需要向EVP_CIPHER_block_size提供一个真实的对象。


这是您在加密期间正在做的事情:

190         if (*argv[2] == '1') {
(gdb)
192             printf("\nEncrypting File\n");
(gdb)

Encrypting File
195             params->encrypt = 1;
(gdb)
198             params->cipher_type = EVP_aes_256_cbc();

我猜想解密丢失了对params->cipher_type = EVP_aes_256_cbc();的呼叫。

您应该将其从if/else块中取出,以便加密和解密都可以执行它。

/* Set the cipher type you want for encryption-decryption */
params->cipher_type = EVP_aes_256_cbc();