使用OpenSSL做RSA签名验证 支付宝移动快捷支付 的服务器异步通知

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

支付宝给的服务器demo只有Java、C#、PHP三种,而我们服务器端使用的是C++。这其中就涉及到接收支付宝的服务器异步通知。为了确保接收到的服务器异步通知来至支付宝,我们就必须验证支付宝的签名。坑爹的是,原来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-----\n开始,以\n-----END PUBLIC KEY-----\n结束,中间的字符串需要每64个字符换行一次,即为:

[plain]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. -----BEGIN PUBLIC KEY-----  
  2. MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnxj/9qwVfgoUh/y2W89L6BkRA  
  3. FljhNhgPdyPuBV64bfQNN1PjbCzkIM6qRdKBoLPXmKKMiFYnkd6rAoprih3/PrQE  
  4. B/VsW8OoM8fxn67UDYuyBTqA23MML9q1+ilIZwBC2AQ2UBVOrFXfFl75p6/B5Ksi  
  5. NG9zpgmLCUYuLkxpLQIDAQAB  
  6. -----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代码如下:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <openssl/rsa.h>  
  2. #include <openssl/sha.h>  
  3. #include <openssl/md5.h>  
  4. #include <openssl/rand.h>  
  5. #include <openssl/objects.h>  
  6. #include <openssl/pem.h>  
  7. #include <openssl/bio.h>  
  8.   
  9. #include <string>  
  10. #include <map>  
  11.   
  12. #include "urlcodec.h"  
  13. #include "base64.h"  
  14.   
  15. struct ltstr  
  16. {  
  17.     bool operator()(std::string s1, std::string s2) const{return (s1.compare(s2) < 0);}  
  18. };  
  19.   
  20. int verifyString(const std::string& signString, const std::string& sign, const std::string& alipayPublicKey)  
  21. {  
  22.     //获得支付宝的签名字节串  
  23.     char szSign[128];  
  24.     unsigned long szSignLen = 128;  
  25.     bool decodeResult = CBase64::Decode(sign, (unsigned char*)szSign, &szSignLen);//CBase::Decode是Base64解码函数,您可以在网上随便下载一个  
  26.     if(!decodeResult)  
  27.     {  
  28.         return -1;  
  29.     }  
  30.   
  31.     //获得SHA1摘要字符串  
  32.     unsigned char sha1Origin[20];  
  33.     SHA1((unsigned char*)signString.c_str(), signString.size(), sha1Origin);  
  34.   
  35.     //由支付宝公钥内存字符串转化为openssl的RSA结构  
  36.     BIO* memBIO = NULL;  
  37.     memBIO = BIO_new(BIO_s_mem());  
  38.     int bioWriteLen = BIO_write(memBIO, alipayPublicKey.c_str(), alipayPublicKey.length());  
  39.     RSA* rsa = PEM_read_bio_RSA_PUBKEY(memBIO, NULL, NULL, NULL);  
  40.     if(NULL == rsa)  
  41.     {  
  42.         return -2;  
  43.     }  
  44.     //签名验证  
  45.     int verifyResult = RSA_verify(NID_sha1, sha1Origin, SHA_DIGEST_LENGTH, (unsigned char*)szSign, szSignLen, rsa);  
  46.     return verifyResult;  
  47. }  
  48.   
  49. int verifyAlipayNotify(const std::string& alipayNotifyData, const std::string& alipayPublicKey)  
  50. {  
  51.     std::string strAlipayNotifyData = alipayNotifyData;  
  52.     std::string sign;  
  53.     std::map<std::string, std::string, ltstr> omap;  
  54.     std::string::size_type pos = strAlipayNotifyData.find("&");  
  55.     while(std::string::npos != pos)  
  56.     {  
  57.         std::string one = strAlipayNotifyData.substr(0, pos);  
  58.         std::string::size_type subpos = one.find("=");  
  59.         if(std::string::npos != subpos)  
  60.         {  
  61.             std::string key = one.substr(0, subpos);  
  62.             if("sign_type" != key && "sign" != key)  
  63.             {  
  64.                 std::string value = one.substr(subpos+1);  
  65.                 std::string newValue = UrlDecode(value);//UrlDecode是URL解码函数,您可以在网上随便下载一个  
  66.                 omap.insert(std::make_pair(key, newValue));  
  67.             }  
  68.             else if("sign" == key)  
  69.             {  
  70.                 sign = UrlDecode(one.substr(subpos+1));  
  71.             }  
  72.         }  
  73.   
  74.         strAlipayNotifyData = strAlipayNotifyData.substr(pos + 1);  
  75.         pos = strAlipayNotifyData.find("&");  
  76.     }  
  77.     std::string::size_type subpos = strAlipayNotifyData.find("=");  
  78.     if(std::string::npos != subpos)  
  79.     {  
  80.         std::string key = strAlipayNotifyData.substr(0, subpos);  
  81.         if("sign_type" != key && "sign" != key)  
  82.         {  
  83.             std::string value = strAlipayNotifyData.substr(subpos+1);  
  84.             std::string newValue = UrlDecode(value);  
  85.             omap.insert(std::make_pair(key, newValue));  
  86.         }  
  87.         else if("sign" == key)  
  88.         {  
  89.             sign = UrlDecode(strAlipayNotifyData.substr(subpos+1));  
  90.         }  
  91.     }  
  92.   
  93.     //获得支付宝被签名字符串  
  94.     std::string signString = "";  
  95.     std::map<std::string, std::string, ltstr>::iterator itr = omap.begin();  
  96.     for(; itr != omap.end(); ++itr)  
  97.     {  
  98.         signString += itr->first;  
  99.         signString += "=";  
  100.         signString += itr->second;  
  101.         signString += "&";  
  102.     }  
  103.     if(!signString.empty())  
  104.     {  
  105.         signString.erase(signString.length() - 1);  
  106.     }  
  107.   
  108.     return verifyString(signString, sign, alipayPublicKey);  
  109. }  

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

