该帖子主要目标是:想开发自己SDK的研发人员
支付宝请求流程
- 首先根据支付宝的要求,生成签名,组装参数
- 然后向支付宝发起支付
- 支付宝会根据你组装好的参数验证签名
- 签名验证成功,则开始验证参数
- 验证成功,支付宝会返回成功的响应结果,code 为 10000
- 响应后,拿到一个支付宝的签名,需要你在本地验签
- 若验签成功,流程结束
支付宝提供了各个语言的SDK,用户可以自行下载,或如果你考虑自己开发SDK,下面我遇到的坑给大家分享一下:
密钥和签名类型是否匹配
支付宝分为两种密钥类型,RSA和RSA2,RSA 是 SHA1 算法 ,RSA2 是 SHA256算法,一旦两者弄混了,自然验签会不通过。
支付宝的密钥是JAVA算法生成的
我用的是C#语言,两者RSA算法是不一样的,我没试过其他语言。在C#中不能直接使用RSAServiceProvider 类型来生成签名,因为算法不同。在最底部我会贴一段代码,可以CV走使用。该代码是在支付宝提供的源码里,非自己原创。
发送请求之前要转码
我忘了是在哪里看到这个要求的了,反正没在支付宝文档里看到。要把所有的参数根据charset参数进行url的转码操作(encode),否则总说你参数无效。
本地验签
其实这一步你可以考虑不做,因为你已经拿到结果了(无论成功还是失败),为了安全起见当然还是做一下比较好。支付宝的响应是一个动态对象,名称格式大致为:xxxxxxx_response
,这个对象虽然是json的,但在验签前,一定要清楚所有的空格和换行符(\r\n),保留成一行的字符串,不然总提示我验签失败。
证书类型
JAVA用的好像是pk8类型的,提示上说的C#用pk12就可以,但是然并卵。
好像就这些了吧?
这是那段代码,我太菜,太底层的读不懂
static class AlipayRsaServiceHelper
{
#region Methods
/// <summary>
/// 获取指定签名类型对应的RSA加密算法类型。
/// </summary>
/// <param name="signType">签名类型。</param>
/// <returns>算法类型的字符串。</returns>
public static string GetHashlgType(string signType)
{
switch (signType.ToUpper())
{
case "RSA":return "SHA1";
case "RSA2":return "SHA256";
default:throw new ArgumentOutOfRangeException("仅支持 RSA/RSA2 两种类型");
}
}
/// <summary>
/// 加载 DER 类型的公钥。
/// </summary>
/// <param name="provider"><see cref="RSACryptoServiceProvider"/> 实例。</param>
/// <param name="DERData">一个符合 DER 格式的二进制数据。</param>
public static void LoadPublicKeyDER(RSACryptoServiceProvider provider, byte[] DERData)
{
byte[] RSAData = GetRSAFromDER(DERData);
byte[] publicKeyBlob = GetPublicKeyBlobFromRSA(RSAData);
provider.ImportCspBlob(publicKeyBlob);
}
/// <summary>
/// 加载指定 PEM 格式的公钥。
/// </summary>
/// <param name="provider"><see cref="RSACryptoServiceProvider"/> 实例。</param>
/// <param name="pem">一个符合 DER 格式的二进制数据。</param>
public static void LoadPublicKeyPEM(RSACryptoServiceProvider provider, string pem)
{
byte[] DERData = GetDERFromPEM(pem);
LoadPublicKeyDER(provider, DERData);
}
/// <summary>
/// 获取指定RSA数据的公钥二进制数据。
/// </summary>
public static byte[] GetPublicKeyBlobFromRSA(byte[] RSAData)
{
byte[] data = null;
UInt32 dwCertPublicKeyBlobSize = 0;
if (CryptDecodeObject(CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING,
new IntPtr((int)CRYPT_OUTPUT_TYPES.RSA_CSP_PUBLICKEYBLOB), RSAData, (UInt32)RSAData.Length, CRYPT_DECODE_FLAGS.NONE,
data, ref dwCertPublicKeyBlobSize))
{
data = new byte[dwCertPublicKeyBlobSize];
if (!CryptDecodeObject(CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING,
new IntPtr((int)CRYPT_OUTPUT_TYPES.RSA_CSP_PUBLICKEYBLOB), RSAData, (UInt32)RSAData.Length, CRYPT_DECODE_FLAGS.NONE,
data, ref dwCertPublicKeyBlobSize))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return data;
}
throw new Win32Exception(Marshal.GetLastWin32Error());
}
/// <summary>
/// 从指定的 DER 数据获取符合 RSA 的二进制数据。
/// </summary>
public static byte[] GetRSAFromDER(byte[] DERData)
{
byte[] data = null;
byte[] publicKey = null;
CERT_PUBLIC_KEY_INFO info;
UInt32 dwCertPublicKeyInfoSize = 0;
IntPtr pCertPublicKeyInfo = IntPtr.Zero;
if (CryptDecodeObject(CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr((int)CRYPT_OUTPUT_TYPES.X509_PUBLIC_KEY_INFO),
DERData, (UInt32)DERData.Length, CRYPT_DECODE_FLAGS.NONE, data, ref dwCertPublicKeyInfoSize))
{
data = new byte[dwCertPublicKeyInfoSize];
if (CryptDecodeObject(CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr((int)CRYPT_OUTPUT_TYPES.X509_PUBLIC_KEY_INFO),
DERData, (UInt32)DERData.Length, CRYPT_DECODE_FLAGS.NONE, data, ref dwCertPublicKeyInfoSize))
{
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
try
{
info = (CERT_PUBLIC_KEY_INFO)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(CERT_PUBLIC_KEY_INFO));
publicKey = new byte[info.PublicKey.cbData];
Marshal.Copy(info.PublicKey.pbData, publicKey, 0, publicKey.Length);
}
finally
{
handle.Free();
}
return publicKey;
}
}
throw new Win32Exception(Marshal.GetLastWin32Error());
}
/// <summary>
/// Extracts the binary data from a PEM file.
/// 从 pem 格式的字符串中获取 DER 类型的二进制数据。
/// </summary>
public static byte[] GetDERFromPEM(string pem)
{
UInt32 dwSkip, dwFlags;
UInt32 dwBinarySize = 0;
if (!CryptStringToBinary(pem, (UInt32)pem.Length, CRYPT_STRING_FLAGS.CRYPT_STRING_BASE64HEADER, null, ref dwBinarySize, out dwSkip, out dwFlags))
throw new Win32Exception(Marshal.GetLastWin32Error());
byte[] decodedData = new byte[dwBinarySize];
if (!CryptStringToBinary(pem, (UInt32)pem.Length, CRYPT_STRING_FLAGS.CRYPT_STRING_BASE64HEADER, decodedData, ref dwBinarySize, out dwSkip, out dwFlags))
throw new Win32Exception(Marshal.GetLastWin32Error());
return decodedData;
}
/// <summary>
/// 解析 支付宝的 的 pkcs8 私钥
/// </summary>
/// <param name="pemPrivateKey">The privkey.</param>
/// <param name="signType">签名类型 RSA/RSA2</param>
/// <returns></returns>
public static RSACryptoServiceProvider Create(byte[] pemPrivateKey, string signType = "RSA")
{
if (pemPrivateKey == null)
{
throw new ArgumentNullException(nameof(pemPrivateKey));
}
if (signType == null)
{
throw new ArgumentNullException(nameof(signType));
}
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
MemoryStream mem = new MemoryStream(pemPrivateKey);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
int elems = 0;
signType = signType.ToString();
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0102) //version number
return null;
bt = binr.ReadByte();
if (bt != 0x00)
return null;
//------ all private key components are Integer sequences ----
elems = GetIntegerSize(binr);
MODULUS = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
E = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
D = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
P = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
Q = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DP = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DQ = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
IQ = binr.ReadBytes(elems);
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
CspParameters CspParameters = new CspParameters();
CspParameters.Flags = CspProviderFlags.UseMachineKeyStore;
int bitLen = 1024;
if ("RSA2".Equals(signType))
{
bitLen = 2048;
}
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(bitLen, CspParameters);
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus = MODULUS;
RSAparams.Exponent = E;
RSAparams.D = D;
RSAparams.P = P;
RSAparams.Q = Q;
RSAparams.DP = DP;
RSAparams.DQ = DQ;
RSAparams.InverseQ = IQ;
RSA.ImportParameters(RSAparams);
return RSA;
}
catch (Exception ex)
{
throw ex;
}
finally
{
binr.Close();
}
}
private static int GetIntegerSize(BinaryReader binr)
{
byte bt = 0;
byte lowbyte = 0x00;
byte highbyte = 0x00;
int count = 0;
bt = binr.ReadByte();
if (bt != 0x02) //expect integer
return 0;
bt = binr.ReadByte();
if (bt == 0x81)
count = binr.ReadByte(); // data size in next byte
else
if (bt == 0x82)
{
highbyte = binr.ReadByte(); // data size in next 2 bytes
lowbyte = binr.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
count = bt; // we already have the data size
}
while (binr.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= 1;
}
binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte
return count;
}
#endregion Methods
#region P/Invoke Constants
/// <summary>Enumeration derived from Crypto API.</summary>
enum CRYPT_ACQUIRE_CONTEXT_FLAGS : uint
{
CRYPT_NEWKEYSET = 0x8,
CRYPT_DELETEKEYSET = 0x10,
CRYPT_MACHINE_KEYSET = 0x20,
CRYPT_SILENT = 0x40,
CRYPT_DEFAULT_CONTAINER_OPTIONAL = 0x80,
CRYPT_VERIFYCONTEXT = 0xF0000000
}
/// <summary>Enumeration derived from Crypto API.</summary>
enum CRYPT_PROVIDER_TYPE : uint
{
PROV_RSA_FULL = 1
}
/// <summary>Enumeration derived from Crypto API.</summary>
enum CRYPT_DECODE_FLAGS : uint
{
NONE = 0,
CRYPT_DECODE_ALLOC_FLAG = 0x8000
}
/// <summary>Enumeration derived from Crypto API.</summary>
enum CRYPT_ENCODING_FLAGS : uint
{
PKCS_7_ASN_ENCODING = 0x00010000,
X509_ASN_ENCODING = 0x00000001,
}
/// <summary>Enumeration derived from Crypto API.</summary>
enum CRYPT_OUTPUT_TYPES : int
{
X509_PUBLIC_KEY_INFO = 8,
RSA_CSP_PUBLICKEYBLOB = 19,
PKCS_RSA_PRIVATE_KEY = 43,
PKCS_PRIVATE_KEY_INFO = 44
}
/// <summary>Enumeration derived from Crypto API.</summary>
enum CRYPT_STRING_FLAGS : uint
{
CRYPT_STRING_BASE64HEADER = 0,
CRYPT_STRING_BASE64 = 1,
CRYPT_STRING_BINARY = 2,
CRYPT_STRING_BASE64REQUESTHEADER = 3,
CRYPT_STRING_HEX = 4,
CRYPT_STRING_HEXASCII = 5,
CRYPT_STRING_BASE64_ANY = 6,
CRYPT_STRING_ANY = 7,
CRYPT_STRING_HEX_ANY = 8,
CRYPT_STRING_BASE64X509CRLHEADER = 9,
CRYPT_STRING_HEXADDR = 10,
CRYPT_STRING_HEXASCIIADDR = 11,
CRYPT_STRING_HEXRAW = 12,
CRYPT_STRING_NOCRLF = 0x40000000,
CRYPT_STRING_NOCR = 0x80000000
}
#endregion P/Invoke Constants
#region P/Invoke Structures
/// <summary>Structure from Crypto API.</summary>
[StructLayout(LayoutKind.Sequential)]
struct CRYPT_OBJID_BLOB
{
UInt32 cbData;
IntPtr pbData;
}
/// <summary>Structure from Crypto API.</summary>
[StructLayout(LayoutKind.Sequential)]
struct CRYPT_ALGORITHM_IDENTIFIER
{
IntPtr pszObjId;
CRYPT_OBJID_BLOB Parameters;
}
/// <summary>Structure from Crypto API.</summary>
[StructLayout(LayoutKind.Sequential)]
struct CRYPT_BIT_BLOB
{
internal UInt32 cbData;
internal IntPtr pbData;
internal UInt32 cUnusedBits;
}
/// <summary>Structure from Crypto API.</summary>
[StructLayout(LayoutKind.Sequential)]
struct CERT_PUBLIC_KEY_INFO
{
public CRYPT_ALGORITHM_IDENTIFIER Algorithm;
public CRYPT_BIT_BLOB PublicKey;
}
#endregion P/Invoke Structures
#region P/Invoke Functions
/// <summary>Function for Crypto API.</summary>
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CryptDestroyKey(IntPtr hKey);
/// <summary>Function for Crypto API.</summary>
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CryptImportKey(IntPtr hProv, byte[] pbKeyData, UInt32 dwDataLen, IntPtr hPubKey, UInt32 dwFlags, ref IntPtr hKey);
/// <summary>Function for Crypto API.</summary>
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CryptReleaseContext(IntPtr hProv, Int32 dwFlags);
/// <summary>Function for Crypto API.</summary>
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CryptAcquireContext(ref IntPtr hProv, string pszContainer, string pszProvider, CRYPT_PROVIDER_TYPE dwProvType, CRYPT_ACQUIRE_CONTEXT_FLAGS dwFlags);
/// <summary>Function from Crypto API.</summary>
[DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CryptStringToBinary(string sPEM, UInt32 sPEMLength, CRYPT_STRING_FLAGS dwFlags, [Out] byte[] pbBinary, ref UInt32 pcbBinary, out UInt32 pdwSkip, out UInt32 pdwFlags);
/// <summary>Function from Crypto API.</summary>
[DllImport("crypt32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CryptDecodeObjectEx(CRYPT_ENCODING_FLAGS dwCertEncodingType, IntPtr lpszStructType, byte[] pbEncoded, UInt32 cbEncoded, CRYPT_DECODE_FLAGS dwFlags, IntPtr pDecodePara, ref byte[] pvStructInfo, ref UInt32 pcbStructInfo);
/// <summary>Function from Crypto API.</summary>
[DllImport("crypt32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CryptDecodeObject(CRYPT_ENCODING_FLAGS dwCertEncodingType, IntPtr lpszStructType, byte[] pbEncoded, UInt32 cbEncoded, CRYPT_DECODE_FLAGS flags, [In, Out] byte[] pvStructInfo, ref UInt32 cbStructInfo);
#endregion P/Invoke Functions
}
生成签名示例
var pemData = Convert.FromBase64String(pemKey);//java的pk8类型的密钥
using (var rsa = AlipayRsaServiceHelper.Create(pemData, signType))//signType 签名类型,rsa 还是 rsa2
{
var data = Encoding.GetEncoding(kv["charset"].ToString()).GetBytes(signStamp);
var signedHashBytes = rsa.SignData(data, AlipayRsaServiceHelper.GetHashlgType(signType));
return Convert.ToBase64String(signedHashBytes);
}
验证签名示例
using (var rsa = new RSACryptoServiceProvider())
{
rsa.PersistKeyInCsp = false;
//以下为固定格式 pkcs8 公钥解析所用
var publicKey = string.Format("-----BEGIN PUBLIC KEY-----\r\n{0}-----END PUBLIC KEY-----\r\n\r\n", validationModel.AlipayPublicKey);//这是我的实体的属性,一定要用支付宝公钥
AlipayRsaServiceHelper.LoadPublicKeyPEM(rsa, publicKey);
var data = Encoding.GetEncoding(validationModel.CharSet).GetBytes(validationModel.SignStamp);
var signBase64String = Convert.FromBase64String(hashedSign);
return rsa.VerifyData(data, AlipayRsaServiceHelper.GetHashlgType(validationModel.SignType), signBase64String);
}