c++ - OpenSSL库中的SMIME_read_PKCS7方法有输入长度1200的限制?

标签 c++ cryptography openssl

我正在尝试使用 OpenSSL 提供的高级 API 进行加密工作。

通常情况下,它在这里工作得很好。

/**
 * Instructions for generating private key file and self signed certificate file.
 *
 * openssl genrsa -des3 -out keys.pem 2048
 * openssl rsa -in keys.pem -out rsa.pem
 * openssl req -new -x509 -key rsa.pem -out rsa-cert.pem -days 9999
 * 
 * Compile
 * g++ -ggdb -o exe.bex src.cpp -lcrypto
 */

//c standard library
#include <stdio.h>
#include <string.h>

//openssl library
#include <openssl/pem.h>
#include <openssl/pkcs7.h>
#include <openssl/x509.h>

//c++ standard library
#include <iostream>
#include <string>

int main(int argc, char* argv[]) {
    FILE* fd = fopen("rsa.pem", "r");
    X509* x509;
    STACK_OF(X509)* x509_stack = sk_X509_new_null();
    EVP_PKEY* key;
    if (NULL != fd)
    {
        key = PEM_read_PrivateKey(fd, NULL, NULL, NULL);
    }
    fd = fopen("rsa-cert.pem", "r");
    if (NULL != fd)
    {
        while (NULL != (x509 = PEM_read_X509(fd, NULL, NULL, NULL)))
        {
            sk_X509_push(x509_stack, x509);
        }
        fclose(fd);
    }
    OpenSSL_add_all_algorithms();

    while (!std::cin.eof()) {
        std::string msg;

        //get input text
        printf("Message to PKCS7 encrypt: ");
        fflush(stdout);
        std::getline(std::cin, msg);
        if (223 < msg.length())
        {
            // SMIME_read_PKCS7 bug, need to be fixed.
            //
            // bt:
            //#0  asn1_d2i_read_bio (in=0x60bc40, pb=0x7fffffffe238) at a_d2i_fp.c:286
            //#1  0x00007ffff7aeb762 in ASN1_item_d2i_bio (it=0x7ffff7dc2da0, in=0x60bc40, x=0x0) at a_d2i_fp.c:113
            //#2  0x00007ffff7b0201c in b64_read_asn1 (bio=0x60bc40, it=0x7ffff7dc2da0) at asn_mime.c:191
            //#3  0x00007ffff7b02dd0 in SMIME_read_ASN1 (bio=0x60af00, bcont=0x7fffffffe350, it=0x7ffff7dc2da0) at asn_mime.c:527
            //#4  0x00007ffff7b39db2 in SMIME_read_PKCS7 (bio=0x60af00, bcont=0x7fffffffe350) at pk7_mime.c:96
            //#5  0x0000000000401668 in main (argc=1, argv=0x7fffffffe4b8) at ./crypto-pkcs.cpp:99
            //
            std::cout << "string too long " << msg.length() << std::endl;
            continue;
        }
        if (msg.empty())
        {
            std::cout << "string too short" << std::endl;
            continue;
        }


        //save input
        BIO* bio_input_plain_text = BIO_new(BIO_s_mem());
        BIO_write(bio_input_plain_text, msg.c_str(), msg.length());
        BIO_flush(bio_input_plain_text);

        //create PKCS7 object in the way of PKCS7_encrypt.
        PKCS7* pkcs7_encrypt = PKCS7_encrypt(x509_stack, bio_input_plain_text, EVP_aes_256_cbc(), 0);
        if (NULL == pkcs7_encrypt) {
            std::cout << "PKCS7_encrypt returns NULL" <<  std::endl; return -1;}

        //dump encryped info.
        BIO* bio_encrypted_smime = BIO_new(BIO_s_mem());
        if (SMIME_write_PKCS7(bio_encrypted_smime, pkcs7_encrypt, bio_input_plain_text, 0) != 1){ std::cout << "SMIME_write_PKCS7 failed" << std::endl; return -1;}
        BIO_flush(bio_encrypted_smime);

        //get internal data address
        const char* encrypted = NULL;
        BIO_get_mem_data(bio_encrypted_smime, &encrypted);  //encrypted has no new resource, only the reflection of the internal BIO data.
        //char encrypted[8 * 1024] = "";
        //copy BIO to char array
        //BIO_read(bio_encrypted_smime, encrypted, sizeof encrypted - 1); //if we read the data out of BIO, later we need to write it back, BIO_read deletes the internal data inside BIO
        std::cout << "PKCS7_encrypt length:" << strlen(encrypted) << std::endl << encrypted << std::endl;
        //please be careful while taking care of BIO object.
        //if we call BIO_read against BIO to get out data, the operation will cause the data deleted in the BIO at the same time.
        //here we recover BIO data.
        //BIO_write(bio_encrypted_smime, encrypted, strlen(encrypted));
        //BIO_flush(bio_encrypted_smime);

        BIO* bio_pkcs7 = BIO_new(BIO_s_mem());
        //read&load PKCS7 object from SMIME format.
        PKCS7* pkcs7_smime = SMIME_read_PKCS7(bio_encrypted_smime, &bio_pkcs7);
        if (NULL == pkcs7_smime) {std::cout << "SMIME_read_PKCS7 returns NULL" << std::endl;return -1;}
        BIO_flush(bio_pkcs7);
        BIO* bio_pkcs7_decrypt = BIO_new(BIO_s_mem());
        //decrypt in the way of PKCS7_decrypt
        if (0 == PKCS7_decrypt(pkcs7_smime, key, x509, bio_pkcs7_decrypt, 0)){ std::cout << "PKCS7_decrypt failed" << std::endl;return -1;}
        BIO_flush(bio_pkcs7_decrypt);
        //char decrypted[8 * 1024] = "";
        //dump decrypted data.
        //BIO_read(bio_pkcs7_decrypt, decrypted, sizeof decrypted - 1);
        const char* decrypted = NULL;
        //get internal data address
        BIO_get_mem_data(bio_pkcs7_decrypt, &decrypted);
        std::cout << "PKCS7_decrypt length: " << strlen(decrypted) << std::endl << decrypted << std::endl;

        //cleanup, idiot! donot forget to release resource you piece of shit!
        BIO_free(bio_pkcs7_decrypt);
        BIO_free(bio_pkcs7);
        BIO_free(bio_encrypted_smime);
        PKCS7_free(pkcs7_smime);
        PKCS7_free(pkcs7_encrypt);
    }
    X509_free(x509);
    sk_X509_pop_free(x509_stack, X509_free);

    return 0;
}

