使用 OpenSSL 解密 AES 的奇怪问题,用看起来相同的垃圾填充,Base64 填充问题

Strange problem with decryption of AES using OpenSSL, Gets padded with same looking junk, Base64 padding issue

有人能告诉我我的代码有什么问题吗?
它在我的测试示例中运行良好。但是当我在生产模型中使用它时,它会解密字符串但会添加一个填充符号以保持某种块大小或其他内容。 我没有 post 我的 encrypt/decrypt 方法,因为它们会使这个 post 太大,而且它们工作正常,因为我的测试示例正确解密和加密,ini.GetValue 是一个 INI检索方法没有任何问题,而且你可以看到base64大小与示例代码相同,所以我相信它工作正常,我之前使用它时没有加密,它returns a const char*

这个问题是众所周知的,因为你可以看到生产代码密文附加了 2 个空字节,我觉得很奇怪,因为这两个代码几乎相同,我我不擅长 C++,所以我可能忽略了一些基本的字符数组内容

我使用的加密代码是来自 OpenSSL 1.1.1

的 AES-256-CBC

看看我的输出,看看有什么问题。

好看的示例代码:

Ciphertext is:
000000: 7a e1 69 61 65 bb 74 ad 1a 68 8a ae 73 70 b6 0e  z.iae.t..h..sp..
000010: 4f c9 45 9b 44 ca e2 be e2 aa 16 14 cd b1 79 7b  O.E.D.........y{
000020: 86 a5 92 26 e6 08 3e 55 61 4e 60 03 50 f3 e4 c1  ...&..>UaN`.P...
000030: fe 5a 2c 0b df c9 1b d8 92 1f 48 75 0d f8 c2 44  .Z,.......Hu...D
Base64 (size=88):
000000: 65 75 46 70 59 57 57 37 64 4b 30 61 61 49 71 75  euFpYWW7dK0aaIqu
000010: 63 33 43 32 44 6b 2f 4a 52 5a 74 45 79 75 4b 2b  c3C2Dk/JRZtEyuK+
000020: 34 71 6f 57 46 4d 32 78 65 58 75 47 70 5a 49 6d  4qoWFM2xeXuGpZIm
000030: 35 67 67 2b 56 57 46 4f 59 41 4e 51 38 2b 54 42  5gg+VWFOYANQ8+TB
000040: 2f 6c 6f 73 43 39 2f 4a 47 39 69 53 48 30 68 31  /losC9/JG9iSH0h1
000050: 44 66 6a 43 52 41 3d 3d                          DfjCRA==
b cip len = 64
a cip len = 16
plain b = 0
plain a = 3
Decrypted text is:
wtf
Decrypted base64 is:
wtf
000000: 77 74 66 00                                      wtf.

错误的生产代码示例:

Base64 (size=88)
000000: 6a 7a 48 30 46 71 73 54 45 47 4d 76 2f 67 76 59  jzH0FqsTEGMv/gvY
000010: 4d 73 34 54 2f 39 58 32 6c 37 54 31 4d 6d 56 61  Ms4T/9X2l7T1MmVa
000020: 36 45 4f 38 52 64 45 57 42 6b 65 48 71 31 31 45  6EO8RdEWBkeHq11E
000030: 39 2b 77 37 47 4e 49 4a 47 4a 71 42 55 74 54 70  9+w7GNIJGJqBUtTp
000040: 30 36 58 46 31 4d 66 45 79 44 45 71 5a 69 58 54  06XF1MfEyDEqZiXT
000050: 79 45 53 6b 65 41 3d 3d                          yESkeA==
Ciphertext is:
000000: 8f 31 f4 16 ab 13 10 63 2f fe 0b d8 32 ce 13 ff  .1.....c/...2...
000010: d5 f6 97 b4 f5 32 65 5a e8 43 bc 45 d1 16 06 47  .....2eZ.C.E...G
000020: 87 ab 5d 44 f7 ec 3b 18 d2 09 18 9a 81 52 d4 e9  ..]D..;......R..
000030: d3 a5 c5 d4 c7 c4 c8 31 2a 66 25 d3 c8 44 a4 78  .......1*f%..D.x
000040: 00 00                                            ..
b cip len = 65
a cip len = 17
crypt miss-match
plain b = 16
crypt write fail
plain a = 16
000000: 77 74 66 09 09 09 09 09 09 09 09 05 05 05 05 05  wtf.............

这是我的代码,您可以看到它们看起来非常相似,所以我不明白这是什么问题。

这是我使用的 hexdump 输出的一个小辅助函数。

void Hexdump(void* ptr, int buflen)
{
    unsigned char* buf = (unsigned char*)ptr;
    int i, j;
    for (i = 0; i < buflen; i += 16) {
        myprintf("%06x: ", i);
        for (j = 0; j < 16; j++)
            if (i + j < buflen)
                myprintf("%02x ", buf[i + j]);
            else
                myprintf("   ");
        myprintf(" ");
        for (j = 0; j < 16; j++)
            if (i + j < buflen)
                myprintf("%c", isprint(buf[i + j]) ? buf[i + j] : '.');
        myprintf("\n");
    }
}

char* base64(const unsigned char* input, int length) {
    const auto pl = 4 * ((length + 2) / 3);
    auto output = reinterpret_cast<char*>(calloc(pl + 1, 1)); //+1 for the terminating null that EVP_EncodeBlock adds on
    const auto ol = EVP_EncodeBlock(reinterpret_cast<unsigned char*>(output), input, length);
    if (pl != ol) { myprintf("b64 calc %d,%d\n",pl, ol); }
    return output;
}

unsigned char* decode64(const char* input, int length) {
    const auto pl = 3 * length / 4;
    auto output = reinterpret_cast<unsigned char*>(calloc(pl + 1, 1));
    const auto ol = EVP_DecodeBlock(output, reinterpret_cast<const unsigned char*>(input), length);
    if (pl != ol) { myprintf("d64 calc %d,%d\n", pl, ol); }
    return output;
}

这是运行良好的测试示例。

    /* enc test */
    /* Message to be encrypted */
    unsigned char* plaintext = (unsigned char*)"wtf";

    /*
     * Buffer for ciphertext. Ensure the buffer is long enough for the
     * ciphertext which may be longer than the plaintext, depending on the
     * algorithm and mode.
     */
    unsigned char* ciphertext = new unsigned char[128];

    /* Buffer for the decrypted text */
    unsigned char decryptedtext[128];

    int decryptedtext_len, ciphertext_len;

    /* Encrypt the plaintext */
    ciphertext_len = encrypt(plaintext, strlen((char*)plaintext), ciphertext);

    /* Do something useful with the ciphertext here */
    myprintf("Ciphertext is:\n");
    Hexdump((void*)ciphertext, ciphertext_len);
    myprintf("Base64 (size=%d):\n", strlen(base64(ciphertext, ciphertext_len)));
    Hexdump((void*)base64(ciphertext, ciphertext_len), 4 * ((ciphertext_len + 2) / 3));

    /* Decrypt the ciphertext */
    decryptedtext_len = decrypt(ciphertext, ciphertext_len, decryptedtext);

    /* Add a NULL terminator. We are expecting printable text */
    decryptedtext[decryptedtext_len] = '[=14=]';

    /* Show the decrypted text */
    myprintf("Decrypted text is:\n");
    myprintf("%s\n", decryptedtext);
    myprintf("Decrypted base64 is:\n");
    myprintf("%s\n", decode64(base64(decryptedtext, decryptedtext_len), 4 * ((decryptedtext_len + 2) / 3)));
    Hexdump(decode64(base64(decryptedtext, decryptedtext_len), 4 * ((decryptedtext_len + 2) / 3)), 4 * ((decryptedtext_len + 2) / 3));
    /* enc test end */

这是错误的生产代码:

    //Decrypt the username
    const char* b64buffer = ini.GetValue("Credentials", "SavedPassword", "");
    int b64buffer_length = strlen(b64buffer);
    myprintf("Base64 (size=%d)\n", b64buffer_length);
    Hexdump((void*)b64buffer, b64buffer_length);
    int decryptedtext_len;
    int decoded_size = 3 * b64buffer_length / 4;
    unsigned char* decryptedtext = new unsigned char[decoded_size];
    //unsigned char* ciphertext = decode64(b64buffer, b64buffer_length); //had this before same problem as below line, this worked without initializing new memory I perfer to fix this back up
    unsigned char* ciphertext = new unsigned char[decoded_size];
    memcpy(ciphertext, decode64(b64buffer, b64buffer_length), decoded_size); //same problem as top line.
    myprintf("Ciphertext is:\n");
    Hexdump((void*)ciphertext, decoded_size);

    /* Decrypt the ciphertext */
    decryptedtext_len = decrypt(ciphertext, decoded_size - 1, decryptedtext);
    /* Add a NULL terminator. We are expecting printable text */
    decryptedtext[decryptedtext_len] = '[=15=]';
    Hexdump(decryptedtext, decryptedtext_len);
    strcpy(password_setting, (char*)decryptedtext); //save decrypted password back

    delete[] decryptedtext;
    delete[] ciphertext;

在有效的示例中,您直接从加密函数中获得 ciphertext_len。当你显示密文时,你使用这个长度。

在“错误的生产代码”中,您根据 Base64 数据的长度计算 decoded_size。但是,Base64编码的数据长度总是4的倍数。如果原始数据大小不是3的倍数,那么字符串中会添加一两个填充字符。在你的两个例子中,你有两个这样的字符,Base64 数据末尾的 '='

在计算解密数据的长度时,需要考虑这些字节。如果字符串末尾没有 '=' 个字符,请使用您计算出的长度 (3 * N / 4)。如果有一个'='字符,则将计算的长度减1,如果有两个'='字符,则将计算的长度减2。(不会有3个填充字符。)

编辑:这是我的修复:(sspoke)

char* base64(const unsigned char* input, int length) {
    const auto pl = 4 * ((length + 2) / 3);
    auto output = reinterpret_cast<char*>(calloc(pl + 1, 1)); //+1 for the terminating null that EVP_EncodeBlock adds on
    const auto ol = EVP_EncodeBlock(reinterpret_cast<unsigned char*>(output), input, length);
    if (pl != ol) { printf("encode64 fail size size %d,%d\n",pl, ol); }
    return output;
}

unsigned char* decode64(const char* input, int* length) {
    //Old code generated base length sizes because it didn't take into account the '==' signs.
    const auto pl = 3 * *length / 4;
    auto output = reinterpret_cast<unsigned char*>(calloc(pl + 1, 1));
    const auto ol = EVP_DecodeBlock(output, reinterpret_cast<const unsigned char*>(input), *length);
    if (pl != ol) { printf("decode64 fail size size %d,%d\n", pl, ol); }
    
    //Little bug fix I added to fix incorrect length's because '==' signs are not considered in the output. -sspoke
    if (*length > 3 && input[*length - 1] == '=' && input[*length - 2] == '=')
        *length = ol - 2;
    else if (*length > 2 && input[*length - 1] == '=')
        *length = ol - 1;
    else
        *length = ol;

    return output;
}