使用DER二进制私钥对SHA256哈希签名

时间:2019-12-30 17:12:09

标签: c++ openssl rsa

假设您获得了一个二进制私有文件,如下所示: Screenshot of binary file in Notepad++,您知道它是DER编码的。

此外,二进制文件受密码保护,假设密码为“密码”

您想要对已经散列的字符串进行签名(RSA),换句话说,您要签名的数据实际上是一个SHA256散列,看起来像这样:

78/YCOiMFRP66tXunCviIi/GaDvgBWFudaWnfcSkU4M=

您想在C ++中使用OpenSSL进行此操作。

我们如何做到这一点?

1 个答案:

答案 0 :(得分:0)

由于我不拥有网页,因此我将在此处发布,我想大多数人都会来这里寻求答案。

首先,我们希望能够打开受密码保护的二进制私钥并能够读取其内容。使用OpenSSL命令行,我们可以使用以下代码行:

openssl pkcs8 -in binPrivateKey.key -inform DER -passin pass:password -out private.key 

这会将私钥输出到如下所示的文件private.key中:

-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCN0peKpgfOL75i
YRv1fqq+oVYsLPVUR/GibYmGKc9InHFy5lYF6OTYjnIIvmkOdRobbGlCUxORX/tL
qY9/8F5mUvVgkcczsIgGdvf9vMQPSf3jjCiKj7j6ucxl1+FwJWmbvgNmiaUR/0q4
....
h0lBer2hP8NFqBPBBTNDwQ==
-----END PRIVATE KEY-----

在OpenSSL中,有一个名为d2i_PKCS8PrivateKey_fp的函数,该函数从字面上接受DER二进制文件指针,并输出一个内部密钥类型(对于OpenSSL内部),该类型可以是ESSL_PKEY,这是OpenSSL签名过程所必需的。该功能实际上可以使用密码来处理二进制文件。但是,必须通过回调函数输入密码,如果我们不处理GUI或命令行程序,则只需稍作调整即可。

以下函数采用一个字符串,该字符串包含二进制私钥的路由,一个char指针包含密码。

EVP_PKEY * PKCS8_DERtoEVP(std::string der_key_file, char * pwd){
    FILE * fp = NULL;
    EVP_PKEY * evp_pvt = NULL;
    OpenSSL_add_all_algorithms(); //This is necessary for some reason I do not fully understand.
    fp = fopen(der_key_file.c_str(),"rb"); //Usual file pointer of C++.
    if( fp ){
        evp_pvt = d2i_PKCS8PrivateKey_fp(fp, &evp_pvt, pwd_cb, pwd); //pwd_cb is a function callback I'll describe later.
        if (evp_pvt == NULL) {
            //ERROR HANDLING...
            fclose(fp);
            return NULL ;
            }
    }
    fclose(fp);
    return evp_pvt;
}

我提到的调整是为了防止以下情况发生,我正在使用OpenSSL的标准密码回调:

int pwd_cb(char *buf, int size, int rwflag, void *u){
    char * pwd;
    if (u == NULL){
       pwd = "1234";
    } else {
       pwd = (char*)u;
    }
    size_t len = strlen(pwd);
    if (len > size) len = size;
    memcpy(buf,pwd,len);

    return len;
}

这样,我们可以“欺骗”系统,以为用户输入是存储在char数组中的密码。

现在,我们几乎已经准备好签名哈希了。要签名的核心功能如下:

unsigned char* signHash_DER(std::string hash_str, std::string privateKey_file, char * passphrase = NULL){
    EVP_PKEY * pkey = PKCS8_DERtoEVP(privateKey_file,passphrase);
    EVP_PKEY_CTX *ctx; //Context used in signature.
    unsigned char *sig; //The container of the signature.
    size_t siglen;
    size_t mdlen;
    if (pkey == NULL) {
        //Error handling...
        return NULL;
        }
    //Here our 'clear message' is actually our hash str and we copy it from the parameters.
    unsigned char clear_message[hash_str.length()];
    strcpy((char*) clear_message,hash_str.c_str());
    //In my implementation we require this decoding, it might not be the case for everyone.
    unsigned char * md = base64_decode(clear_message, strlen((char*) clear_message), &mdlen);
    //Initiate the context and stablish we're using default RSA.
    ctx = EVP_PKEY_CTX_new(pkey, ENGINE_get_default_RSA() /* no engine */);
    if(!ctx) {
    LOGF_ERROR("Error CTX_new");
    return NULL;
  }
  if (EVP_PKEY_sign_init(ctx) <= 0){
    //Error handling
    return NULL;
  }
 if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0){
    //Error handling
    return NULL;
 }
 if (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0){
    //Error handling
    return NULL;
 }
 /* Determine buffer length */
 if (EVP_PKEY_sign(ctx, NULL, &siglen, md, mdlen) <= 0){
    //Error handling
    return NULL;
 }
 sig = (unsigned char*)OPENSSL_malloc(siglen);

 if (!sig){
    //Error handling
    return NULL;
 }

 if (EVP_PKEY_sign(ctx, sig, &siglen, md, mdlen) <= 0){
    std::cout << "Error sign";
    return NULL;
 }
 /* Signature is siglen bytes written to buffer sig */
 // encode to base64 again .
 size_t signed_string_len;
 unsigned char * signed_string = base64_encode(sig, siglen, &signed_string_len);

 return signed_string;
}

这已经足够了,大部分最新的功能代码是从OpenSSL文档中获得的,这很糟糕,但是耐心地完成了工作。

通话示例:

unsigned char* signature_decoded = signHash_DER(hashed_string, "privateBinary.key","dont4get");

如果您有任何疑问,请告诉我,希望这对您有所帮助!