初始化仓库

This commit is contained in:
wangxiaowei
2025-04-22 14:09:52 +08:00
commit 8b100110bb
5155 changed files with 664201 additions and 0 deletions

View File

@ -0,0 +1,88 @@
using System;
using System.Text;
using System.Security.Cryptography;
namespace Alipay.EasySDK.Kernel.Util
{
public class AES
{
/// <summary>
/// 128位全0初始向量
/// </summary>
private static readonly byte[] AES_IV = InitIV(16);
/// <summary>
/// AES加密
/// </summary>
/// <param name="plainText">明文</param>
/// <param name="key">对称密钥</param>
/// <returns>密文</returns>
public static string Encrypt(string plainText, string key)
{
try
{
byte[] keyBytes = Convert.FromBase64String(key);
byte[] plainBytes = AlipayConstants.DEFAULT_CHARSET.GetBytes(plainText); ;
RijndaelManaged rijndatel = new RijndaelManaged
{
Key = keyBytes,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
IV = AES_IV
};
ICryptoTransform transform = rijndatel.CreateEncryptor(rijndatel.Key, rijndatel.IV);
byte[] cipherBytes = transform.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
return Convert.ToBase64String(cipherBytes);
}
catch (Exception e)
{
throw new Exception("AES加密失败plainText=" + plainText +
"keySize=" + key.Length + "" + e.Message, e);
}
}
/// <summary>
/// AES解密
/// </summary>
/// <param name="cipherText">密文</param>
/// <param name="key">对称密钥</param>
/// <returns>明文</returns>
public static string Decrypt(string cipherText, string key)
{
try
{
byte[] keyBytes = Convert.FromBase64String(key);
byte[] cipherBytes = Convert.FromBase64String(cipherText);
RijndaelManaged rijndatel = new RijndaelManaged
{
Key = keyBytes,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
IV = AES_IV
};
ICryptoTransform transform = rijndatel.CreateDecryptor(rijndatel.Key, rijndatel.IV);
byte[] plainBytes = transform.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length);
return AlipayConstants.DEFAULT_CHARSET.GetString(plainBytes);
}
catch (Exception e)
{
throw new Exception("AES解密失败ciphertext=" + cipherText +
"keySize=" + key.Length + "" + e.Message, e);
}
}
private static byte[] InitIV(int blockSize)
{
byte[] iv = new byte[blockSize];
for (int i = 0; i < blockSize; ++i)
{
iv[i] = 0x0;
}
return iv;
}
}
}

View File

