2 kfbyj kfbyj 于 2016.04.14 21:03 提问

openssl EVP_DecryptFinal_ex 错误 100C

在做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*)(&param), &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;
}


2个回答

devmiao
devmiao   Ds   Rxr 2016.04.14 23:31

openssl之EVP系列之4---EVP_Encrypt系列函数详解(一)
---根据openssl doc/crypto/EVP_EncryptInit.pod和doc/ssleay.txt cipher.doc部分翻译和自己的理解写成
(作者:DragonKing, Mail: wzhah@263.net ,发布于:http://openssl.126.com 之openssl专业论坛,版本:openssl-0.9.7)
EVP_Cipher系列包含了很多函数,我将他们大概分成两部分来介绍,一部分是基本函数系列,就是本文要介绍的,另一个部分是设置函数系列,将在后面的文章进行介绍。基本系列函数主要是进行基本的加密和解密操作的函数,他们的定义如下(openssl/evp.h):
int EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *a);

 int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
 ENGINE *impl, unsigned char *key, unsigned char *iv);
 int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
 int *outl, unsigned char *in, int inl);
 int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out,
 int *outl);

 int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
 ENGINE *impl, unsigned char *key, unsigned char *iv);
 int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
 int *outl, unsigned char *in, int inl);
 int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm,
 int *outl);

 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);

 int EVP_EncryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
 unsigned char *key, unsigned char *iv);
 int EVP_EncryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *out,
 int *outl);

 int EVP_DecryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
 unsigned char *key, unsigned char *iv);
 int EVP_DecryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *outm,
 int *outl);

 int EVP_CipherInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
 unsigned char *key, unsigned char *iv, int enc);
 int EVP_CipherFinal(EVP_CIPHER_CTX *ctx, unsigned char *outm,
 int *outl);

 int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *a);