[plain]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnxj/9qwVfgoUh/y2W89L6BkRAFljhNhgPdyPuBV64bfQNN1PjbCzkIM6qRdKBoLPXmKKMiFYnkd6rAoprih3/PrQEB/VsW8OoM8fxn67UDYuyBTqA23MML9q1+ilIZwBC2AQ2UBVOrFXfFl75p6/B5KsiNG9zpgmLCUYuLkxpLQIDAQAB  

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

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. std::string completeAlipayPublicKey(std::string strPublicKey)  
  2. {  
  3.     int nPublicKeyLen = strPublicKey.size();      //strPublicKey为base64编码的公钥字符串  
  4.     for(int i = 64; i < nPublicKeyLen; i+=64)  
  5.     {  
  6.         if(strPublicKey[i] != '\n')  
  7.         {  
  8.             strPublicKey.insert(i, "\n");  
  9.         }  
  10.         i++;  
  11.     }  
  12.     strPublicKey.insert(0, "-----BEGIN PUBLIC KEY-----\n");  
  13.     strPublicKey.append("\n-----END PUBLIC KEY-----\n");  
  14.     return strPublicKey;  
  15. }  

最后,测试代码如下:
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. int main(int argc, char **argv)  
  2. {  
  3.   
  4.     std::string strPublicKey = "**********";  
  5.     std::string strAlipayData = "**********";  
  6.     std::string strCompletePublicKey = completeAlipayPublicKey(strPublicKey);  
  7.     int result = verifyAlipayNotify(strAlipayData, strCompletePublicKey);  
  8.     if(1 == result)  
  9.     {  
  10.         printf("verify sign ok!\n");  
  11.     }  
  12.     else if(0 == result)  
  13.     {  
  14.         printf("mock alipay notify data");  
  15.     }  
  16.     else  
  17.     {  
  18.         printf("error\n");  
  19.     }  
  20.     return 0;  
  21. }  

猜你喜欢

转载自blog.csdn.net/j6915819/article/details/53857082
今日推荐