这段代码适用于 openssl-1.0.1g。我尝试了线程和无线程配置选项。

但是如果我们输入一个 224 长度的纯文本而没有 if-continue 的东西,加密仍然有效,它无法在 SMIME_read_PKCS7 的方法中解密,查看上面的回溯评论。

在输入长度为223的情况下,加密后的S/MIME长度为1200。

输入224长度的情况下,S/MIME格式的加密结果长度超过1200,SMIME_read_PKCS7无法处理这个长度,内部调用查看上面的bt,但是我没有找到任何文档或页面解释。

SMIME_read_PKCS7 的 secret 是什么?

缺陷一:

在@jww 的帮助下。应该有

BIO_set_mem_eof_return(bio_encrypted_smime, 0);

之前

PKCS7* pkcs7_smime = SMIME_read_PKCS7(bio_encrypted_smime, &bio_pkcs7);

缺陷2:

BIO_get_mem_data 这个应该返回数据的长度,实际上我们需要根据长度处理 char*,不要只依赖空终止,除非你期望附加垃圾值。

最佳答案

What is the secret of SMIME_read_PKCS7?

嗯,真的没有 secret 。它只是 buggy 。根据 SMIME_read_PKCS7(3) 上的文档:

BUGS

The MIME parser used by SMIME_read_PKCS7() is somewhat primitive.
While it will handle most S/MIME messages more complex compound
formats may not work.

The parser assumes that the PKCS7 structure is always base64
encoded and will not handle the case where it is in binary
format or uses quoted printable format.

The use of a memory BIO to hold the signed content limits the
size of message which can be processed due to memory restraints:
a streaming single pass option should be available. 

OpenSSL User Group about SMIME_read_PKCS7 上有一些不错的话题.

关于c++ - OpenSSL库中的SMIME_read_PKCS7方法有输入长度1200的限制?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23622837/

相关文章:

c++ - 复制/修改 STL 容器的拷贝是线程安全的吗?

c++ - 必须在类成员函数返回类型中提供更具体的类型,而不是参数类型?

c++ - C++ lambda的机制是什么?

android - 如何检查Android应用程序的OpenSSL库版本

c - 使用base64算法进行加密和解密

c - 我如何使用 wget 来获取公钥?

c# - 静态方法与单例

javascript - 将游戏分数从客户端更新到服务器数据库的最安全方法? Javascript

java - 具有公钥的 RSA 签名作为未在 java 中验证的文本

c# - 如何使用 MACTripleDES 加密解密字符串?