需要使用代碼對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
以上命令中
- “-p”參數用來指定輸出加密時使用的key和iv。
- “-e”和”-d”參數用來指定加密還是解密。
- “-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裏的和stackoverflow 上的問答。
//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。