@ -0,0 +1,326 @@
using System.Collections.Generic;
using System;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using System.Security.Cryptography;
using System.Text;
using System.Linq;
namespace Alipay.EasySDK.Kernel.Util
{
/// <summary>
/// 证书相关工具类
/// </summary>
public static class AntCertificationUtil
{
/// <summary>
/// 提取根证书序列号
/// </summary>
/// <param name="rootCertContent">根证书文本</param>
/// <returns>根证书序列号</returns>
public static string GetRootCertSN(string rootCertContent)
{
string rootCertSN = "";
try
{
List<X509Certificate> x509Certificates = ReadPemCertChain(rootCertContent);
foreach (X509Certificate cert in x509Certificates)
{
//只提取与指定算法类型匹配的证书的序列号
if (cert.SigAlgOid.StartsWith("1.2.840.113549.1.1", StringComparison.Ordinal))
{
string certSN = GetCertSN(cert);
if (string.IsNullOrEmpty(rootCertSN))
{
rootCertSN = certSN;
}
else
{
rootCertSN = rootCertSN + "_" + certSN;
}
}
}
}
catch (Exception ex)
{
throw new Exception("提取根证书序列号失败。" + ex.Message);
}
return rootCertSN;
}
/// <summary>
/// 反序列化证书文本
/// </summary>
/// <param name="certContent">证书文本</param>
/// <returns>X509Certificate证书对象</returns>
public static X509Certificate ParseCert(string certContent)
{
return new X509CertificateParser().ReadCertificate(Encoding.UTF8.GetBytes(certContent));
}
/// <summary>
/// 计算指定证书的序列号
/// </summary>
/// <param name="cert">证书</param>
/// <returns>序列号</returns>
public static string GetCertSN(X509Certificate cert)
{
string issuerDN = cert.IssuerDN.ToString();
//提取出的证书的issuerDN本身是以CN开头的则无需逆序直接返回
if (issuerDN.StartsWith("CN", StringComparison.Ordinal))
{
return CalculateMd5(issuerDN + cert.SerialNumber);
}
List<string> attributes = issuerDN.Split(',').ToList();
attributes.Reverse();
return CalculateMd5(string.Join(",", attributes.ToArray()) + cert.SerialNumber);
}
/// <summary>
/// 校验证书链是否可信
/// </summary>
/// <param name="certContent">需要验证的目标证书或者证书链文本</param>
/// <param name="rootCertContent">可信根证书列表文本</param>
/// <returns>true证书可信false证书不可信</returns>
public static bool IsTrusted(string certContent, string rootCertContent)
{
List<X509Certificate> certs = ReadPemCertChain(certContent);
List<X509Certificate> rootCerts = ReadPemCertChain(rootCertContent);
return VerifyCertChain(certs, rootCerts);
}
/// <summary>
/// 从证书链文本反序列化证书链集合
/// </summary>
/// <param name="cert">证书链文本</param>
/// <returns>证书链集合</returns>
private static List<X509Certificate> ReadPemCertChain(string cert)
{
System.Collections.ICollection collection = new X509CertificateParser().ReadCertificates(Encoding.UTF8.GetBytes(cert));
List<X509Certificate> result = new List<X509Certificate>();
foreach (var each in collection)
{
result.Add((X509Certificate)each);
}
return result;
}
/// <summary>
/// 将证书链按照完整的签发顺序进行排序,排序后证书链为:[issuerA, subjectA]-[issuerA, subjectB]-[issuerB, subjectC]-[issuerC, subjectD]...
/// </summary>
/// <param name="certChain">未排序的证书链</param>
/// <returns>true排序成功false证书链不完整</returns>
private static bool SortCertChain(List<X509Certificate> certChain)
{
//主题和证书的映射
Dictionary<X509Name, X509Certificate> subject2CertMap = new Dictionary<X509Name, X509Certificate>();
//签发者和证书的映射
Dictionary<X509Name, X509Certificate> issuer2CertMap = new Dictionary<X509Name, X509Certificate>();
//是否包含自签名证书
bool hasSelfSignedCert = false;
foreach (X509Certificate cert in certChain)
{
if (IsSelfSigned(cert))
{
if (hasSelfSignedCert)
{
//同一条证书链中只能有一个自签名证书
return false;
}
hasSelfSignedCert = true;
}
subject2CertMap[cert.SubjectDN] = cert;
issuer2CertMap[cert.IssuerDN] = cert;
}
List<X509Certificate> orderedCertChain = new List<X509Certificate>();
X509Certificate current = certChain[0];
AddressingUp(subject2CertMap, orderedCertChain, current);
AddressingDown(issuer2CertMap, orderedCertChain, current);
//说明证书链不完整
if (certChain.Count != orderedCertChain.Count)
{
return false;
}
//用排序后的结果覆盖传入的证书链集合
for (int i = 0; i < orderedCertChain.Count; i++)
{
certChain[i] = orderedCertChain[i];
}
return true;
}
private static bool IsSelfSigned(X509Certificate cert)
{
return cert.SubjectDN.Equivalent(cert.IssuerDN);
}
/// <summary>
/// 向上构造证书链
/// </summary>
/// <param name="subject2CertMap">主题与证书的映射</param>
/// <param name="orderedCertChain">储存排序后的证书链集合</param>
/// <param name="current">当前需要插入排序后的证书链集合中的证书</param>
private static void AddressingUp(Dictionary<X509Name, X509Certificate> subject2CertMap,
List<X509Certificate> orderedCertChain, X509Certificate current)
{
orderedCertChain.Insert(0, current);
if (IsSelfSigned(current))
{
return;
}
if (!subject2CertMap.ContainsKey(current.IssuerDN))
{
return;
}
X509Certificate issuer = subject2CertMap[current.IssuerDN];
AddressingUp(subject2CertMap, orderedCertChain, issuer);
}
/// <summary>
/// 向下构造证书链
/// </summary>
/// <param name="issuer2CertMap">签发者和证书的映射</param>
/// <param name="certChain">储存排序后的证书链集合</param>
/// <param name="current">当前需要插入排序后的证书链集合中的证书</param>
private static void AddressingDown(Dictionary<X509Name, X509Certificate> issuer2CertMap,
List<X509Certificate> certChain, X509Certificate current)
{
if (!issuer2CertMap.ContainsKey(current.SubjectDN))
{
return;
}
X509Certificate subject = issuer2CertMap[current.SubjectDN];
if (IsSelfSigned(subject))
{
return;
}
certChain.Add(subject);
AddressingDown(issuer2CertMap, certChain, subject);
}
/// <summary>
/// 验证证书是否是信任证书库中的证书签发的
/// </summary>
/// <param name="cert">待验证证书</param>
/// <param name="rootCerts">可信根证书列表</param>
/// <returns>true验证通过false验证不通过</returns>
private static bool VerifyCert(X509Certificate cert, List<X509Certificate> rootCerts)
{
if (!cert.IsValidNow)
{
return false;
}
Dictionary<X509Name, X509Certificate> subject2CertMap = new Dictionary<X509Name, X509Certificate>();
foreach (X509Certificate root in rootCerts)
{
subject2CertMap[root.SubjectDN] = root;
}
X509Name issuerDN = cert.IssuerDN;
if (!subject2CertMap.ContainsKey(issuerDN))
{
return false;
}
X509Certificate issuer = subject2CertMap[issuerDN];
try
{
AsymmetricKeyParameter publicKey = issuer.GetPublicKey();
cert.Verify(publicKey);
}
catch (Exception ex)
{
Console.WriteLine("证书验证出现异常。" + ex.Message);
return false;
}
return true;
}
/// <summary>
/// 验证证书列表
/// </summary>
/// <param name="certs">待验证的证书列表</param>
/// <param name="rootCerts">可信根证书列表</param>
/// <returns>true验证通过false验证不通过</returns>
private static bool VerifyCertChain(List<X509Certificate> certs, List<X509Certificate> rootCerts)
{
//证书列表排序,形成排序后的证书链
bool sorted = SortCertChain(certs);
if (!sorted)
{
//不是完整的证书链
return false;
}
//先验证第一个证书是不是信任库中证书签发的
X509Certificate previous = certs[0];
bool firstOK = VerifyCert(previous, rootCerts);
if (!firstOK || certs.Count == 1)
{
return firstOK;
}
//验证证书链
for (int i = 1; i < certs.Count; i++)
{
try
{
X509Certificate cert = certs[i];
if (!cert.IsValidNow)
{
return false;
}
//用上级证书的公钥验证本证书是否是上级证书签发的
cert.Verify(previous.GetPublicKey());
previous = cert;
}
catch (Exception ex)
{
//证书链验证失败
Console.WriteLine("证书链验证失败。" + ex.Message);
return false;
}
}
return true;
}
private static string CalculateMd5(string input)
{
using (MD5 md5 = new MD5CryptoServiceProvider())
{
string result = "";
byte[] bytes = md5.ComputeHash(Encoding.GetEncoding("utf-8").GetBytes(input));
for (int i = 0; i < bytes.Length; i++)
{
result += bytes[i].ToString("x2");
}
return result;
}
}
/// <summary>
/// 从证书中提取公钥并转换为PEM编码
/// </summary>
/// <param name="input">证书</param>
/// <returns>PEM编码公钥</returns>
public static string ExtractPemPublicKeyFromCert(X509Certificate input)
{
SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(input.GetPublicKey());
return Convert.ToBase64String(subjectPublicKeyInfo.GetDerEncoded());
}
}
}

