<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> 最近一段时间一直被一个事情困扰:支付相关RSA签名与验证签名,服务器使用java,客户端是c++的程序,在C++端验证签名的时候,试用了很多方法都无法签名通过。在java中,签名和验证签名很容易调用现有的类实现,但是在c++中却是不太容易。</span>
采用openssl原生的c++程序,不行;
在网上搜索了很久,也翻墙google了,试用了很多,也不行;
用了其他网友借鉴PHP的代码的,也不行;
所有RSA流程总规则:
私钥签名,公钥验签
最后,在参考了一个貌似支付宝验证的alipay.h的文件后,今天终于通过验证签名,现把代码贴出,以便以后再有用,也对遇到同等类型的问题的朋友有个参考。
JAVA端:
* 用私钥对信息生成数字签名
* @param data
* 加密数据
* @param privateKey
* 私钥
* @return
* @throws Exception
public static String sign(byte[] data, String privateKey) throws Exception {
// 解密由base64编码的私钥
byte[] keyBytes = decryptBASE64(privateKey);
// 构造PKCS8EncodedKeySpec对象
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
// KEY_ALGORITHM 指定的加密算法
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 取私钥匙对象
PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 用私钥对信息生成数字签名
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(priKey);
signature.update(data);
return encryptBASE64(signature.sign());
* 校验数字签名
* @param data
* 加密数据
* @param publicKey
* 公钥
* @param sign
* 数字签名
* @return 校验成功返回true 失败返回false
* @throws Exception
public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
// 解密由base64编码的公钥
byte[] keyBytes = decryptBASE64(publicKey);
// 构造X509EncodedKeySpec对象
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
// KEY_ALGORITHM 指定的加密算法
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 取公钥匙对象
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(pubKey);
signature.update(data);
// 验证签名是否正常
return signature.verify(decryptBASE64(sign));
看着很简单吧(不纠结于RSA实现过程)
PHP端也很好找,不在贴部分代码,网上很多;
通过上面JAVA代码通过私钥签名的字符串,如何通过c++验证签名的正确呢?
先把公私钥的openssl生成过程,给贴出来(我是linux服务器测试的,终端中输入openssl):
OpenSSL> genrsa -out rsa_private_key.pem 1024 #生成私钥
OpenSSL> pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out rsa_private_key_pkcs8.pem #Java开发者需要将私钥转换成PKCS8格式
OpenSSL> rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem #生成公钥
OpenSSL> exit #退出OpenSSL程序
JAVA签名/验证签名测试:
public static void main(String[] args) throws Exception {
// 签名生成
String content = "appId=23232323&&testestesjfijfe12";
String privateKey = "mbyKNDk+fpYHGZ8WMzGJjA6wWsFcWDxOtdIP4BR7W00Shvau/QJBALQxheLcK9s3CfnD+RtQK9MxKbk/oe0Pjf+UvmufUJOWzGNzThuwNA70EThKb0VBNMaXbeHxVicU0QquTdKQkH0CQG/VwLy00QjqwLv6oqZ+i6XpsSoCTlwe25Yp/pjsUrpq5+DnZ9mkw2s2WUi2sdwOpUogctQ5XlBbdjOLpoLhVjM=";
String sign = sign(content.getBytes(), privateKey);
String lastSign = URLEncoder.encode(sign.replace("\n", ""), "UTF-8");
System.out.println("签名内容:" + content);
System.out.println("最终签名:" + lastSign);
// 签名验证
String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA";
boolean bverify = verify(content.getBytes(), publicKey, URLDecoder.decode(xiaoySign, "UTF-8"));
System.out.println("验证结果:" + bverify +";decode sign="+URLDecoder.decode(xiaoySign, "UTF-8"));
c++实现,直接代码:
工具类中,需要工具:
static std::string url_encode(const std::string& szToEncode);
static std::string url_decode(const std::string& szToDecode);
static bool verify_rsa(RSA *rsa ,const std::string &content, const std::string &sign);
实现:
std::string common_tool::url_encode(const std::string& szToEncode)
std::string src = szToEncode;
char hex[] = "0123456789ABCDEF";
std::string dst;
for (size_t i = 0; i < src.size(); ++i)
unsigned char cc = src[i];
if (isascii(cc))
if (cc == ' ')
dst += "%20";
dst += cc;
unsigned char c = static_cast<unsigned char>(src[i]);
dst += '%';
dst += hex[c / 16];
dst += hex[c % 16];
return dst;
std::string common_tool::url_decode(const std::string &SRC) {
std::string ret;
char ch;
int i, ii;
for (i=0; i<SRC.length(); i++) {
if (int(SRC[i])==37) {
sscanf(SRC.substr(i+1,2).c_str(), "%x", &ii);
ch=static_cast<char>(ii);
ret+=ch;
i=i+2;
} else {
ret+=SRC[i];
return (ret);
bool common_tool::verify_rsa(/*const char *public_key*/RSA *rsa ,
const std::string &content, const std::string &sign) {
BIO *bufio = NULL;
EVP_PKEY *evpKey = NULL;
bool verify = false;
EVP_MD_CTX ctx;
int result = 0;
std::string decodedSign = common_tool::base64_decode(sign);
char *chDecodedSign = const_cast<char*>(decodedSign.c_str());
if (rsa == NULL) {
printf("PEM_read_bio_RSA_PUBKEY failed");
goto safe_exit;
evpKey = EVP_PKEY_new();
if (evpKey == NULL) {
printf("EVP_PKEY_new failed");
goto safe_exit;
if ((result = EVP_PKEY_set1_RSA(evpKey, rsa)) != 1) {
printf("EVP_PKEY_set1_RSA failed");
goto safe_exit;
EVP_MD_CTX_init(&ctx);
if (result == 1 && (result = EVP_VerifyInit_ex(&ctx,
EVP_md5(), NULL)) != 1) {
printf("EVP_VerifyInit_ex failed");
if (result == 1 && (result = EVP_VerifyUpdate(&ctx,
content.c_str(), content.size())) != 1) {
printf("EVP_VerifyUpdate failed");
if (result == 1 && (result = EVP_VerifyFinal(&ctx,
(unsigned char*)chDecodedSign,
decodedSign.size(), evpKey)) != 1) {
printf("EVP_VerifyFinal failed");
if (result == 1) {
verify = true;
} else {
printf("verify failed");
EVP_MD_CTX_cleanup(&ctx);
safe_exit:
if (rsa != NULL) {
RSA_free(rsa);
rsa = NULL;
if (evpKey != NULL) {
EVP_PKEY_free(evpKey);
evpKey = NULL;
if (bufio != NULL) {
BIO_free_all(bufio);
bufio = NULL;
return verify;
verify_rsa 是整个验证的核心。
参数准备:
RSA *rsa
BIO *key = NULL;
RSA *r = NULL;
key = BIO_new(BIO_s_file());
BIO_read_filename(key, "rsa_public_key.pem");
r = PEM_read_bio_RSAPublicKey(key, NULL, NULL, NULL);
BIO_free_all(key);
content为加密的字符串
sign为java生成的签名,java中这个签名urlencode了,传入之前,需要先urldecode一下。
在此函数中,sign还需要base64 decode一下,这样就没问题了。
时间有限,不多写了。
把c++签名过程再贴一下:
static std::string sign(const char *private_key,
const std::string &content) {
BIO *bufio = NULL;
RSA *rsa = NULL;
EVP_PKEY *evpKey = NULL;
bool verify = false;
EVP_MD_CTX ctx;
int result = 0;
unsigned int size = 0;
char *sign = NULL;
std::string signStr = "";
//bufio = BIO_new_mem_buf((void*)private_key, -1);
//if (bufio == NULL) {
// ERR("BIO_new_mem_buf failed");
// goto safe_exit;
bufio = BIO_new(BIO_s_file());
BIO_read_filename(bufio, "rsa_private_key_pkcs8.pem");
//BIO_read_filename(bufio, "rsa_private_key.pem");
rsa = PEM_read_bio_RSAPrivateKey(bufio, NULL, NULL, NULL);
if (rsa == NULL) {
ERR("PEM_read_bio_RSAPrivateKey failed");
goto safe_exit;
evpKey = EVP_PKEY_new();
if (evpKey == NULL) {
ERR("EVP_PKEY_new failed");
goto safe_exit;
if ((result = EVP_PKEY_set1_RSA(evpKey, rsa)) != 1) {
ERR("EVP_PKEY_set1_RSA failed");
goto safe_exit;
EVP_MD_CTX_init(&ctx);
if (result == 1 && (result = EVP_SignInit_ex(&ctx,
EVP_md5(), NULL)) != 1) {
ERR("EVP_SignInit_ex failed");
if (result == 1 && (result = EVP_SignUpdate(&ctx,
content.c_str(), content.size())) != 1) {
ERR("EVP_SignUpdate failed");
size = EVP_PKEY_size(evpKey);
sign = (char*)malloc(size+1);
memset(sign, 0, size+1);
if (result == 1 && (result = EVP_SignFinal(&ctx,
(unsigned char*)sign,
&size, evpKey)) != 1) {
ERR("EVP_SignFinal failed");
if (result == 1) {
verify = true;
} else {
ERR("verify failed");
signStr = common_tool::base64_encode((const unsigned char*)sign, size);
EVP_MD_CTX_cleanup(&ctx);
free(sign);
safe_exit:
if (rsa != NULL) {
RSA_free(rsa);
rsa = NULL;
if (evpKey != NULL) {
EVP_PKEY_free(evpKey);
evpKey = NULL;
if (bufio != NULL) {
BIO_free_all(bufio);
bufio = NULL;
return signStr;
//return sign;
sign的过程,如果需要和verify一样,需要改一下传入参数。
https://wiki.openssl.org/index.php/EVP_Signing_and_Verifying
http://www.codeguru.com/cpp/cpp/algorithms/strings/article.php/c12759/URI-Encoding-and-Decoding.htm
http://blog.csdn.net/lazyclough/article/details/7646696
最近一段时间一直被一个事情困扰:支付相关RSA签名与验证签名,服务器使用java,客户端是c++的程序,在C++端验证签名的时候,试用了很多方法都无法签名通过。在java中,签名和验证签名很容易调用现有的类实现,但是在c++中却是不太容易。 采用openssl原生的c++程序,不行; 在网上搜索了很久,也翻墙google了,试用了很多,也不行; 用了其他网友借鉴
// v_pwszFilePath --- 程序的全路径
// v_pwszSign --- 用于返回数字签名的缓冲区,如果为NULL,
// 那么将会需要的缓冲区大小
一、RSA是一种非对称加密算法,一般在数据加密的过程中会使用公钥加密,私钥解密,在签名生成和验证过程中会使用私钥加密,公钥解密。
二、使用openssl生成公钥和私钥
1、生成私钥,保存在文件rsa_private_key.pem里面
openssl genrsa -out rsa_private_key.pem 1024
2、通过私钥生成公钥,保存在文件rsa_private_key.p...
RSA验签是使用RSA公钥对数字签名进行验证的过程。在Go语言中,可以使用标准库"crypto/rsa"中的VerifyPKCS1v15函数来实现RSA验签。使用方法如下:
1. 使用x509标准库解析公钥文件得到公钥结构体
2. 使用sha256.New()得到hash.Hash接口
3. 使用hash.Write(originalData)对需要验签的数据进行hash
4. 使用rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, hashed, signData)来验签,其中pubKey为公钥结构体,hashed为第3步得到的hash值,signData为签名数据
示例代码:
package main
import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"io/ioutil"
func main() {
// 读取公钥文件
pubKeyData, _ := ioutil.ReadFile("public.pem")
block, _ := pem.Decode(pubKeyData)
pubKey, _ := x509.ParsePKIXPublicKey(block.Bytes)
// 读取签名数据
signData, _ := ioutil.ReadFile("sign.txt")
signData, _ = base64.StdEncoding.DecodeString(string(signData))
// 读取需要验签的数据
originalData, _ := ioutil.ReadFile("data.txt")
// 计算hash值
hasher := sha256.New()
hasher.Write(originalData)
hashed := hasher.Sum(nil)
// 验签
err := rsa.VerifyPKCS1v15(pubKey.(*rsa.PublicKey), crypto.SHA256, hashed, signData)
if err != nil {
fmt.Println("验签失败")
} else {
fmt.Println("验签成功")
上面代码展示