openssl EVP_DecryptFinal_ex 错误

狂风暴雨 2016-04-16 06:57:56
在做chrome cookie Decrypt的功能小工具。
目前遇到问题EVP_DecryptFinal_ex 的时候经常返回值为0。
知道是padding的问题,但是不知道到底该如何处理这个padding。有大神指导一下吗。



#include <iostream>
#include <string>

#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/aes.h>
#include <openssl/rand.h>
#include <openssl/hmac.h>
#include <openssl/engine.h>

#include "sqlite3.h"

using namespace std;

const char kSalt[] = "saltysalt";
const int kDerivedKeySizeInBits = 128;
const int kEncryptionIterations = 1003;
const char kEncryptionVersionPrefix[] = "v10";

struct Param {
string pass;
string host;
string key;
};

static std::string &rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
return s;
}

bool deriveKeyFromPassword(const char *password, int pass_len, const unsigned char *salt, int salt_len, unsigned char *out) {
if(PKCS5_PBKDF2_HMAC_SHA1(password, pass_len, salt, salt_len, kEncryptionIterations, kDerivedKeySizeInBits/8, out) != 0) {
return true;
} else {
cout << "0.PKCS5_PBKDF2_HMAC_SHA1 failed\n";
return false;
}
}

int decrypt(const unsigned char *ciphertext, int ciphertext_len, const unsigned char *key, const unsigned char *iv, unsigned char *plaintext) {
EVP_CIPHER_CTX *ctx;

int len;
int plaintext_len = -1;

if(!(ctx = EVP_CIPHER_CTX_new())) {
ERR_print_errors_fp(stderr);
return -1;
}

if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) {
ERR_print_errors_fp(stderr);
return -1;
}
// EVP_CIPHER_CTX_set_padding(ctx, 0);

if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) {
ERR_print_errors_fp(stderr);
goto CLEARUP;
}

plaintext_len = len;
if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) {
ERR_print_errors_fp(stderr);
plaintext_len = -1;
goto CLEARUP;
}
plaintext_len += len;

CLEARUP:
EVP_CIPHER_CTX_free(ctx);

return plaintext_len;
}

bool chrome_decrypt(const string& password, const string &enc_value, string *dec_value) {
if (enc_value.find(kEncryptionVersionPrefix) != 0) {
cout << "invalid encrypted data\n";
return false;
}

string raw_enc_value = enc_value.substr(strlen(kEncryptionVersionPrefix));

unsigned char iv[AES_BLOCK_SIZE] = {0};
memset(iv, ' ', AES_BLOCK_SIZE);

unsigned char *decryptedtext = new unsigned char[raw_enc_value.size() + 64];
int decryptedtext_len = 0;
bool ret = false;

unsigned char aes_key[kDerivedKeySizeInBits/8] = {0};
if (deriveKeyFromPassword(password.c_str(), password.size(), (unsigned char *)kSalt, (int)strlen(kSalt), aes_key)) {
decryptedtext_len = decrypt((const unsigned char *)raw_enc_value.c_str(), raw_enc_value.size(), aes_key, iv, decryptedtext);
if (decryptedtext_len > 0) {
*dec_value = string((char *)decryptedtext, decryptedtext_len);
ret = true;
}
}

delete[] decryptedtext;

return ret;
}

int db_callback(void* param, int row_count, char** argv, char** col_name) {
Param* _param = (Param*)param;
if (!_param->host.empty() && argv[1] != _param->host)
return 0;

bool show_detail = _param->key.empty();

for(int i = 0; i < row_count; i++) {
if (show_detail) {
if (i == 12) {
string value = "";
chrome_decrypt(_param->pass, argv[i], &value);
cout << col_name[i] << ": " << rtrim(value) << "\n";
} else {
cout << col_name[i] << ": " << argv[i] << "\n";
}
} else {
if (argv[2] == _param->key && i == 12) {
string value = "";
chrome_decrypt(_param->pass, argv[i], &value);
cout << rtrim(value) << value.length() << "\n";
}
}
}
if (show_detail)
cout << "--------------------------------------------\n";
return 0;
}

void read_db(const string& db_path, const string& password, const string& host, const string& key) {
sqlite3* handle = NULL;
sqlite3_open(db_path.data(), &handle);

if (handle == NULL) {
cout << "打开cookie数据库失败!";
return;
}

sqlite3_stmt* stmp = NULL;

Param param;
param.pass = password;
param.host = host;
param.key = key;

char* error = NULL;
string sql = "select * from cookies";
sqlite3_exec(handle, sql.data(), db_callback, (void*)(¶m), &error);
sqlite3_close(handle);
}