View File

@ -0,0 +1,34 @@
using System;
namespace Alipay.EasySDK.Kernel.Util
{
/// <summary>
/// 参数校验类
/// </summary>
public static class ArgumentValidator
{
public static void CheckArgument(bool expression, string errorMessage)
{
if (!expression)
{
throw new Exception(errorMessage);
}
}
public static void CheckNotNull(object value, string errorMessage)
{
if (value == null)
{
throw new Exception(errorMessage);
}
}
public static void EnsureNull(object value, string errorMessage)
{
if (value != null)
{
throw new Exception(errorMessage);
}
}
}
}

View File

@ -0,0 +1,63 @@
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
namespace Alipay.EasySDK.Kernel.Util
{
/// <summary>
/// 字典工具类
/// </summary>
public static class DictionaryUtil
{
/// <summary>
/// 将字典各层次Value中的JObject和JArray转换成C#标准库中的Dictionary和List
/// </summary>
/// <param name="iputObj">输入字典</param>
/// <returns>转换后的输出字典</returns>
public static Dictionary<string, object> ObjToDictionary(Dictionary<string, object> iputObj)
{
Dictionary<string, object> result = new Dictionary<string, object>();
foreach (string key in iputObj.Keys)
{
if (iputObj[key] is JArray)
{
List<object> objList = ((JArray)iputObj[key]).ToObject<List<object>>();
result.Add(key, ConvertList(objList));
}
else if (iputObj[key] is JObject)
{
Dictionary<string, object> dicObj = ((JObject)iputObj[key]).ToObject<Dictionary<string, object>>();
result.Add(key, ObjToDictionary(dicObj));
}
else
{
result.Add(key, iputObj[key]);
}
}
return result;
}
private static List<object> ConvertList(List<object> inputList)
{
List<object> result = new List<object>();
foreach (var obj in inputList)
{
if (obj is JArray)
{
List<object> listObj = ((JArray)obj).ToObject<List<object>>();
result.Add(ConvertList(listObj));
}
else if (obj is JObject)
{
Dictionary<string, object> dicObj = ((JObject)obj).ToObject<Dictionary<string, object>>();
result.Add(ObjToDictionary(dicObj));
}
else
{
result.Add(obj);
}
}
return result;
}
}
}

