记支付宝接口对接,涉及到提取证书SN号的解决方案
Posted xlhblogs
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记支付宝接口对接,涉及到提取证书SN号的解决方案相关的知识,希望对你有一定的参考价值。
支付宝针对.NET SDK并未封装有提取证书SN序列号的方法,仅针对Java平台才有对应的方法(赤裸裸的歧视啊~~)
要想在提取这个SN序列号有两种方案:
1. 直接用Java SDK包来提取SN
2. 根据Java代码转换成C#代码来提取
支付宝的签名指导上有如下提示:
我两种方案都有使用,提取应用证书的SN用的是Java来提取的,没有问题;但是在通过Java SDK来提取支付宝根证书的SN时,返回错误;
因为对Java代码不熟悉,改不动,直接换成用C#代码来提取SN;
代码是从网上找的,这里记录下:
using Org.BouncyCastle.Math; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Security; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.RegularExpressions; using System.Xml; namespace Qcnt.ThreeParty.Payment { /// <summary> /// 三方支付相关的静态帮助类。 /// </summary> public static class Alipay_Crt_SN_Helper { /// <summary> /// 获取系统当前时间的时间戳。 /// </summary> public static string Timestamp { get { TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); return Convert.ToInt64(ts.TotalSeconds).ToString(); } } /// <summary> /// 获取指定长度的随机字符串。 /// </summary> /// <param name="length">待生成的随机字符串长度。</param> /// <returns>返回生成好的随机字符串。</returns> public static string GetRandomString(int length) { string str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; StringBuilder result = new StringBuilder(); Random rd = new Random(); while (length > 0) { result.Append(str[rd.Next(str.Length)]); length--; } return result.ToString(); } /// <summary> /// 对字符串进行MD5加密。 /// </summary> /// <param name="str">需要加密的字符串。</param> /// <param name="encoding">字符串使用的编码集。</param> /// <param name="delimiter">分隔符。</param> /// <returns>返回加密后的字符串。</returns> public static string MD5Encrypt(string str, Encoding encoding, char? delimiter) { if (string.IsNullOrEmpty(str)) return null; byte[] result = encoding.GetBytes(str); MD5 md5 = new MD5CryptoServiceProvider(); byte[] output = md5.ComputeHash(result); if (delimiter != null && delimiter.HasValue) return BitConverter.ToString(output).ToUpper().Replace(‘-‘, delimiter.Value); return BitConverter.ToString(output).ToUpper().Replace("-", ""); } /// <summary> /// 模拟发送HTTP POST请求。 /// </summary> /// <param name="url">需要请求的URL地址。</param> /// <param name="data">请求参数字符串。</param> /// <param name="encoding">请求所使用的字符串编码集。</param> /// <returns>返回请求结果。</returns> public static string HttpPost(string url, string data, Encoding encoding) { return HttpPost(url, data, null, null, encoding); } /// <summary> /// 模拟发送HTTP POST请求。 /// </summary> /// <param name="url">需要请求的URL地址。</param> /// <param name="data">请求参数字符串。</param> /// <param name="certPath">附加的证书路径(使用前先双击安装该证书)。</param> /// <param name="certPassword">附加的证书密码。</param> /// <param name="encoding">请求所使用的字符串编码集。</param> /// <returns>返回请求结果。</returns> public static string HttpPost(string url, string data, string certPath, string certPassword, Encoding encoding) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Timeout = 10000; request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; if (!string.IsNullOrWhiteSpace(certPath) && !string.IsNullOrWhiteSpace(certPassword) && File.Exists(certPath)) { ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback((sender, certificate, chain, errors) => { if (errors == SslPolicyErrors.None) return true; return false; }); request.ClientCertificates.Add(new X509Certificate2(certPath, certPassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable)); } if (!string.IsNullOrWhiteSpace(data)) { byte[] dataBytes = encoding.GetBytes(data); request.ContentLength = dataBytes.Length; using (Stream stream = request.GetRequestStream()) { stream.Write(dataBytes, 0, dataBytes.Length); } } using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { using (Stream responseStream = response.GetResponseStream()) { using (StreamReader streamReader = new StreamReader(responseStream, Encoding.GetEncoding("gb2312"))) { return streamReader.ReadToEnd(); } } } } /// <summary> /// 获取根据XmlNode名称匹配到第一个XmlNode的InnerText值。 /// </summary> /// <param name="node">匹配XmlNode的根XmlNode。</param> /// <param name="nodeName">要获取InnerText值的XmlNode名称。</param> /// <returns>若找到指定的XmlNode则返回该XmlNode的InnerText值,否则返回null。</returns> public static string GetNodeInnerText(this XmlNode node, string nodeName) { if (node == null) return null; XmlNode resultNode = node.SelectSingleNode(nodeName); if (resultNode == null) return null; return resultNode.InnerText; } /// <summary> /// 使用指定的参数集生成MD5签名。 /// </summary> /// <param name="pars">生成签名所用到的参数集合。</param> /// <param name="key">生成签名所用到的Key。</param> /// <returns>返回生成好的签名字符串。</returns> public static string CreateMD5Sign(Dictionary<string, string> pars, string key) { List<string> sortedKey = new List<string>(pars.Keys); sortedKey.Sort(); StringBuilder parsString = new StringBuilder(); foreach (string itemKey in sortedKey) { parsString.AppendFormat("{0}={1}&", itemKey, pars[itemKey]); } parsString.AppendFormat("key={0}", key); return MD5Encrypt(parsString.ToString(), Encoding.UTF8, null); } /// <summary> /// 使用指定的参数集生成RSA2签名。 /// </summary> /// <param name="pars">生成签名所用到的参数集合。</param> /// <param name="privateKey">生成签名所用到的私有秘钥。</param> /// <returns>返回生成好的签名字符串。</returns> public static string CreateRSA2Sign(Dictionary<string, string> pars, string privateKey) { List<string> sortedKey = new List<string>(pars.Keys); sortedKey.Sort(); StringBuilder parsString = new StringBuilder(); int i = 0; foreach (string key in sortedKey) { if (i > 0) parsString.Append("&"); parsString.AppendFormat("{0}={1}", key, pars[key]); i++; } return RSA2Encrypt(parsString.ToString(), privateKey); } /// <summary> /// 将指定的Xml字符串转换成<see cref="Dictionary{TKey, TValue}"/>类型的参数集。 /// </summary> /// <param name="xmlString">待转换的Xml字符串。</param> /// <returns>若转换成功则返回转换后的参数集。</returns> public static Dictionary<string, string> XmlToParameters(string xmlString) { if (string.IsNullOrWhiteSpace(xmlString)) return null; XmlDocument xml = new XmlDocument(); xml.LoadXml(xmlString); XmlNode xmlRoot = xml.DocumentElement; Dictionary<string, string> result = new Dictionary<string, string>(); foreach (XmlNode childNode in xmlRoot.ChildNodes) { result.Add(childNode.Name, childNode.InnerText); } return result; } /// <summary> /// 将指定的参数集转换成Xml格式的字符串。 /// </summary> /// <param name="pars">待转换的参数集。</param> /// <returns>返回转换后的Xml字符串。</returns> public static string ParametersToXmlString(Dictionary<string, string> pars) { StringBuilder parsString = new StringBuilder("<xml>"); foreach (string key in pars.Keys) { parsString.AppendFormat("<{0}>{1}</{0}>", key, pars[key].Replace("<", "<").Replace(">", ">").Replace("&", "&").Replace(""", """)); } parsString.Append("</xml>"); return parsString.ToString(); } /// <summary> /// 将指定的参数集转换成POST数据字符串。 /// </summary> /// <param name="pars">待转换的参数集。</param> /// <returns>返回转换后的POST数据字符串。</returns> public static string ParametersToPostDataString(Dictionary<string, string> pars) { StringBuilder postDataString = new StringBuilder(); int i = 0; foreach (string key in pars.Keys) { if (i > 0) postDataString.Append("&"); postDataString.AppendFormat("{0}={1}", key, System.Web.HttpUtility.UrlEncode(pars[key])); i++; } return postDataString.ToString(); } /// <summary> /// 对AES加密字符串方向进行AES解密操作。 /// </summary> /// <param name="decryptStr">待解密字符串。</param> /// <param name="key">AES解密Key。</param> /// <returns>返回解密后的字符串。</returns> public static string AESDecrypt(string decryptStr, string key) { byte[] keyByte = Encoding.UTF8.GetBytes(key); byte[] encryptByte = Convert.FromBase64String(decryptStr); RijndaelManaged rijndaelManaged = new RijndaelManaged(); rijndaelManaged.Key = keyByte; rijndaelManaged.Mode = CipherMode.ECB; rijndaelManaged.Padding = PaddingMode.PKCS7; ICryptoTransform cryptoTransform = rijndaelManaged.CreateDecryptor(); byte[] resultByte = cryptoTransform.TransformFinalBlock(encryptByte, 0, encryptByte.Length); return Encoding.UTF8.GetString(resultByte); } /// <summary> /// 使用RSA2加密算法加密字符串。 /// </summary> /// <param name="str">待加密字符串。</param> /// <param name="privateKey">加密私有秘钥。</param> /// <returns>获取成功则返回该CRT证书的序列号,否则返回null。</returns> private static string RSA2Encrypt(string str, string privateKey) { RSACryptoServiceProvider rsaCrypto = GetRSACryptoFromPrivateKey(privateKey); return Convert.ToBase64String(rsaCrypto.SignData(Encoding.UTF8.GetBytes(str), "SHA256")); } /// <summary> /// 从RSA私有秘钥字符串获取一个RSA加密对象。 /// </summary> /// <param name="privateKey">私有秘钥字符串。</param> /// <returns>返回获取到的RSA加密对象。</returns> private static RSACryptoServiceProvider GetRSACryptoFromPrivateKey(string privateKey) { byte[] modulus, exponent, d, p, q, dp, dq, inverseQ; MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(privateKey)); BinaryReader binaryReader = new BinaryReader(memoryStream); switch (binaryReader.ReadUInt16()) { case 0x8130: binaryReader.ReadByte(); break; case 0x8230: binaryReader.ReadInt16(); break; } if (binaryReader.ReadUInt16() != 0x0102) return null; if (binaryReader.ReadByte() != 0x00) return null; int elementCount = GetIntegerSize(binaryReader); modulus = binaryReader.ReadBytes(elementCount); elementCount = GetIntegerSize(binaryReader); exponent = binaryReader.ReadBytes(elementCount); elementCount = GetIntegerSize(binaryReader); d = binaryReader.ReadBytes(elementCount); elementCount = GetIntegerSize(binaryReader); p = binaryReader.ReadBytes(elementCount); elementCount = GetIntegerSize(binaryReader); q = binaryReader.ReadBytes(elementCount); elementCount = GetIntegerSize(binaryReader); dp = binaryReader.ReadBytes(elementCount); elementCount = GetIntegerSize(binaryReader); dq = binaryReader.ReadBytes(elementCount); elementCount = GetIntegerSize(binaryReader); inverseQ = binaryReader.ReadBytes(elementCount); CspParameters cspParameters = new CspParameters(); cspParameters.Flags = CspProviderFlags.UseMachineKeyStore; RSACryptoServiceProvider rsaCrypto = new RSACryptoServiceProvider(2048, cspParameters); RSAParameters rsaParams = new RSAParameters(); rsaParams.Modulus = modulus; rsaParams.Exponent = exponent; rsaParams.D = d; rsaParams.P = p; rsaParams.Q = q; rsaParams.DP = dp; rsaParams.DQ = dq; rsaParams.InverseQ = inverseQ; rsaCrypto.ImportParameters(rsaParams); return rsaCrypto; } /// <summary> /// 获取下一个基本部分的字节长度。 /// </summary> /// <param name="binaryReader">二进制读取器。</param> /// <returns>返回下一个基本部分的字节长度。</returns> private static int GetIntegerSize(BinaryReader binaryReader) { byte byteValue = 0; byte lowByte = 0x00; byte highByte = 0x00; int count = 0; byteValue = binaryReader.ReadByte(); if (byteValue != 0x02) return 0; byteValue = binaryReader.ReadByte(); if (byteValue == 0x81) { count = binaryReader.ReadByte(); } else { if (byteValue == 0x82) { highByte = binaryReader.ReadByte(); lowByte = binaryReader.ReadByte(); byte[] modelInt = { lowByte, highByte, 0x00, 0x00 }; count = BitConverter.ToInt32(modelInt, 0); } else { count = byteValue; } } while (binaryReader.ReadByte() == 0x00) { count -= 1; } binaryReader.BaseStream.Seek(-1, SeekOrigin.Current); return count; } /// <summary> /// 获取指定CRT根证书的序列号。 /// </summary> /// <param name="rootCertPath">根证书路径。</param> /// <returns>返回该根证书序列号。</returns> public static string GetRootCertSN(string rootCertPath) { List<List<byte>> allCertByte = new List<List<byte>>(); string beginText = "-----BEGIN CERTIFICATE-----"; string endText = "-----END CERTIFICATE-----"; Encoding encoding = Encoding.UTF8; using (StreamReader streamRead = new StreamReader(rootCertPath, encoding)) { List<byte> bytes = null; while (!streamRead.EndOfStream) { string lineText = streamRead.ReadLine(); if (lineText.StartsWith(beginText)) { bytes = new List<byte>(); allCertByte.Add(bytes); if (lineText.Length > beginText.Length) bytes.AddRange(encoding.GetBytes(lineText.Replace(beginText, "").Trim())); } else if (!lineText.StartsWith(endText)) { bytes.AddRange(encoding.GetBytes(lineText.Trim())); } } } StringBuilder resultSN = new StringBuilder(); int index = 0; foreach (List<byte> item in allCertByte) { X509Certificate2 certificate = new X509Certificate2(item.ToArray()); if (certificate.SignatureAlgorithm.Value.StartsWith("1.2.840.113549.1.1")) { if (index > 0) resultSN.Append("_"); resultSN.Append(GetCertSN(certificate)); index++; } } return resultSN.ToString(); } /// <summary> /// 获取指定CRT证书文件的序列号。 /// </summary> /// <param name="certPath">证书路径。</param> /// <returns>返回该证书序列号。</returns> public static string GetCertSN(string certPath) { return GetCertSN(new X509Certificate2(certPath)); } /// <summary> /// 获取指定CRT证书的序列号。 /// </summary> /// <param name="cert">证书对象。</param> /// <returns>返回该证书序列号。</returns> private static string GetCertSN(X509Certificate2 cert) { string issuerName = new Regex(@", +C=").Replace(new Regex(@", +O=").Replace(new Regex(@", +OU=").Replace(cert.IssuerName.Name, ",OU="), ",O="), ",C="); return MD5Encrypt(issuerName + System.Numerics.BigInteger.Parse(string.Format("0{0}", cert.SerialNumber), System.Globalization.NumberStyles.AllowHexSpecifier), Encoding.UTF8, null).ToLower(); } } }
代码是CSDN上一位网友的提供的,原文链接:https://blog.csdn.net/aaa907638015/article/details/101246654
以上是关于记支付宝接口对接,涉及到提取证书SN号的解决方案的主要内容,如果未能解决你的问题,请参考以下文章