zoukankan      html  css  js  c++  java
  • 使用OpenSSL做RSA签名验证 支付宝移动快捷支付 的server异步通知

    因为业务须要。我们须要使用支付宝移动快捷支付做收款。支付宝给了我们《移动快捷支付应用集成接入包支付接口》见支付宝包《WS_SECURE_PAY_SDK》。

    支付宝给的serverdemo仅仅有Java、C#、PHP三种,而我们server端使用的是C++。

    这当中就涉及到接收支付宝的server异步通知。为了确保接收到的server异步通知来至支付宝,我们就必须验证支付宝的签名。

    坑爹的是,原来PC端使用MD5做签名,预计支付宝考虑到移动端的风险更高,于是改用RSA做移动快捷支付应用的签名。这无疑添加了我们迁移到移动端的开发成本。

    支付宝文档中说明是使用openssl,我们这边就决定使用openssl做rsa签名验证。

    因为第一次使用openssl做RSA验证签名,我们碰到了各种坑,为了避免其它项目也碰到类似问题。分享例如以下:


    首先要说明的是RSA签名和签名验证的过程。

    RSA签名的过程(支付宝操作)例如以下:对须要签名的字符串按key的字母升序排序,使用=和&连接,形成一个签名字符串。

    对该字符串做摘要(能够使用MD5或者SHA1,支付宝使用的是SHA1),然后对摘要字符串(即接口中的hash參数)使用支付宝私钥做RSA加密,获得加密字符串,即为签名字符串(放在sign中),设置sign_type=RSA。

    这样,就完毕了发送字符串的签名。

    RSA签名验证的过程(我们第三方企业操作)例如以下:接收到发送过来的字符串(假设字符串没有做url decode解码。须要做url decode解码)。拆分为key、value对,依照支付宝的文档。依据key的字母升序排序,使用=和&链接,获得被签名字符串。

    被签名字符串做SHA1摘要算法,获得SHA1摘要字符串。假设sign_type=RSA。先将sign字段做base64解码,然后使用支付宝公钥做RSA解密。得到SHA1摘要字符串。比較两个SHA1摘要字符串,假设SHA1摘要字符串一致,则签名验证成功。

    特别说明的是:支付宝的公钥字符串为以-----BEGIN PUBLIC KEY----- 開始,以 -----END PUBLIC KEY----- 结束,中间的字符串须要每64个字符换行一次,即为:

    -----BEGIN PUBLIC KEY-----
    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnxj/9qwVfgoUh/y2W89L6BkRA
    FljhNhgPdyPuBV64bfQNN1PjbCzkIM6qRdKBoLPXmKKMiFYnkd6rAoprih3/PrQE
    B/VsW8OoM8fxn67UDYuyBTqA23MML9q1+ilIZwBC2AQ2UBVOrFXfFl75p6/B5Ksi
    NG9zpgmLCUYuLkxpLQIDAQAB
    -----END PUBLIC KEY-----

    理论说完了,再解释一下使用的函数吧。


    验证签名函数为:int verifyAlipayNotify(const std::string& recvString, const std::string& alipayPublicKey);

    recvString为接收的字符串,未做urldecode。

    alipayPublicKey为本地内存中存储的支付宝公钥,已经保证包括特殊说明的条件。


    使用的openssl函数例如以下:

    int RSA_verify(int type, const unsigned char *m, unsigned int m_length,
        const unsigned char *sigbuf, unsigned int siglen, RSA *rsa);

    type 使用何种摘要算法,这里因为使用的是SHA1算法,填写NID_sha1

    m 摘要字符串

    m_length 摘要字符串长度

    sigbuf 支付宝返回的签名,已经做了base64解码

    siglen 支付宝返回的签名长度,这里应该为128

    rsa openssl的RSA密钥结构体,这里由支付宝公钥转化而来的

    返回值:负数为运行错误,0为签名验证失败(预计是有黑客攻击你),1为签名验证成功


    verifyAlipayNotify代码例如以下:

    #include <openssl/rsa.h>
    #include <openssl/sha.h>
    #include <openssl/md5.h>
    #include <openssl/rand.h>
    #include <openssl/objects.h>
    #include <openssl/pem.h>
    #include <openssl/bio.h>
    
    #include <string>
    #include <map>
    
    #include "urlcodec.h"
    #include "base64.h"
    
    struct ltstr
    {
    	bool operator()(std::string s1, std::string s2) const{return (s1.compare(s2) < 0);}
    };
    
    int verifyString(const std::string& signString, const std::string& sign, const std::string& alipayPublicKey)
    {
    	//获得支付宝的签名字节串
    	char szSign[128];
    	unsigned long szSignLen = 128;
    	bool decodeResult = CBase64::Decode(sign, (unsigned char*)szSign, &szSignLen);//CBase::Decode是Base64解码函数,您能够在网上随便下载一个
    	if(!decodeResult)
    	{
    		return -1;
    	}
    
    	//获得SHA1摘要字符串
    	unsigned char sha1Origin[20];
    	SHA1((unsigned char*)signString.c_str(), signString.size(), sha1Origin);
    
    	//由支付宝公钥内存字符串转化为openssl的RSA结构
    	BIO* memBIO = NULL;
    	memBIO = BIO_new(BIO_s_mem());
    	int bioWriteLen = BIO_write(memBIO, alipayPublicKey.c_str(), alipayPublicKey.length());
    	RSA* rsa = PEM_read_bio_RSA_PUBKEY(memBIO, NULL, NULL, NULL);
        if(NULL == rsa)
        {
            return -2;
        }
    	//签名验证
    	int verifyResult = RSA_verify(NID_sha1, sha1Origin, SHA_DIGEST_LENGTH, (unsigned char*)szSign, szSignLen, rsa);
    	return verifyResult;
    }
    
    int verifyAlipayNotify(const std::string& alipayNotifyData, const std::string& alipayPublicKey)
    {
    	std::string strAlipayNotifyData = alipayNotifyData;
    	std::string sign;
    	std::map<std::string, std::string, ltstr> omap;
    	std::string::size_type pos = strAlipayNotifyData.find("&");
    	while(std::string::npos != pos)
    	{
    		std::string one = strAlipayNotifyData.substr(0, pos);
    		std::string::size_type subpos = one.find("=");
    		if(std::string::npos != subpos)
    		{
    			std::string key = one.substr(0, subpos);
    			if("sign_type" != key && "sign" != key)
    			{
    				std::string value = one.substr(subpos+1);
    				std::string newValue = UrlDecode(value);//UrlDecode是URL解码函数。您能够在网上随便下载一个
    				omap.insert(std::make_pair(key, newValue));
    			}
    			else if("sign" == key)
    			{
    				sign = UrlDecode(one.substr(subpos+1));
    			}
    		}
    
    		strAlipayNotifyData = strAlipayNotifyData.substr(pos + 1);
    		pos = strAlipayNotifyData.find("&");
    	}
    	std::string::size_type subpos = strAlipayNotifyData.find("=");
    	if(std::string::npos != subpos)
    	{
    		std::string key = strAlipayNotifyData.substr(0, subpos);
    		if("sign_type" != key && "sign" != key)
    		{
    			std::string value = strAlipayNotifyData.substr(subpos+1);
    			std::string newValue = UrlDecode(value);
    			omap.insert(std::make_pair(key, newValue));
    		}
    		else if("sign" == key)
    		{
    			sign = UrlDecode(strAlipayNotifyData.substr(subpos+1));
    		}
    	}
    
    	//获得支付宝被签名字符串
    	std::string signString = "";
    	std::map<std::string, std::string, ltstr>::iterator itr = omap.begin();
    	for(; itr != omap.end(); ++itr)
    	{
    		signString += itr->first;
    		signString += "=";
    		signString += itr->second;
    		signString += "&";
    	}
    	if(!signString.empty())
    	{
    		signString.erase(signString.length() - 1);
    	}
    
    	return verifyString(signString, sign, alipayPublicKey);
    }

    有时候,你本地存储的公钥是没有包括头尾的,如

    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnxj/9qwVfgoUh/y2W89L6BkRAFljhNhgPdyPuBV64bfQNN1PjbCzkIM6qRdKBoLPXmKKMiFYnkd6rAoprih3/PrQEB/VsW8OoM8fxn67UDYuyBTqA23MML9q1+ilIZwBC2AQ2UBVOrFXfFl75p6/B5KsiNG9zpgmLCUYuLkxpLQIDAQAB

    为此。提供一个函数支持转化为完整公钥的函数:

    std::string completeAlipayPublicKey(std::string strPublicKey)
    {
    	int nPublicKeyLen = strPublicKey.size();      //strPublicKey为base64编码的公钥字符串
    	for(int i = 64; i < nPublicKeyLen; i+=64)
    	{
    		if(strPublicKey[i] != '
    ')
    		{
    			strPublicKey.insert(i, "
    ");
    		}
    		i++;
    	}
    	strPublicKey.insert(0, "-----BEGIN PUBLIC KEY-----
    ");
    	strPublicKey.append("
    -----END PUBLIC KEY-----
    ");
    	return strPublicKey;
    }

    最后,測试代码例如以下:
    int main(int argc, char **argv)
    {
    
    	std::string strPublicKey = "**********";
    	std::string strAlipayData = "**********";
    	std::string strCompletePublicKey = completeAlipayPublicKey(strPublicKey);
    	int result = verifyAlipayNotify(strAlipayData, strCompletePublicKey);
    	if(1 == result)
    	{
    		printf("verify sign ok!
    ");
    	}
    	else if(0 == result)
    	{
    		printf("mock alipay notify data");
    	}
    	else
    	{
    		printf("error
    ");
    	}
    	return 0;
    }






  • 相关阅读:
    Spring标签@Aspect-实现面向方向编程(@Aspect的多数据源自动加载)——SKY
    easyUI参数传递Long型时,前台解析出错的问题——SKY
    javax.servlet.ServletException: Could not resolve view with name‘ XXXX’in servlet with name 'spring'的解决方案-----SKY
    Netty实现java多线程Post请求解析(Map参数类型)—SKY
    java并发编程基础---Sky
    创建100个1k的随机文件到FSxL
    RAID磁盘阵列与LVM逻辑卷管理
    The beginners’ guide to farming Chia Coin on Windows.
    有赞移动Crash平台建设
    Comparisons Serverless
  • 原文地址:https://www.cnblogs.com/jhcelue/p/6917283.html
Copyright © 2011-2022 走看看