View File

@ -0,0 +1,53 @@
using System.Collections.Generic;
using Tea;
using Newtonsoft.Json;
namespace Alipay.EasySDK.Kernel.Util
{
/// <summary>
/// JSON工具类
/// </summary>
public class JsonUtil
{
/// <summary>
/// 将字典集合转换为Json字符串转换过程中对于TeaModel使用标注的字段名称而不是字段的变量名
/// </summary>
/// <param name="input">字典集合</param>
/// <returns>Json字符串</returns>
public static string ToJsonString(IDictionary<string, object> input)
{
IDictionary<string, object> result = new Dictionary<string, object>();
foreach (var pair in input)
{
if (pair.Value is TeaModel)
{
result.Add(pair.Key, GetTeaModelMap((TeaModel)pair.Value));
}
else
{
result.Add(pair.Key, pair.Value);
}
}
return JsonConvert.SerializeObject(result);
}
private static IDictionary<string, object> GetTeaModelMap(TeaModel teaModel)
{
IDictionary<string, object> result = new Dictionary<string, object>();
IDictionary<string, object> teaModelMap = teaModel.ToMap();
foreach (var pair in teaModelMap)
{
if (pair.Value is TeaModel)
{
result.Add(pair.Key, GetTeaModelMap((TeaModel)pair.Value));
}
else
{
result.Add(pair.Key, pair.Value);
}
}
return result;
}
}
}

View File

@ -0,0 +1,79 @@
using System;
using System.Text;
using System.IO;
namespace Alipay.EasySDK.Kernel.Util
{
/// <summary>
/// HTTP multipart/form-data格式相关工具类
/// </summary>
public static class MultipartUtil
{
/// <summary>
/// 获取Multipart分界符
/// </summary>
/// <param name="boundary">用作分界的随机字符串</param>
/// <returns>Multipart分界符</returns>
public static byte[] GetEntryBoundary(string boundary)
{
return Encoding.UTF8.GetBytes("\r\n--" + boundary + "\r\n");
}
/// <summary>
/// 获取Multipart结束标记
/// </summary>
/// <param name="boundary">用作分界的随机字符串</param>
/// <returns>Multipart结束标记</returns>
public static byte[] GetEndBoundary(string boundary)
{
return Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
}
/// <summary>
/// 获取Multipart中的文本参数结构
/// </summary>
/// <param name="fieldName">字段名称</param>
/// <param name="fieldValue">字段值</param>
/// <returns>文本参数结构</returns>
public static byte[] GetTextEntry(string fieldName, string fieldValue)
{
string entry = "Content-Disposition:form-data;name=\""
+ fieldName
+ "\"\r\nContent-Type:text/plain\r\n\r\n"
+ fieldValue;
return AlipayConstants.DEFAULT_CHARSET.GetBytes(entry);
}
/// <summary>
/// 获取Multipart中的文件参数结构不含文件内容只有文件元数据
/// </summary>
/// <param name="fieldName">字段名称</param>
/// <param name="filePath">文件路径</param>
/// <returns>文件参数结构(不含文件内容)</returns>
public static byte[] GetFileEntry(String fieldName, String filePath)
{
ArgumentValidator.CheckArgument(File.Exists(filePath),
Path.GetFullPath(filePath) + "文件不存在");
ArgumentValidator.CheckArgument(Path.GetFileName(filePath).Contains("."),
"文件名必须带上正确的扩展名");
String entry = "Content-Disposition:form-data;name=\""
+ fieldName
+ "\";filename=\""
+ Path.GetFileName(filePath)
+ "\"\r\nContent-Type:application/octet-stream"
+ "\r\n\r\n";
return AlipayConstants.DEFAULT_CHARSET.GetBytes(entry);
}
/// <summary>
/// 往指定流中写入整个字节数组
/// </summary>
/// <param name="stream">流</param>
/// <param name="content">字节数组</param>
public static void WriteToStream(Stream stream, byte[] content)
{
stream.Write(content, 0, content.Length);
}
}
}

