问题描述
之前使用C写了一个计算rsa签名的函数,最近在根据私钥计算签名的时候突然遇到如下错误:PEM_read_bio_PrivateKey err[error:0906D064:lib(9):func(109):reason(100)],虽然代码里是用openssl提供的ERR_error_string_n接口打印的详细错误描述,但是仍然看不懂错误的含义,后来man了下,可以通过openssl errstr 0906D064方法查看某个错误码对应的详细错误信息。
$openssl errstr 0906D064
error:0906D064:PEM routines:PEM_read_bio:bad base64 decode
原因分析
第一步:首先怀疑是rsa私钥格式的问题,因为openssl默认使用PEM的格式,而不是DER的格式,经确认私钥格式没问题。
第二步:因为计算rsa签名报错使用的是字符串方式PEM_read_bio_RSAPrivateKey,所以先考虑使用文件方式PEM_read_RSAPrivateKey看是否存在相同的错误。通过将字符串还原成标准的PEM私钥格式,验证也没有问题。那说明字符串方式在构造私钥的时候有问题。
-----BEGIN RSA PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCHLMCvgZGJuJ2I
...
qWW232xBX9o51hRgrr8y82OsBDrQ7epuuHhrbNZzAHEjUKnIlyc3eeSrmSL7FaiL
+Cs3Hxv0A==
-----END RSA PRIVATE KEY-----
检查代码找到了问题,在通过字符串构造PEM私钥的时候,因为每次迭代多插入了一个换行符多了一次++i操作,所以每次迭代比较时要计算实际的strPrivateKey长度。当RSA私钥比较长的时候,最后一次迭代长度可能超过64,导致私钥格式非法;而当私钥长度较短的时候,这个问题又不会出现。
int keyLen = strPrivateKey.size();
for (int i = 64; i < keyLen; i += 64)// <--- Problem! 应为 i < strPrivateKey.size()
{
if (strPrivateKey[i] != '\n')
{
strPrivateKey.insert(i, "\n");
}
++i;
}
strPrivateKey.insert(0, "-----BEGIN RSA PRIVATE KEY-----\n");
strPrivateKey.append("\n-----END RSA PRIVATE KEY-----\n");
char * szPrivateKey = const_cast<char *>(strPrivateKey.c_str());
if ((bio = BIO_new_mem_buf(szPrivateKey, strPrivateKey.length())) == NULL)
{
char errBuf[512] = {0};
ERR_error_string_n(ERR_get_error(), errBuf, sizeof(errBuf));
iResultCode = 1;
strResultInfo = std::string("BIO_new_mem_buf err[") + errBuf + "]";;
return -1;
}
rsa = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
if (NULL == rsa)
{
char errBuf[512] = {0};
ERR_error_string_n(ERR_get_error(), errBuf, sizeof(errBuf));
iResultCode = 2;
strResultInfo = std::string("PEM_read_bio_PrivateKey err[") + errBuf + "]";
return -1;
}
BIO_free(bio);
小技巧: 除了用代码生成,也可以使用工具fold -w 64格式化文本,简单方便。
其他
RSA的公私钥可以使用openssl工具生成,方法如下。
#!/bin/bash
echo "create rsa privatekey"
openssl genrsa -out privatekey.pem 1024
echo "create rsa publickey from privatekey"
openssl rsa -in privatekey.pem -out publickey.pem -outform PEM -pubout
echo "done"
具体也可以参考 测试代码