其实在这里列出的函数虽然很多,但是大部分是功能重复的,有的是旧的版本支持的函数,新的版本中已经可以不再使用了。事实上,函数EVP_EncryptInit, EVP_EncryptFinal, EVP_DecryptInit, EVP_CipherInit以及EVP_CipherFinal在新代码中不应该继续使用,他们保留下来只是为了兼容以前的代码。在新的代码中,应该使用EVP_EncryptInit_ex、EVP_EncryptFinal_ex、EVP_DecryptInit_ex、EVP_DecryptFinal_ex、EVP_CipherInit_ex以及EVP_CipherFinal_ex函数,因为它们可以在每次调用完算法后,不用重新释放和分配已有EVP_CIPHER_CTX结构的内存的情况下重用该结构,方便很多。下面我们分别对这些函数进行介绍。
【EVP_CIPHER_CTX_init】
该函数初始化一个EVP_CIPHER_CTX结构体,只有初始化后该结构体才能在下面介绍的函数中使用。操作成功返回1,否则返回0。
【EVP_EncryptInit_ex】
该函数采用ENGINE参数impl的算法来设置并初始化加密结构体。其中,参数ctx必须在调用本函数之前已经进行了初始化。参数type通常通过函数类型来提供参数,如EVP_des_cbc函数的形式,即我们上一章中介绍的对称加密算法的类型。如果参数impl为NULL,那么就会使用缺省的实现算法。参数key是用来加密的对称密钥,iv参数是初始化向量(如果需要的话)。在算法中真正使用的密钥长度和初始化密钥长度是根据算法来决定的。在调用该函数进行初始化的时候,除了参数type之外,所有其它参数可以设置为NULL,留到以后调用其它函数的时候再提供,这时候参数type就设置为NULL就可以了。在缺省的加密参数不合适的时候,可以这样处理。操作成功返回1,否则返回0。
【EVP_EncryptUpdate】
该函数执行对数据的加密。该函数加密从参数in输入的长度为inl的数据,并将加密好的数据写入到参数out里面去。可以通过反复调用该函数来处理一个连续的数据块。写入到out的数据数量是由已经加密的数据的对齐关系决定的,理论上来说,从0到(inl+cipher_block_size-1)的任何一个数字都有可能(单位是字节),所以输出的参数out要有足够的空间存储数据。写入到out中的实际数据长度保存在outl参数中。操作成功返回1,否则返回0。
【EVP_EncryptFinal_ex】
该函数处理最后(Final)的一段数据。在函数在padding功能打开的时候(缺省)才有效,这时候,它将剩余的最后的所有数据进行加密处理。该算法使用标志的块padding方式(AKA PKCS padding)。加密后的数据写入到参数out里面,参数out的长度至少应该能够一个加密块。写入的数据长度信息输入到outl参数里面。该函数调用后,表示所有数据都加密完了,不应该再调用EVP_EncryptUpdate函数。如果没有设置padding功能,那么本函数不会加密任何数据,如果还有剩余的数据,那么就会返回错误信息,也就是说,这时候数据总长度不是块长度的整数倍。操作成功返回1,否则返回0。
PKCS padding标准是这样定义的,在被加密的数据后面加上n个值为n的字节,使得加密后的数据长度为加密块长度的整数倍。无论在什么情况下,都是要加上padding的,也就是说,如果被加密的数据已经是块长度的整数倍,那么这时候n就应该等于块长度。比如,如果块长度是9,要加密的数据长度是11,那么5个值为5的字节就应该增加在数据的后面。
【EVP_DecryptInit_ex, EVP_DecryptUpdate和EVP_DecryptFinal_ex】
这三个函数是上面三个函数相应的解密函数。这些函数的参数要求基本上都跟上面相应的加密函数相同。如果padding功能打开了,EVP_DecryptFinal会检测最后一段数据的格式,如果格式不正确,该函数会返回错误代码。此外,如果打开了padding功能,EVP_DecryptUpdate函数的参数out的长度应该至少为(inl+cipher_block_size)字节;但是,如果加密块的长度为1,则其长度为inl字节就足够了。三个函数都是操作成功返回1,否则返回0。
需要注意的是,虽然在padding功能开启的情况下,解密操作提供了错误检测功能,但是该功能并不能检测输入的数据或密钥是否正确,所以即便一个随机的数据块也可能无错的完成该函数的调用。如果padding功能关闭了,那么当解密数据长度是块长度的整数倍时,操作总是返回成功的结果。
【EVP_CipherInit_ex, EVP_CipherUpdate和EVP_CipherFinal_ex】
事实上,上面介绍的函数都是调用这三个函数实现的,它们是更底层的函数。完成了数据的加密和解密功能。他们根据参数enc决定执行加密还是解密操作,如果enc为1,则加密;如果enc为0,则解密;如果enc是-1,则不改变数据。三个函数都是操作成功返回1,否则返回0。
【EVP_CIPHER_CTX_cleanup】
该函数清除一个EVP_CIPHER_CTX结构中的所有信息并释放该结构占用的所有内存。在使用上述的函数完成一个加密算法过程后应该调用该函数,这样可以避免一些敏感信息遗留在内存造成安全隐犯。操作成功返回1,否则返回0。
【EVP_EncryptInit, EVP_DecryptInit和EVP_CipherInit】
这三个函数的功能分别跟函数EVP_EncryptInit_ex, EVP_DecryptInit_ex和EVP_CipherInit_ex功能相同,只是他们的ctx参数不需要进行初始化,并且使用缺省的算法库。三个函数都是操作成功返回1,否则返回0。
【EVP_EncryptFinal, EVP_DecryptFinal和EVP_CipherFinal】
这三个函数分别跟函数EVP_EncryptFinal_ex, EVP_DecryptFinal_ex以及EVP_CipherFinal_ex函数功能相同,不过,他们的参数ctx会在调用后自动释放。三个函数都是操作成功返回1,否则返回0。
kfbyj
kfbyj 你好,那我问下。我要如何获取块长度呢。
一年多之前 回复
CSDNXIAON
CSDNXIAON   2016.04.14 21:12

openssl之EVP
----------------------同志你好,我是CSDN问答机器人小N,奉组织之命为你提供参考答案,编程尚未成功,同志仍需努力!

Csdn user default icon
上传中...
上传图片
插入图片
准确详细的回答,更有利于被提问者采纳,从而获得C币。复制、灌水、广告等回答会被删除,是时候展现真正的技术了!