• C#平安对接之SHA256withRSA,以及AES128加密(平安仅java文档,有需要使用c#对接的可以参考下)


    这边有个需求需要对接平安积分,但是操蛋的是对接文档中给的示例是java的,并且!!!关于加密的说明只是很笼统的说了下,具体怎么实现还要要看java示例。坑的雅痞。 

    下面记录几个接口用到的加密及验签方式:

    首先说明几个必要的参数,是加密、加签、包括解密解签需要使用到的,

    其次只会贴和业务无关的代码。

    接口方提供:

    1. 商户密钥:merchantKey
    2. 私钥-加密用:privateKey
    3. 公钥-解密用:publicKey
    4. 测试商户号

    1、签名用商户侧RSA私钥进行SHA-256,生成签名:

    使用的加密方式为:SHA1WithRSA,先来看文档:

    这里需要对公共请求参数进行处理,

    参数格式很好理解,这里只展示部分业务参数,仅用于加密说明

    1. {
    2. sign:签名,
    3. bizData:业务请求参数
    4. }

    提供的java示例有兴趣的可以看下:

    有道云笔记http://note.youdao.com/noteshare?id=29bb960b01c1db6417462ab86ed81504&sub=BD9CFD6FB3AE4FBD8E2F6EFDA04672CB

    加签规则如下:

    1. bizData字段设置为未加密原始json字符串;
    2. 去掉sign参数字段;
    3. 将报文json字符串 按照key字典序升序排序(bizData中json字符串内容无需排序)

    1、bizData字段设置为未加密原始json字符串;

     req.bizData = bizreq.ToJson();//将bizdata转json

    2、去掉sign参数字段;因为实例化业务参数的时候,没有对sign参数进行赋值,所以可以使用下面方法去除sign参数字段

    调用方式为:

      string sign_ = common.signfilter<PinganBaseReq>(req);
    1. ///
    2. /// 签名过滤[过滤对象为空/null]-这里用来去除对象中的签名
    3. ///
    4. public string signfilter<T>(T signobject)
    5. {
    6. JsonSerializerSettings jsonSetting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
    7. string jsonOut = JsonConvert.SerializeObject(signobject, Formatting.Indented, jsonSetting);
    8. return jsonOut;
    9. }

    3、将报文json字符串 按照key字典序升序排序(bizData中json字符串内容无需排序)

    调用方式为:

       req.sign = common.signmethod(sign_);z//sign_为第二步的变量
    1. public string signmethod(string strjson)
    2. {
    3. //json字符串升序排列
    4. strjson = StortJson(strjson);
    5. //去除特殊字符,不然验签不成功
    6. strjson = System.Text.RegularExpressions.Regex.Replace(strjson, "\\s*|\t|\r|\n", "");
    7. //SHA256加密-并转base64
    8. string strjson_ = RSAWithSha256.Sign(strjson, privateKey);一个是签名字符串,一个是私钥
    9. return strjson_;
    10. }
    11. ///
    12. /// 将报文json字符串根据key进行升序排列
    13. ///
    14. ///
    15. ///
    16. public string StortJson(string json)
    17. {
    18. var dic = JsonConvert.DeserializeObjectstring, object>>(json);
    19. SortedDictionary<string, object> keyValues = new SortedDictionary<string, object>(dic);
    20. keyValues.OrderBy(m => m.Key);//升序 把Key换成Value 就是对Value进行排序
    21. return JsonConvert.SerializeObject(keyValues);
    22. }

    RSAWithSha256类:

    1. public class RSAWithSha256
    2. {
    3. ///
    4. /// RSA私钥,从Java格式转.net格式(不依赖第三方包)
    5. ///
    6. /// 私钥
    7. ///
    8. private static string RSAPrivateKeyJava2DotNet(string privateKey)
    9. {
    10. RsaPrivateCrtKeyParameters privateKeyParam = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey));
    11. return string.Format("{0}{1}

      {2}

      {3}{4}{5}{6}{7}
      "
      ,
    12. Convert.ToBase64String(privateKeyParam.Modulus.ToByteArrayUnsigned()),
    13. Convert.ToBase64String(privateKeyParam.PublicExponent.ToByteArrayUnsigned()),
    14. Convert.ToBase64String(privateKeyParam.P.ToByteArrayUnsigned()),
    15. Convert.ToBase64String(privateKeyParam.Q.ToByteArrayUnsigned()),
    16. Convert.ToBase64String(privateKeyParam.DP.ToByteArrayUnsigned()),
    17. Convert.ToBase64String(privateKeyParam.DQ.ToByteArrayUnsigned()),
    18. Convert.ToBase64String(privateKeyParam.QInv.ToByteArrayUnsigned()),
    19. Convert.ToBase64String(privateKeyParam.Exponent.ToByteArrayUnsigned()));
    20. }
    21. ///
    22. /// 256私钥签名
    23. ///
    24. ///
    25. ///
    26. ///
    27. public static string Sign(string contentForSign, string privateKey)
    28. {
    29. var netKey = RSAPrivateKeyJava2DotNet(privateKey); //转换成适用于.net的私钥
    30. //var rsa = FromXmlString(netKey); //.net core2.2及其以下版本使用,重写FromXmlString(string)方法
    31. var rsa = new RSACryptoServiceProvider();
    32. rsa.FromXmlString(netKey); //.net core3.0直接使用,不需要重写
    33. var rsaClear = new RSACryptoServiceProvider();
    34. var paras = rsa.ExportParameters(true);
    35. rsaClear.ImportParameters(paras); //签名返回
    36. using (var sha256 = new SHA256CryptoServiceProvider())
    37. {
    38. var signData = rsa.SignData(Encoding.Default.GetBytes(contentForSign), sha256);
    39. return Convert.ToBase64String(signData);
    40. }
    41. }
    42. ///
    43. /// SHA1签名-用于联合登录
    44. ///
    45. ///
    46. ///
    47. ///
    48. public static string SignSHA1(string contentForSign, string privateKey)
    49. {
    50. var netKey = RSAPrivateKeyJava2DotNet(privateKey); //转换成适用于.net的私钥
    51. //var rsa = FromXmlString(netKey); //.net core2.2及其以下版本使用,重写FromXmlString(string)方法
    52. var rsa = new RSACryptoServiceProvider();
    53. rsa.FromXmlString(netKey); //.net core3.0直接使用,不需要重写
    54. var rsaClear = new RSACryptoServiceProvider();
    55. var paras = rsa.ExportParameters(true);
    56. rsaClear.ImportParameters(paras); //签名返回
    57. using (var sha1 = new SHA1CryptoServiceProvider())
    58. {
    59. var signData = rsa.SignData(Encoding.Default.GetBytes(contentForSign), sha1);
    60. return Convert.ToBase64String(signData);
    61. }
    62. }
    63. ///
    64. /// RSA公钥,从Java格式转.net格式(不依赖第三方包)
    65. ///
    66. ///
    67. ///
    68. private static string RSAPublicKeyJava2DotNet(string publicKey)
    69. {
    70. RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey));
    71. return string.Format("{0}{1}",
    72. Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()),
    73. Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned()));
    74. }
    75. ///
    76. /// RSA签名验证
    77. ///
    78. /// 签名
    79. /// 验证的字符串
    80. /// 公钥
    81. /// 是否相同,true验证成功,false验证失败。
    82. public static bool VerifySignature(string encryptSource, string compareString, string publicKey)
    83. {
    84. try
    85. {
    86. //.net core2.2及其以下版本使用,重写FromXmlString(string)方法
    87. //using (RSACryptoServiceProvider rsa = FromXmlString(RSAPublicKeyJava2DotNet(publicKey)))
    88. using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
    89. {
    90. rsa.FromXmlString(RSAPublicKeyJava2DotNet(publicKey)); //.net core3.0直接使用,不需要重写
    91. byte[] signature = Convert.FromBase64String(encryptSource);
    92. SHA256Managed sha256 = new SHA256Managed();
    93. RSAPKCS1SignatureDeformatter df = new RSAPKCS1SignatureDeformatter(rsa);
    94. df.SetHashAlgorithm("SHA256");
    95. byte[] compareByte = sha256.ComputeHash(Encoding.Default.GetBytes(compareString));
    96. return df.VerifySignature(compareByte, signature);
    97. }
    98. }
    99. catch (Exception)
    100. {
    101. return false;
    102. }
    103. }
    104. }

    2、bizData加密:

    对bizData业务参数原json字符串进行AES128加密(加密数据Base64编码)

    模式: ​ AES/CBC/PKCS5Padding , 秘钥由平安付提供。

    注意:这里和其他aes接口加密不同的是需要计算iv,客户提供的ivParameter有两种, 这里两种都会介绍

    这里加密前需要先对商户密钥转换成字节组

    调用方式为:

      req.bizData = common.AesEncrypt(req.bizData, common.toAesKey());
    1. public string toAesKey()
    2. {
    3. int aesKeyBit = 128;
    4. int aesKeySize = aesKeyBit / 8;
    5. // 16位编码转为字节数组
    6. byte[] bs = strToToHexByte(merchantKey);
    7. byte[] aesKeyBytes = new byte[aesKeySize];
    8. Array.Copy(bs, aesKeyBytes, aesKeySize);
    9. int merchantKeySize = bs.Length;
    10. if (aesKeySize > merchantKeySize)
    11. {
    12. // 填充
    13. for (int index = merchantKeySize; index < aesKeySize; index++)
    14. {
    15. aesKeyBytes[index] = (byte)index;
    16. }
    17. }
    18. return base64encode(aesKeyBytes);
    19. }
    20. private byte[] strToToHexByte(string hexString)
    21. {
    22. hexString = hexString.Replace(" ", "");
    23. if ((hexString.Length % 2) != 0)
    24. hexString += " ";
    25. byte[] returnBytes = new byte[hexString.Length / 2];
    26. for (int i = 0; i < returnBytes.Length; i++)
    27. returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
    28. return returnBytes;
    29. }

    //使用客户提供的iv字节组

    private static byte[] IV_VALUE = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 };

    //加密

    1. public string AesEncrypt(string encryptStr, string key)
    2. {
    3. byte[] keyArray = Convert.FromBase64String(key);
    4. byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(encryptStr);//然后你看走完这部,数据不一样了看到没
    5. RijndaelManaged rDel = new RijndaelManaged();
    6. rDel.Key = keyArray;
    7. rDel.Mode = CipherMode.CBC;
    8. rDel.Padding = PaddingMode.PKCS7;
    9. rDel.IV = IV_VALUE;
    10. ICryptoTransform cTransform = rDel.CreateEncryptor();
    11. byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
    12. return Convert.ToBase64String(resultArray, 0, resultArray.Length);
    13. }

    //解密--平安返回的业务参数也是加密的,这里需要通过解密处理

    1. ///
    2. /// AES 解密
    3. ///
    4. /// 明文(待解密)
    5. /// 密文
    6. ///
    7. public string AESDencrypt(string encryptStr, string key)
    8. {
    9. try
    10. {
    11. encryptStr = CorrectionCiphertext(encryptStr);
    12. byte[] keyArray = Convert.FromBase64String(key);
    13. byte[] toEncryptArray = Convert.FromBase64String(encryptStr);
    14. RijndaelManaged rDel = new RijndaelManaged();
    15. rDel.Key = keyArray;
    16. rDel.Mode = CipherMode.CBC;
    17. rDel.Padding = PaddingMode.PKCS7;
    18. rDel.IV = IV_VALUE;
    19. ICryptoTransform cTransform = rDel.CreateDecryptor();
    20. byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
    21. return UTF8Encoding.UTF8.GetString(resultArray);
    22. }
    23. catch (Exception)
    24. {
    25. return "";
    26. }
    27. }
    28. ///
    29. /// 补全密文
    30. ///
    31. /// 密文
    32. /// 秘钥长度
    33. /// 补全后的密文
    34. private static string CorrectionCiphertext(string strCiphertext, int keySize = 128)
    35. {
    36. int ciphertextLength = keySize / 8;
    37. byte[] data = Convert.FromBase64String(strCiphertext);
    38. var newData = new List<byte>(data);
    39. while (newData.Count < ciphertextLength)
    40. {
    41. newData.Insert(0, 0x00);
    42. }
    43. return Convert.ToBase64String(newData.ToArray());
    44. }

    下面介绍iv基于32位字符串时,加密解密方法

    客户提供的32位iv

    private static string ivParameter32 = "0123456789abcdef0123456789abcdef";

    1. ///
    2. /// AES 加密--仅适用于平安联合登录
    3. ///
    4. /// 明文(待加密)
    5. /// 密文
    6. ///
    7. public string AesEncrypt_(string encryptStr, string key)
    8. {
    9. byte[] IV = OpenApiAESKeyCreator.hexStr2Bytes(ivParameter32);
    10. byte[] keyArray = Convert.FromBase64String(key);
    11. byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(encryptStr);//
    12. RijndaelManaged rDel = new RijndaelManaged();
    13. rDel.Key = keyArray;
    14. rDel.Mode = CipherMode.CBC;
    15. rDel.Padding = PaddingMode.PKCS7;
    16. rDel.IV = IV;
    17. ICryptoTransform cTransform = rDel.CreateEncryptor();
    18. byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
    19. return Convert.ToBase64String(resultArray, 0, resultArray.Length);
    20. }

    1. ///
    2. /// 解密--仅适用于平安联合登录
    3. ///
    4. ///
    5. ///
    6. ///
    7. public static string AESDencrypt_(string encryptStr, string key)
    8. {
    9. byte[] keyArray = Convert.FromBase64String(key);
    10. byte[] toEncryptArray = Convert.FromBase64String(encryptStr);
    11. byte[] IV_VALUE = OpenApiAESKeyCreator.hexStr2Bytes(ivParameter32);
    12. RijndaelManaged rDel = new RijndaelManaged();
    13. rDel.Key = keyArray;
    14. rDel.Mode = CipherMode.CBC;
    15. rDel.Padding = PaddingMode.PKCS7;
    16. rDel.IV = IV_VALUE;
    17. ICryptoTransform cTransform = rDel.CreateDecryptor();
    18. byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
    19. return UTF8Encoding.UTF8.GetString(resultArray);
    20. }

    OpenApiAESKeyCreator类:

    1. internal class OpenApiAESKeyCreator
    2. {
    3. public static String toAesKey128(String merchantKey)
    4. {
    5. return toAesKey(merchantKey, 128);
    6. }
    7. private static String toAesKey(String merchantKey, int aesKeyBit)
    8. {
    9. int aesKeySize = aesKeyBit / 8;
    10. // 16位编码转为字节数组
    11. //[-123, 62, -64, -29, -41, 101, 71, -92, -71, 123, 93, -41, 118, 54, 111, -113]
    12. //[133, 62, 192, 227, 215, 101, 71, 164, 185, 123, 93, 215, 118, 54, 111, 143]这是转base64之前的数组数据
    13. byte[] bs = hexStr2Bytes(merchantKey);
    14. byte[] aesKeyBytes = new byte[aesKeySize];
    15. Array.Copy(bs, aesKeyBytes, aesKeySize);
    16. int merchantKeySize = bs.Length;
    17. if (aesKeySize > merchantKeySize)
    18. {
    19. // 填充
    20. for (int index = merchantKeySize; index < aesKeySize; index++)
    21. {
    22. aesKeyBytes[index] = (byte)index;
    23. }
    24. }
    25. return base64encode(aesKeyBytes);
    26. }
    27. // 将 s 进行 BASE64 编码
    28. private static String base64encode(byte[] src)
    29. {
    30. if (src == null)
    31. {
    32. return null;
    33. }
    34. return Convert.ToBase64String(src);
    35. }
    36. public static byte[] hexStr2Bytes(string src)
    37. {
    38. /* 对输入值进行规范化整理 */
    39. src = src.Trim().Replace(" ", "").ToUpper();
    40. // 处理值初始化
    41. int m = 0, n = 0;
    42. int iLen = src.Length / 2; // 计算长度
    43. byte[] ret = new byte[iLen]; // 分配存储空间
    44. for (int i = 0; i < iLen; i++)
    45. {
    46. ret[i] = (byte)(Convert.ToInt32(src.Substring(i * 2, 2), 16) & 0xFF);
    47. }
    48. return ret;
    49. }
    50. }

  • 相关阅读:
    网工内推 | 急聘网络运维,周末双休,厂商认证优先
    What’s new in Grafana v11.0-preview
    为什么微服务一定要有API网关?
    STM32G070RBT6-MCU温度测量(ADC)
    数据监测都可以监测啥
    el-select 多选模式下嵌套el-tree 删除tag时能去掉el-tree对应节点的勾
    【JWT】JWT 整合
    战略调整?顺丰科技将从深圳撤退到武汉!
    matlab常微分方程在传染病建模中的应用
    C++模板
  • 原文地址:https://blog.csdn.net/huxinyu0208/article/details/126288476