int main(int argc, const char * argv[]) {
// read_db("/Users/bitebit/Library/Application Support/Google/Chrome/Default/Cookies", "F3/F95DvICIA==", "", "");
// read_db("/Users/bitebit/Library/Application Support/Google/Chrome/Default/Cookies", "F3/F95DvICIA==", "10.2.69.69", "");
// read_db("/Users/bitebit/Library/Application Support/Google/Chrome/Default/Cookies", "F3/F95DvICIA==", "10.2.69.69", "LBCLUSTERID");

if (argc < 3) {
cout << "使用方法: \n\t decryptor cookie文件路径 chrome钥匙串 [cookie的host] [cookie的名字]\n" << endl;
cout << "Mac下获取chrome钥匙串: \n\t security find-generic-password -w -s \"Chrome Safe Storage\"" << endl;
return 1;
}

if (argc == 5)
read_db(argv[1], argv[2], argv[3], argv[4]);
else if (argc == 4)
read_db(argv[1], argv[2], argv[3], "");
else if (argc == 3)
read_db(argv[1], argv[2], "", "");

return 0;
}

...全文
2509 4 打赏 收藏 转发到动态 举报
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
cocoabird 2016-04-17
  • 打赏
  • 举报
回复
/*
 * key:加密密钥,一般设置位24,不知为啥
 * iv:加密初始向量
 * in_dec:密文数组,输入数组
 * out_dec:解密后的数组,输出数组
 * in_len:密文长度
 * out_len:明文长度
 * */
//解密函数
int DecryptBuffer(unsigned char * key,unsigned char *iv,unsigned char * in_dec, unsigned char *out_dec,int in_len,int *out_len)
{
 int outl;  //第一次使用update解密的数据长度
 int outl2; //剩余的字段,经过final解密并去除填充后的长度
 int rv;

 EVP_CIPHER_CTX ctx;
 //初始化ctx
 EVP_CIPHER_CTX_init(&ctx);
 //设置解密的算法、key和iv
 rv = EVP_DecryptInit_ex(&ctx,EVP_des_ede3_ecb(),NULL,key,iv);
 if(rv != 1)
 {
  EVP_CIPHER_CTX_cleanup(&ctx);
  return -1;
 }

 //循环读取原文,解密后后保存到明文文件。
 rv = EVP_DecryptUpdate(&ctx,out_dec,&outl,in_dec,in_len);//解密
 if(rv != 1)
 {
  EVP_CIPHER_CTX_cleanup(&ctx);
  return -1;
 }

 //解密结束
 rv = EVP_DecryptFinal_ex(&ctx,out_dec+outl,&outl2);

  if(rv != 1)
 {
  EVP_CIPHER_CTX_cleanup(&ctx);
  return -1;
 }
 *out_len=outl+outl2;
 EVP_CIPHER_CTX_cleanup(&ctx);//清除EVP加密上下文环境
 printf("解密已完成\n");
}
狂风暴雨 2016-04-17
  • 打赏
  • 举报
回复
那到底解密时该怎么做呢。 都是说的加密啊
引用 2 楼 yuelengdihai 的回复:
此函数调用失败的原因很多, 理解函数描述及使用细节很关键。   1、函数原型:   int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out,   int *outl);   2、函数描述:   该函数处理最后(Final)的一段数据。在函数在padding功能打开的时候(缺省)才有效,这时候,它将剩余的最后的所有数据进行加密处理。该算法使用标志的块padding方式(AKA PKCS padding)。加密后的数据写入到参数out里面,参数out的长度至少应该能够一个加密块。写入的数据长度信息输入到outl参数里面。该函数调用后,表示所有数据都加密完了,不应该再调用EVP_EncryptUpdate函数。如果没有设置padding功能,那么本函数不会加密任何数据,如果还有剩余的数据,那么就会返回错误信息,也就是说,这时候数据总长度不是块长度的整数倍。操作成功返回1,否则返回0。   PKCS padding标准是这样定义的,在被加密的数据后面加上n个值为n的字节,使得加密后的数据长度为加密块长度的整数倍。无论在什么情况下,都是要加上padding的,也就是说,如果被加密的数据已经是块长度的整数倍,那么这时候n就应该等于块长度。比如,如果块长度是9,要加密的数据长度是11,那么5个值为5的字节就应该增加在数据的后面。
cocoabird 2016-04-17
  • 打赏
  • 举报
