因为业务须要。我们须要使用支付宝移动快捷支付做收款。支付宝给了我们《移动快捷支付应用集成接入包支付接口》见支付宝包《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-----\n開始,以\n-----END PUBLIC KEY-----\n结束,中间的字符串须要每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] != '\n')
{
strPublicKey.insert(i, "\n");
}
i++;
}
strPublicKey.insert(0, "-----BEGIN PUBLIC KEY-----\n");
strPublicKey.append("\n-----END PUBLIC KEY-----\n");
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!\n");
}
else if(0 == result)
{
printf("mock alipay notify data");
}
else
{
printf("error\n");
}
return 0;
}

使用OpenSSL做RSA签名验证 支付宝移动快捷支付 的server异步通知的更多相关文章

  1. java如何集成支付宝移动快捷支付功能

    项目需要,需要在客户端集成支付宝接口.第一次集成,过程还是挺简单的,不过由于支付宝官方文档写的不够清晰,也是走了一些弯路,下面把过程写出来分享给大家.就研究了一下:因为使用支付宝接口,就需要到支付宝官 ...

  2. java集成支付宝移动快捷支付时报错java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence

    出错原因是代码中的私钥设置错误,不是填原始的私钥,而是转换为PKCS8格式的私钥(Java格式的) ,改成后就会报创建交易异常了

  3. phonegap支付宝2.0移动快捷支付插件IOS版

    坑爹的支付宝,一两年都没有更新sdk了,这两天突然更新sdk,而且更新的变化特别大,所以只能对之前的支付宝快捷支付插件重新写了一遍. 这样既顺应了支付宝的更新,同时也支持了ios8. 废话少说,集成过 ...

  4. Android-支付宝快捷支付

    支付宝的快捷支付Android版业务流程比較麻烦,出现的意外情况比較多.在此,简单说下开发流程以及出现错误的解决方式; 1.注冊支付业务.这里不在赘述.建立数据安全传输所须要的私钥公钥,这里採用的是R ...

  5. ASP.NET Core 2.0 使用支付宝PC网站支付

    前言 最近在使用ASP.NET Core来进行开发,刚好有个接入支付宝支付的需求,百度了一下没找到相关的资料,看了官方的SDK以及Demo都还是.NET Framework的,所以就先根据官方SDK的 ...

  6. Java支付宝PC网站支付功能开发(详细教程)

    一.前言 本案例使用的是Java实现的.使用支付宝的沙盒环境示例.发布需要换成正式环境.这里就不作详细说明了 本代码适合用来做参考,不要直接复制去使用. 没有账号的需要去平台注册一个: 登录支付宝开发 ...

  7. ASP.NET Core2.0 环境下MVC模式的支付宝PC网站支付接口-沙箱环境开发测试

    1.新建.NET Core web项目 2.Controllers-Models-Views 分三个大部分 3.下载安装最新sdk 官方的SDK以及Demo都还是.NET Framework的,根据官 ...

  8. day114:MoFang:基于支付宝沙箱测试环境完成创建充值订单接口&服务端处理支付结果的同步通知和异步通知

    目录 1.基于支付宝提供的沙箱测试环境开发支付接口 1.后端提供创建充值订单接口 2.前端调用AlipayPlus发起支付 3.注意:自定义APPLoader完成接下来的开发 4.下载支付宝沙箱钱包A ...

  9. Android学习笔记_69_android 支付宝之网页支付和快捷支付

    参考资料: https://b.alipay.com/order/productDetail.htm?productId=2013080604609654 https://b.alipay.com/o ...

随机推荐

  1. jdbc 接口学习笔记

    一.JDBC概念 JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系型数据库提供统一访问,它由一组用Jav ...

  2. Quartz 表达式的学习

    只记录用到过的,不全面 Quartz版本:quartz-all-1.6.0.jar 先看图 其他示例: 0 5,6,13 * * ? 意义:每日5:00,6:00,13:00 被触发 0 10,30 ...

  3. JS排序之选择排序

    遍历这个数组,先确定索引为0的数字为暂时最小数, 在剩下的数据中,以第一个为标杆,和剩下的数依次进行比较,如果标杆大于某数,则进行索引交换,继续比较,则a[i]=min; 最后让a[i]与索引为0的数 ...

  4. avaScript中变量的声明和赋值

    变量是指程序中一个已经命名的存储单元,它的主要作用就是为数据操作提供存放信息的容器.变量是相对常量而言的.常量是一个不会改变的固定值,而变量的值可能会随着程序的执行而改变.变量有两个基本特征,即变量名 ...

  5. (转)基于Metronic的Bootstrap开发框架经验总结(4)--Bootstrap图标的提取和利用

    http://www.cnblogs.com/wuhuacong/p/4762924.html 在前面的一篇随笔<基于Metronic的Bootstrap开发框架经验总结(1)-框架总览及菜单模 ...

  6. window path 的基本配置

    %JAVA_HOME%\bin;%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\ ...

  7. 初探CORBA组件化编程

    1.掌握组件化开发的概念,了解CORBA模型及ORB机制:2.掌握CORBA组件编程方法.二.实验内容(一).步骤1.配制环境JDK环境.2.编写编译IDL接口.3.编写编译服务端程序.4.编写编译客 ...

  8. 关于大XML文件与大节点处理(System.Xml.XmlTextReader)

    近期有个任务要求处理大XML文件,其中有个存了Base64的大节点(>90M,路径已知). 这种任务只能上XmlReader,即使如此大节点的处理还是头疼了一阵…… 最初查MSDN的时候,找到了 ...

  9. scrapy-redis使redis不止保存url

    先看scrapy-redis源码 class RedisMixin(object): """Mixin class to implement reading urls f ...

  10. Java8自定义条件让集合分组

    /** * 将一个指定类型对象的集合按照自定义的一个操作分组: 每组对应一个List.最终返回结果类型是:List<List<T>> * * @param <T> ...