View File

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Alipay.EasySDK.Kernel.Util
{
/// <summary>
/// 生成页面信息辅助类
/// </summary>
public static class PageUtil
{
/// <summary>
/// 生成表单
/// </summary>
/// <param name="actionUrl">表单提交链接</param>
/// <param name="parameters">表单参数</param>
/// <returns>表单字符串</returns>
public static string BuildForm(string actionUrl, IDictionary<string, string> parameters)
{
return "<form name=\"punchout_form\" method=\"post\" action=\""
+ actionUrl
+ "\">\n"
+ BuildHiddenFields(parameters)
+ "<input type=\"submit\" value=\"立即支付\" style=\"display:none\" >\n"
+ "</form>\n"
+ "<script>document.forms[0].submit();</script>";
}
private static string BuildHiddenFields(IDictionary<string, string> parameters)
{
if (parameters == null || parameters.Count == 0)
{
return "";
}
StringBuilder stringBuilder = new StringBuilder();
foreach (var pair in parameters)
{
if (pair.Key == null || pair.Value == null)
{
continue;
}
stringBuilder.Append(BuildHiddenField(pair.Key, pair.Value));
}
return stringBuilder.ToString();
}
private static string BuildHiddenField(string key, string value)
{
StringBuilder builder = new StringBuilder(64);
builder.Append("<input type=\"hidden\" name=\"");
builder.Append(key);
builder.Append("\" value=\"");
//转义双引号
String a = value.Replace("\"", "&quot;");
builder.Append(a).Append("\">\n");
return builder.ToString();
}
}
}

View File

@ -0,0 +1,31 @@
using System.Reflection;
using Tea;
namespace Alipay.EasySDK.Kernel.Util
{
/// <summary>
/// 响应检查工具类
/// </summary>
public class ResponseChecker
{
public const string SUB_CODE_FIELD_NAME = "SubCode";
/// <summary>
/// 判断一个请求返回的响应是否成功
/// </summary>
/// <param name="response">响应对象</param>
/// <returns>true成功false失败</returns>
public static bool Success(TeaModel response)
{
PropertyInfo propertyInfo = response.GetType().GetProperty(SUB_CODE_FIELD_NAME);
if (propertyInfo == null)
{
//没有SubCode属性的响应对象通常是那些无需跟网关远程通信的API只要本地执行完成都视为成功
return true;
}
string subCode = (string)propertyInfo.GetValue(response);
return string.IsNullOrEmpty(subCode);
}
}
}

View File