回复
此函数调用失败的原因很多, 理解函数描述及使用细节很关键。   1、函数原型:   int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out,   int *outl);   2、函数描述:   该函数处理最后(Final)的一段数据。在函数在padding功能打开的时候(缺省)才有效,这时候,它将剩余的最后的所有数据进行加密处理。该算法使用标志的块padding方式(AKA PKCS padding)。加密后的数据写入到参数out里面,参数out的长度至少应该能够一个加密块。写入的数据长度信息输入到outl参数里面。该函数调用后,表示所有数据都加密完了,不应该再调用EVP_EncryptUpdate函数。如果没有设置padding功能,那么本函数不会加密任何数据,如果还有剩余的数据,那么就会返回错误信息,也就是说,这时候数据总长度不是块长度的整数倍。操作成功返回1,否则返回0。   PKCS padding标准是这样定义的,在被加密的数据后面加上n个值为n的字节,使得加密后的数据长度为加密块长度的整数倍。无论在什么情况下,都是要加上padding的,也就是说,如果被加密的数据已经是块长度的整数倍,那么这时候n就应该等于块长度。比如,如果块长度是9,要加密的数据长度是11,那么5个值为5的字节就应该增加在数据的后面。
狂风暴雨 2016-04-17
  • 打赏
  • 举报
回复
木有人知道该怎么处理么
0、此例程调试环境 运行uname -a的结果如下: Linux freescale 3.0.35-2666-gbdde708-g6f31253 #1 SMP PREEMPT Thu Nov 30 15:45:33 CST 2017 armv7l GNU/Linux 简称2017 armv7l GNU/Linux 1、openssl 直接处理AES的API 在openssl/aes.h定义。是基本的AES库函数接口,可以直接调用,但是那个接口是没有填充的。而如果要与Java通信,必须要有填充模式。所以看第2条。 2、利用openssl EVP接口 在openssl/evp.h中定义。在这个接口中提供的AES是默认是pkcs5padding方式填充方案。 3、注意openssl新老版本的区别 看如下这段 One of the primary differences between master (OpenSSL 1.1.0) and the 1.0.2 version is that many types have been made opaque, i.e. applications are no longer allowed to look inside the internals of the structures. The biggest impact on applications is that: 1)You cannot instantiate these structures directly on the stack. So instead of: EVP_CIPHER_CTX ctx; you must instead do: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); .... EVP_CIPHER_CTX_free(ctx); 2)You must only use the provided accessor functions to access the internals of the structure. 4、注意加密的内容是数据不限制是否为字符串 openssl接口加密的是数据,不限制是否为字符串,我看到有些人在加密时使用strlen(),来获取要加密的长度,如果是对字符串加密的话没有问题,如果不是字符串的话,用它获取的长度是到第一个0处,因为这个函数获取的是字符串长度,字符串是以零为终止的。 5、在调用EVP_EncryptFinal_ex时不要画蛇添足 正常加解密处理过程,引用网友的代码如下,经测试正确。 int kk_encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key, unsigned char *iv, unsigned char *ciphertext) { EVP_CIPHER_CTX *ctx; int len; int ciphertext_len; ctx = EVP_CIPHER_CTX_new(); EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); //EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv); EVP_EncryptUpdate(ctx, ciphertext, &len;, plaintext, plaintext_len); ciphertext_len = len; EVP_EncryptFinal_ex(ctx, ciphertext + len, &len;); ciphertext_len += len; EVP_CIPHER_CTX_free(ctx); return ciphertext_len; } int kk_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *iv, unsigned char *plaintext) { EVP_CIPHER_CTX *ctx; int len; int plaintext_len; ctx = EVP_CIPHER_CTX_new(); EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); //EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv); EVP_DecryptUpdate(ctx, plaintext, &len;, ciphertext, ciphertext_len); plaintext_len = len; EVP_DecryptFinal_ex(ctx, plaintext + len, &len;); plaintext_len += len; EVP_CIPHER_CTX_free(ctx); return plaintext_len; } 我看到有人提供的代码在加密长度正好是16字节的整数倍时特意不去调用EVP_EncryptFinal_ex,这实在是画蛇添足啊,不论什么情况下,最后一定要调用EVP_EncryptFinal_ex一次,然后结束加密过程。 6、Base64陷阱 如果用到了base64,要注意如下: 1)base64算法是将3个字节变成4个可显示字符。所以在如果数据长度不是3字节对齐时,会补0凑齐。 2)在解密时先要解base64,再解AES。在解base64后,要减掉补上的0。算法就去查看base64后的字符串尾处有几个=号,最多是2个,如果正好要加密的数据是3的倍数,不需要补0,那么base64后的数据尾处就没有=,如果补了1个0,就有一个=号。 算法如下: int encode_str_size = EVP_EncodeBlock(base64, en, el); int length = EVP_DecodeBlock(base64_out, base64, encode_str_size ); //EVP_DecodeBlock内部同样调用EVP_DecodeInit + EVP_DecodeUpdate + Evp_DecodeFinal实现,但是并未处理尾部的'='字符,因此结果字符串长度总是为3的倍数 while(base64[--encode_str_size] == '=') length--; 算法网友提供,测试正确。

24,854

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 工具平台和程序库
社区管理员
  • 工具平台和程序库社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