使用 DER 二进制私钥签署 SHA256 哈希
Sign a SHA256 hash using a DER binary private key
假设您得到一个如下所示的二进制私有文件:
你知道它是 DER 编码的。
此外,二进制文件受密码保护,假设密码是 'password'
并且您想签署 (RSA) 一个已经散列的字符串,换句话说,您要签署的数据实际上是一个 SHA256 散列,可能如下所示:
78/YCOiMFRP66tXunCviIi/GaDvgBWFudaWnfcSkU4M=
并且您想在 C++ 中使用 OpenSSL 来执行此操作。
我们如何做到这一点?
我将在此处发布此内容,因为我没有自己的网页,而且我认为大多数人都是来这里寻求答案的。
首先,我们希望能够打开受密码保护的二进制私钥并能够读取其内容。使用 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 的内部密钥类型),它可以是一个 EVP_PKEY,对于OpenSSL 中的签名程序。这个函数实际上可以带一个密码来处理二进制文件。但是,密码必须通过回调函数输入,如果我们不处理 GUI 或命令行程序,则需要稍微调整一下。
以下函数采用包含二进制私钥路径的字符串和包含密码的字符指针。
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;
}
这样,我们可以 'cheat' 系统认为用户输入的是存储在 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");
如果您有任何疑问,请告诉我,希望对您有所帮助!
假设您得到一个如下所示的二进制私有文件:
此外,二进制文件受密码保护,假设密码是 'password'
并且您想签署 (RSA) 一个已经散列的字符串,换句话说,您要签署的数据实际上是一个 SHA256 散列,可能如下所示:
78/YCOiMFRP66tXunCviIi/GaDvgBWFudaWnfcSkU4M=
并且您想在 C++ 中使用 OpenSSL 来执行此操作。
我们如何做到这一点?
我将在此处发布此内容,因为我没有自己的网页,而且我认为大多数人都是来这里寻求答案的。
首先,我们希望能够打开受密码保护的二进制私钥并能够读取其内容。使用 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 的内部密钥类型),它可以是一个 EVP_PKEY,对于OpenSSL 中的签名程序。这个函数实际上可以带一个密码来处理二进制文件。但是,密码必须通过回调函数输入,如果我们不处理 GUI 或命令行程序,则需要稍微调整一下。
以下函数采用包含二进制私钥路径的字符串和包含密码的字符指针。
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;
}
这样,我们可以 'cheat' 系统认为用户输入的是存储在 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");
如果您有任何疑问,请告诉我,希望对您有所帮助!