@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
namespace Alipay.EasySDK.Kernel.Util
{
/// <summary>
/// 待验签原文提取器
/// 注此处不可使用JSON反序列化工具进行提取会破坏原有格式对于签名而言差个空格都会验签不通过
/// </summary>
public class SignContentExtractor
{
/// <summary>
/// 左大括号
/// </summary>
public const char LEFT_BRACE = '{';
/// <summary>
/// 右大括号
/// </summary>
public const char RIGHT_BRACE = '}';
/// <summary>
/// 双引号
/// </summary>
public const char DOUBLE_QUOTES = '"';
/// <summary>
/// 获取待验签的原文
/// </summary>
/// <param name="body">网关的整体响应字符串</param>
/// <param name="method">本次调用的OpenAPI接口名称</param>
/// <returns>待验签的原文</returns>
public static string GetSignSourceData(string body, string method)
{
string rootNode = method.Replace(".", "_") + AlipayConstants.RESPONSE_SUFFIX;
string errorRootNode = AlipayConstants.ERROR_RESPONSE;
int indexOfRootNode = body.IndexOf(rootNode, StringComparison.Ordinal);
int indexOfErrorRoot = body.IndexOf(errorRootNode, StringComparison.Ordinal);
string result = null;
if (indexOfRootNode > 0)
{
result = ParseSignSourceData(body, rootNode, indexOfRootNode);
}
else if (indexOfErrorRoot > 0)
{
result = ParseSignSourceData(body, errorRootNode, indexOfErrorRoot);
}
return result;
}
private static string ParseSignSourceData(string body, string rootNode, int indexOfRootNode)
{
int signDataStartIndex = indexOfRootNode + rootNode.Length + 2;
int indexOfSign = body.IndexOf("\"" + AlipayConstants.SIGN_FIELD + "\"", StringComparison.Ordinal);
if (indexOfSign < 0)
{
return null;
}
SignSourceData signSourceData = ExtractSignContent(body, signDataStartIndex);
//如果提取的待验签原始内容后还有rootNode
if (body.LastIndexOf(rootNode, StringComparison.Ordinal) > signSourceData.EndIndex)
{
throw new Exception("检测到响应报文中有重复的" + rootNode + ",验签失败。");
}
return signSourceData.SourceData;
}
private static SignSourceData ExtractSignContent(string str, int begin)
{
if (str == null)
{
return null;
}
int beginIndex = ExtractBeginPosition(str, begin);
if (beginIndex >= str.Length)
{
return null;
}
int endIndex = ExtractEndPosition(str, beginIndex);
return new SignSourceData()
{
SourceData = str.Substring(beginIndex, endIndex - beginIndex),
BeginIndex = beginIndex,
EndIndex = endIndex
};
}
private static int ExtractBeginPosition(string responseString, int begin)
{
int beginPosition = begin;
//找到第一个左大括号对应响应的是JSON对象的情况普通调用OpenAPI响应明文
//或者双引号对应响应的是JSON字符串的情况加密调用OpenAPI响应Base64串作为待验签内容的起点
while (beginPosition < responseString.Length
&& responseString[beginPosition] != LEFT_BRACE
&& responseString[beginPosition] != DOUBLE_QUOTES)
{
++beginPosition;
}
return beginPosition;
}
private static int ExtractEndPosition(string responseString, int beginPosition)
{
//提取明文验签内容终点
if (responseString[beginPosition] == LEFT_BRACE)
{
return ExtractJsonObjectEndPosition(responseString, beginPosition);
}
//提取密文验签内容终点
else
{
return ExtractJsonBase64ValueEndPosition(responseString, beginPosition);
}
}
private static int ExtractJsonBase64ValueEndPosition(string responseString, int beginPosition)
{
for (int index = beginPosition; index < responseString.Length; ++index)
{
//找到第2个双引号作为终点由于中间全部是Base64编码的密文所以不会有干扰的特殊字符
if (responseString[index] == DOUBLE_QUOTES && index != beginPosition)
{
return index + 1;
}
}
//如果没有找到第2个双引号说明验签内容片段提取失败直接尝试选取剩余整个响应字符串进行验签
return responseString.Length;
}
private static int ExtractJsonObjectEndPosition(string responseString, int beginPosition)
{
//记录当前尚未发现配对闭合的大括号
LinkedList<char> braces = new LinkedList<char>();
//记录当前字符是否在双引号中
bool inQuotes = false;
//记录当前字符前面连续的转义字符个数
int consecutiveEscapeCount = 0;
//从待验签字符的起点开始遍历后续字符串,找出待验签字符串的终止点,终点即是与起点{配对的}
for (int index = beginPosition; index < responseString.Length; ++index)
{
//提取当前字符
char currentChar = responseString[index];
//如果当前字符是"且前面有偶数个转义标记0也是偶数
if (currentChar == DOUBLE_QUOTES && consecutiveEscapeCount % 2 == 0)
{
//是否在引号中的状态取反
inQuotes = !inQuotes;
}
//如果当前字符是{且不在引号中
else if (currentChar == LEFT_BRACE && !inQuotes)
{
//将该{加入未闭合括号中
braces.AddLast(LEFT_BRACE);
}
//如果当前字符是}且不在引号中
else if (currentChar == RIGHT_BRACE && !inQuotes)
{
//弹出一个未闭合括号
braces.RemoveLast();
//如果弹出后,未闭合括号为空,说明已经找到终点
if (braces.Count == 0)
{
return index + 1;
}
}
//如果当前字符是转义字符
if (currentChar == '\\')
{
//连续转义字符个数+1
++consecutiveEscapeCount;
}
else
{
//连续转义字符个数置0
consecutiveEscapeCount = 0;
}
}
//如果没有找到配对的闭合括号,说明验签内容片段提取失败,直接尝试选取剩余整个响应字符串进行验签
return responseString.Length;
}
/// <summary>
/// 从响应字符串中提取到的待验签原始内容
/// </summary>
public class SignSourceData
{
/// <summary>
/// 待验签原始内容
/// </summary>
public string SourceData { get; set; }
/// <summary>
/// 待验签原始内容在响应字符串中的起始位置
/// </summary>
public int BeginIndex { get; set; }
/// <summary>
/// 待验签原始内容在响应字符串中的结束位置
/// </summary>
public int EndIndex { get; set; }
}
}
}

