欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

使用C语言,对OpenSSL命令行加密的文件进行解密

程序员文章站 2022-07-12 22:42:33
...

需要使用代码对shell命令加密的文件进行解密需要阅读本文。

OpenSSL提供shell命令,以下命令可以对文件进行加密、解密。:

> openssl enc -e -aes-128-cbc -k 123456 -p -nosalt -in inputfile.txt -out encryptfile.txt
> openssl enc -d -aes-128-cbc -k 123456 -p -nosalt -in encryptfile.txt -out tmp.txt

以上命令中

  1. “-p”参数用来指定输出加密时使用的key和iv。
  2. “-e”和”-d”参数用来指定加密还是解密。
  3. “-nosalt”指定不使用加盐算法,默认使用。

对于上述命令加密的文件,如果想写程序解密,略微复杂。
openssl: recover key and IV by passphrase的这个讨论中,提及了openssl enc的key是通过加盐和散列后生成的,内部实现使用了EVP_BytesToKey() 函数,但是其特性并不被文档所规范,也就是说版本变更后有可能改变。(就是这么任性,捂脸。。。)

OpenSSL的EVP框架,提供了各种算法的一个更高层次抽象,不同加密算法共用一组函数接口,推荐使用。
EVP系列函数,一眼忘过去密密麻麻一堆,参数也复杂。但是如果是对称加密算法,使用以下三个函数就够了。

 int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, ENGINE *impl, unsigned char *key, unsigned char *iv, int enc);
int EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,int *outl, unsigned char *in, int inl);
int EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);

对于EVP_CipherInit_ex函数的参数

  • ctx,通过 EVP_CIPHER_CTX_new()获得
  • type, EVP_get_cipherbyname(algorithm)获得,algorithm可以是各种加密算法那,如aes-128-cbc
  • impl, 使用内置算法,传NULL
  • key和iv可以自己指定,本文是通过EVP_BytesToKey() 获得
  • enc,0表示解密,1表示加密,-1表示保持不变。

下面的代码,可以对已打开的加密文件的句柄,进行解码,并将结果保存到pp_out指向的内存中。
结合了官方的manpage里的和* 上的问答。

//IN key: password for decrypt 
//IN algorithm: e.g. "aes-128-cbc"
//OUT pp_out: decrypt buffer
//Return: >0 Decrypt buffer length, 0 Error
int decrypt_open(FILE* in,const unsigned char* key, const char* algorithm,unsigned char**pp_out)
{

    unsigned char inbuf[MAX_CONFIG_LINE];
    int inlen, out_blk_len=0;
    int out_buff_len=0,buff_offset=0;
    EVP_CIPHER_CTX *ctx;

    unsigned char cipher_key[EVP_MAX_KEY_LENGTH];
    unsigned char cipher_iv[EVP_MAX_IV_LENGTH];
    memset(cipher_key,0,sizeof(cipher_key));
    memset(cipher_iv,0,sizeof(cipher_iv));

    const EVP_CIPHER *cipher;
    const EVP_MD *dgst=NULL;
    const unsigned char *salt=NULL;
    int ret=0;

    OpenSSL_add_all_algorithms();
    cipher=EVP_get_cipherbyname(algorithm);
    if(cipher==NULL)
    {
        printf("Not cipher:%s not supported.\n\n");
        return 0;
    }
    dgst=EVP_get_digestbyname("md5");
    if(dgst==NULL)
    {
        printf("Get MD5 object failed.");
        return 0;
    }
    ret=EVP_BytesToKey(cipher,dgst,salt,key,strlen((const char*)key),1,cipher_key,cipher_iv);
    if(ret==0)
    {
        printf("Key and IV generatioin failed.\n");
        return 0;
    }
     /* Don't set key or IV right away; we want to check lengths */
    ctx = EVP_CIPHER_CTX_new();
    EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL,0);
    OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 16);
    OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == 16);

    /* Now we can set key and IV */
    EVP_CipherInit_ex(ctx, NULL, NULL, cipher_key, cipher_iv, 0);//The last parameter, 0 decrypt, 1 encrypt.
    out_buff_len=16*1024;
    *pp_out=(unsigned char*)malloc(out_buff_len*sizeof(unsigned char));
    for (;;) 
    {
        inlen = fread(inbuf, 1, MAX_CONFIG_LINE, in);
        if (inlen <= 0)
            break;
        //Note: EVP_CipherUpdate does not check output buffer size, make sure that you have enough space, or it may overflow.
        if(out_buff_len-buff_offset<inlen+EVP_CIPHER_block_size(cipher)-1)
        {
            out_buff_len*=2;
            *pp_out=(unsigned char*)realloc(*pp_out,out_buff_len);
        }
        out_blk_len=out_buff_len-buff_offset;
        if (!EVP_CipherUpdate(ctx, *pp_out+buff_offset, &out_blk_len, inbuf, inlen))
        {
            printf("EVP_CipherUpdate failed.\n");
            EVP_CIPHER_CTX_free(ctx);
            goto error_out;
        }
        buff_offset+=out_blk_len;

    }
    if (!EVP_CipherFinal_ex(ctx, *pp_out+buff_offset, &out_blk_len))
    {
        printf("EVP_CipherFinal_ex failed.\n");
        EVP_CIPHER_CTX_free(ctx);
        goto error_out;
    }
    buff_offset+=out_blk_len;
    EVP_CIPHER_CTX_free(ctx);
    return buff_offset;
error_out:
    free(*pp_out);
    *pp_out=NULL;
    return 0;
}

EVP_BytesToKey() 这个函数将输入的字符串key转换为加密用的cipher_key和cipher_iv。
提醒大家注意的是,命令行工具 “openssl enc”,在 OpenSSL v1.0.0的实现中,使用的摘要算法是MD5,到v1.1.0,更换为SHA256。