View File

@ -0,0 +1,260 @@
using System;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using System.Collections.Generic;
namespace Alipay.EasySDK.Kernel.Util
{
/// <summary>
/// SHA256WithRSA签名器
/// </summary>
public class Signer
{
/// <summary>
/// 计算签名
/// </summary>
/// <param name="content">待签名的内容</param>
/// <param name="privateKeyPem">私钥</param>
/// <returns>签名值的Base64串</returns>
public static string Sign(string content, string privateKeyPem)
{
try
{
using (RSACryptoServiceProvider rsaService = BuildRSAServiceProvider(Convert.FromBase64String(privateKeyPem)))
{
byte[] data = AlipayConstants.DEFAULT_CHARSET.GetBytes(content);
byte[] sign = rsaService.SignData(data, "SHA256");
return Convert.ToBase64String(sign);
}
}
catch (Exception e)
{
string errorMessage = "签名遭遇异常content=" + content + " privateKeySize=" + privateKeyPem.Length + " reason=" + e.Message;
Console.WriteLine(errorMessage);
throw new Exception(errorMessage, e);
}
}
/// <summary>
/// 验证签名
/// </summary>
/// <param name="content">待验签的内容</param>
/// <param name="sign">签名值的Base64串</param>
/// <param name="publicKeyPem">支付宝公钥</param>
/// <returns>true验证成功false验证失败</returns>
public static bool Verify(string content, string sign, string publicKeyPem)
{
try
{
using (RSACryptoServiceProvider rsaService = new RSACryptoServiceProvider())
{
rsaService.PersistKeyInCsp = false;
rsaService.ImportParameters(ConvertFromPemPublicKey(publicKeyPem));
return rsaService.VerifyData(AlipayConstants.DEFAULT_CHARSET.GetBytes(content),
"SHA256", Convert.FromBase64String(sign));
}
}
catch (Exception e)
{
string errorMessage = "验签遭遇异常content=" + content + " sign=" + sign +
" publicKey=" + publicKeyPem + " reason=" + e.Message;
Console.WriteLine(errorMessage);
throw new Exception(errorMessage, e);
}
}
/// <summary>
/// 对参数集合进行验签
/// </summary>
/// <param name="parameters">参数集合</param>
/// <param name="publicKeyPem">支付宝公钥</param>
/// <returns>true验证成功false验证失败</returns>
public static bool VerifyParams(Dictionary<string, string> parameters, string publicKeyPem)
{
string sign = parameters[AlipayConstants.SIGN_FIELD];
parameters.Remove(AlipayConstants.SIGN_FIELD);
parameters.Remove(AlipayConstants.SIGN_TYPE_FIELD);
string content = GetSignContent(parameters);
return Verify(content, sign, publicKeyPem);
}
private static string GetSignContent(IDictionary<string, string> parameters)
{
// 把字典按Key的字母顺序排序
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters, StringComparer.Ordinal);
IEnumerator<KeyValuePair<string, string>> iterator = sortedParams.GetEnumerator();
// 把所有参数名和参数值串在一起
StringBuilder query = new StringBuilder("");
while (iterator.MoveNext())
{
string key = iterator.Current.Key;
string value = iterator.Current.Value;
query.Append(key).Append("=").Append(value).Append("&");
}
string content = query.ToString().Substring(0, query.Length - 1);
return content;
}
private static RSAParameters ConvertFromPemPublicKey(string pemPublickKey)
{
if (string.IsNullOrEmpty(pemPublickKey))
{
throw new Exception("PEM格式公钥不可为空。");
}
//移除干扰文本
pemPublickKey = pemPublickKey.Replace("-----BEGIN PUBLIC KEY-----", "")
.Replace("-----END PUBLIC KEY-----", "").Replace("\n", "").Replace("\r", "");
byte[] keyData = Convert.FromBase64String(pemPublickKey);
bool keySize1024 = (keyData.Length == 162);
bool keySize2048 = (keyData.Length == 294);
if (!(keySize1024 || keySize2048))
{
throw new Exception("公钥长度只支持1024和2048。");
}
byte[] pemModulus = (keySize1024 ? new byte[128] : new byte[256]);
byte[] pemPublicExponent = new byte[3];
Array.Copy(keyData, (keySize1024 ? 29 : 33), pemModulus, 0, (keySize1024 ? 128 : 256));
Array.Copy(keyData, (keySize1024 ? 159 : 291), pemPublicExponent, 0, 3);
RSAParameters para = new RSAParameters
{
Modulus = pemModulus,
Exponent = pemPublicExponent
};
return para;
}
private static RSACryptoServiceProvider BuildRSAServiceProvider(byte[] privateKey)
{
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
byte bt = 0;
ushort twobytes = 0;
int elems = 0;
//set up stream to decode the asn.1 encoded RSA private key
//wrap Memory Stream with BinaryReader for easy reading
using (BinaryReader binaryReader = new BinaryReader(new MemoryStream(privateKey)))
{
twobytes = binaryReader.ReadUInt16();
//data read as little endian order (actual data order for Sequence is 30 81)
if (twobytes == 0x8130)
{
//advance 1 byte
binaryReader.ReadByte();
}
else if (twobytes == 0x8230)
{
//advance 2 bytes
binaryReader.ReadInt16();
}
else
{
return null;
}
twobytes = binaryReader.ReadUInt16();
//version number
if (twobytes != 0x0102)
{
return null;
}
bt = binaryReader.ReadByte();
if (bt != 0x00)
{
return null;
}
//all private key components are Integer sequences
elems = GetIntegerSize(binaryReader);
MODULUS = binaryReader.ReadBytes(elems);
elems = GetIntegerSize(binaryReader);
E = binaryReader.ReadBytes(elems);
elems = GetIntegerSize(binaryReader);
D = binaryReader.ReadBytes(elems);
elems = GetIntegerSize(binaryReader);
P = binaryReader.ReadBytes(elems);
elems = GetIntegerSize(binaryReader);
Q = binaryReader.ReadBytes(elems);
elems = GetIntegerSize(binaryReader);
DP = binaryReader.ReadBytes(elems);
elems = GetIntegerSize(binaryReader);
DQ = binaryReader.ReadBytes(elems);
elems = GetIntegerSize(binaryReader);
IQ = binaryReader.ReadBytes(elems);
//create RSACryptoServiceProvider instance and initialize with public key
RSACryptoServiceProvider rsaService = new RSACryptoServiceProvider();
RSAParameters rsaParams = new RSAParameters
{
Modulus = MODULUS,
Exponent = E,
D = D,
P = P,
Q = Q,
DP = DP,
DQ = DQ,
InverseQ = IQ
};
rsaService.ImportParameters(rsaParams);
return rsaService;
}
}
private static int GetIntegerSize(BinaryReader binaryReader)
{
byte bt = 0;
byte lowbyte = 0x00;
byte highbyte = 0x00;
int count = 0;
bt = binaryReader.ReadByte();
//expect integer
if (bt != 0x02)
{
return 0;
}
bt = binaryReader.ReadByte();
if (bt == 0x81)
{
//data size in next byte
count = binaryReader.ReadByte();
}
else if (bt == 0x82)
{
//data size in next 2 bytes
highbyte = binaryReader.ReadByte();
lowbyte = binaryReader.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
//we already have the data size
count = bt;
}
while (binaryReader.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= 1;
}
//last ReadByte wasn't a removed zero, so back up a byte
binaryReader.BaseStream.Seek(-1, SeekOrigin.Current);
return count;
}
}
}