zoukankan      html  css  js  c++  java
  • C#与java TCP通道加密通信

    背景说明

    公司收费系统需要与银行做实时代收对接,业务协议使用我们收费系统的标准。但是银行要求在业务协议的基础上,使用银行的加密规则。

    1. 采用MD5计算报文摘要,保证数据的完整性
    2. 采用RSA256对摘要进行签名,保证报文的合法性
    3. 采用AES进行对称加密,保证报文的私密性

    我们几个人一评估,在业务报文上加一套加密方案,加密方法又是通用的,这个能有什么问题,没问题。

    银行发来的测试证书在这里

    后来才知道这应该叫pem格式证书,Java语言可直接识别(经博友提醒更正),而且这个证书经验证是正确的,可正常签名、验签的。

    -----BEGIN PRIVATE KEY-----
    MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDllhc9gw7aIuv8
    URnf8PtPRJZww26OeSsTa1GHtuqHdjCaosBXKWaC2/SxV3lsURizFVY+HhvH6zgq
    NCWhSbnwd5ucq8ZiBkb29kIbb9oVKftxpsMTfZdAgXReUPg1cE5zxLvpmSM8ggw9
    2fOq/CgYgbingzAScYR5MmMgH5PwIEVh7MiloS5fMx9HWDuKAZgdXjaZHB5UL8uu
    vb83iytKJIEmS346X41jCM8UQwL4hRaPvtouGSIz+oiDpotPfJQtJVzP1P/bvlQs
    RJHSn1a5aDo+ygH231+PKdfKzhwxDDMeI3Yy2zxqJKtHpVc033O2yfvn5TlQ79P0
    j/hswY0fAgMBAAECggEBAKZKkWjHfcF4W+91GsW+uXiP2Fuy4mgl0ZKOQA6J6dPW
    Qpwu2BwJ66tLADBXiKZxEu/bu4zgqASlFhhTjxIE4b4QFFFlhhrIKyyD8BwJZy+/
    KdYHEPMUG7LoUU5jXXTvdJOb4vPvLLuOAqnmLP0jCTO++e2zMuWY/Xf/jBbfaHsa
    tSM3PHYN/zlTMDyFqfDc6ig+iKejW0dnNBQZpFytijpYgW3QS6XKknDRoQ0V1vpj
    oCCUMlfe8tottGoBt1Q8qrNKxFb9dyXXkGx/QgDfv8A+kWv1WuwYQGose9Gu0L28
    TWGOfHS399oJffJdwa/o2no+KCaKjjL5dCIjzLDm8QECgYEA/t4JgS32JS8cxyEK
    Hh0tGOZhvsh/f2IOi1+pAtBKCQan34n/NCBUeyn8Ep/K95A+bPMIltaoUZYkBTGF
    bVsqdkcyG19qsV5B2ldyfvBoSpwsS7oLcV78EUPLBClCpYfmbq/MEdYTysJsjKRc
    ROR0p30Cu/VEP72WTyY4zMy6yp8CgYEA5ptKmDAud48hlI7gNWkKIVEH16cg+0fS
    a1JgrBUZHOoVTuK8xMUxsGyYeYYolXd7I5Q4vdZwTzjGkAzCMtHzhMUSbzt6i04o
    rDkfB3HEXgoIiGvChh+CjYOhc6aqKd3vFOwVyqZ0s5slc7EF4qWy9ZWD3hwWGfEE
    XkO1Zc/MrYECgYANq2UBG7D2/5bgi0IaqV/w1PJrJB/Kejzjdsb+0qMV5th8Ic+h
    QRam4HKXoSBmtMLUXxiX1n2CmrXl3WkVm20kmN70HuL/Dloj1sraShSd49BwY1MX
    yotkdale2MOtUyOlziH41u2K03C0/l/AhixHi2npINd/P7DfH+KuAVEHawKBgA2I
    c3o26aMujSPwtour3GJUJQes0Syt7FVMAkxW+KBPxGxatgU+JUpbNR98lgkfd+SA
    oEvTt8eOZ2iwtvzQgV/7SLeqX+io744b1AxVytZR9Go9GK9SThEL9n+Y+kd2tL8f
    k6/O0O5xXmNJsjS40KXE3nY8Y7emA0Gc65pL9ZEBAoGBANPd8FP04tTtyqiunE8G
    JrOIQGtCUoEnp4gJV5VVGus19qVf6rVMr1YEMlojxgujcaQJtE878Zep6ur2WeZu
    VEQOqqXRLwKBTaWYA0iabhUDd/nAh5cCjBfcYrfdnQonNrBJi5AccbfUl6FEhkoF
    XQUoc1mnauKcCB1ACvtBRNGI
    -----END PRIVATE KEY-----
    
    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5ZYXPYMO2iLr/FEZ3/D7
    T0SWcMNujnkrE2tRh7bqh3YwmqLAVylmgtv0sVd5bFEYsxVWPh4bx+s4KjQloUm5
    8HebnKvGYgZG9vZCG2/aFSn7cabDE32XQIF0XlD4NXBOc8S76ZkjPIIMPdnzqvwo
    GIG4p4MwEnGEeTJjIB+T8CBFYezIpaEuXzMfR1g7igGYHV42mRweVC/Lrr2/N4sr
    SiSBJkt+Ol+NYwjPFEMC+IUWj77aLhkiM/qIg6aLT3yULSVcz9T/275ULESR0p9W
    uWg6PsoB9t9fjynXys4cMQwzHiN2Mts8aiSrR6VXNN9ztsn75+U5UO/T9I/4bMGN
    HwIDAQAB
    -----END PUBLIC KEY-----
    

    C#版本证书在这里,这个是后来正确的测试证书转换的C#版本,与上面的java版本对应

    私钥

    <RSAKeyValue><Modulus>5ZYXPYMO2iLr/FEZ3/D7T0SWcMNujnkrE2tRh7bqh3YwmqLAVylmgtv0sVd5bFEYsxVWPh4bx+s4KjQloUm58HebnKvGYgZG9vZCG2/aFSn7cabDE32XQIF0XlD4NXBOc8S76ZkjPIIMPdnzqvwoGIG4p4MwEnGEeTJjIB+T8CBFYezIpaEuXzMfR1g7igGYHV42mRweVC/Lrr2/N4srSiSBJkt+Ol+NYwjPFEMC+IUWj77aLhkiM/qIg6aLT3yULSVcz9T/275ULESR0p9WuWg6PsoB9t9fjynXys4cMQwzHiN2Mts8aiSrR6VXNN9ztsn75+U5UO/T9I/4bMGNHw==</Modulus><Exponent>AQAB</Exponent><P>/t4JgS32JS8cxyEKHh0tGOZhvsh/f2IOi1+pAtBKCQan34n/NCBUeyn8Ep/K95A+bPMIltaoUZYkBTGFbVsqdkcyG19qsV5B2ldyfvBoSpwsS7oLcV78EUPLBClCpYfmbq/MEdYTysJsjKRcROR0p30Cu/VEP72WTyY4zMy6yp8=</P><Q>5ptKmDAud48hlI7gNWkKIVEH16cg+0fSa1JgrBUZHOoVTuK8xMUxsGyYeYYolXd7I5Q4vdZwTzjGkAzCMtHzhMUSbzt6i04orDkfB3HEXgoIiGvChh+CjYOhc6aqKd3vFOwVyqZ0s5slc7EF4qWy9ZWD3hwWGfEEXkO1Zc/MrYE=</Q><DP>DatlARuw9v+W4ItCGqlf8NTyayQfyno843bG/tKjFebYfCHPoUEWpuByl6EgZrTC1F8Yl9Z9gpq15d1pFZttJJje9B7i/w5aI9bK2koUnePQcGNTF8qLZHWpXtjDrVMjpc4h+NbtitNwtP5fwIYsR4tp6SDXfz+w3x/irgFRB2s=</DP><DQ>DYhzejbpoy6NI/C2i6vcYlQlB6zRLK3sVUwCTFb4oE/EbFq2BT4lSls1H3yWCR935ICgS9O3x45naLC2/NCBX/tIt6pf6KjvjhvUDFXK1lH0aj0Yr1JOEQv2f5j6R3a0vx+Tr87Q7nFeY0myNLjQpcTedjxjt6YDQZzrmkv1kQE=</DQ><InverseQ>093wU/Ti1O3KqK6cTwYms4hAa0JSgSeniAlXlVUa6zX2pV/qtUyvVgQyWiPGC6NxpAm0Tzvxl6nq6vZZ5m5URA6qpdEvAoFNpZgDSJpuFQN3+cCHlwKMF9xit92dCic2sEmLkBxxt9SXoUSGSgVdBShzWadq4pwIHUAK+0FE0Yg=</InverseQ><D>pkqRaMd9wXhb73Uaxb65eI/YW7LiaCXRko5ADonp09ZCnC7YHAnrq0sAMFeIpnES79u7jOCoBKUWGFOPEgThvhAUUWWGGsgrLIPwHAlnL78p1gcQ8xQbsuhRTmNddO90k5vi8+8su44CqeYs/SMJM7757bMy5Zj9d/+MFt9oexq1Izc8dg3/OVMwPIWp8NzqKD6Ip6NbR2c0FBmkXK2KOliBbdBLpcqScNGhDRXW+mOgIJQyV97y2i20agG3VDyqs0rEVv13JdeQbH9CAN+/wD6Ra/Va7BhAaix70a7QvbxNYY58dLf32gl98l3Br+jaej4oJoqOMvl0IiPMsObxAQ==</D></RSAKeyValue>
    

    公钥

    <RSAKeyValue><Modulus>5ZYXPYMO2iLr/FEZ3/D7T0SWcMNujnkrE2tRh7bqh3YwmqLAVylmgtv0sVd5bFEYsxVWPh4bx+s4KjQloUm58HebnKvGYgZG9vZCG2/aFSn7cabDE32XQIF0XlD4NXBOc8S76ZkjPIIMPdnzqvwoGIG4p4MwEnGEeTJjIB+T8CBFYezIpaEuXzMfR1g7igGYHV42mRweVC/Lrr2/N4srSiSBJkt+Ol+NYwjPFEMC+IUWj77aLhkiM/qIg6aLT3yULSVcz9T/275ULESR0p9WuWg6PsoB9t9fjynXys4cMQwzHiN2Mts8aiSrR6VXNN9ztsn75+U5UO/T9I/4bMGNHw==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
    

    第一波 复制粘贴加密算法

    百度C#RSA签名,大差不差,还简洁明了,很快签名、验签方法就出来啦。

    /// <summary>
    /// RSA签名
    /// </summary>
    /// <param name="privateKey">私钥</param>
    /// <param name="data">待签名的内容</param>
    /// <returns></returns>
    public static string RSASignCSharp(string data, string privateKey, string hashAlgorithm = "SHA256", string encoding = "UTF-8")
    {
        RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
        rsa.FromXmlString(privateKey); //加载私钥   
        var dataBytes = Encoding.GetEncoding(encoding).GetBytes(data);
        var HashbyteSignature = rsa.SignData(dataBytes, hashAlgorithm);
        return Convert.ToBase64String(HashbyteSignature);
    }
    
    /// <summary> 
    /// 验证签名
    /// </summary>
    /// <param name="data"></param>
    /// <param name="signature"></param>
    /// <param name="encoding"></param>
    /// <returns></returns>
    public static bool VerifyCSharp(string data, string publicKey, string signature, string hashAlgorithm = "SHA256", string encoding = "UTF-8")
    {
        RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
        //导入公钥,准备验证签名
        rsa.FromXmlString(publicKey);
        //返回数据验证结果
        byte[] Data = Encoding.GetEncoding(encoding).GetBytes(data);
        byte[] rgbSignature = Convert.FromBase64String(signature);
        return rsa.VerifyData(Data, hashAlgorithm, rgbSignature);
    }
    

    王婆卖瓜,自卖自夸。

    来波自签自验吧,竟然不过。怎么就不过了呢,越简单越感觉无处下手调试呀。算法都封装了,那只可能是参数的问题了。

    第二波 证书格式转换

    主要原因是java和C#采用的公钥、私钥存储格式不一致,导致无法直接使用银行提供的java版证书,需要进行格式转换,转换成C#能够识别的证书。这里是转换方法

    /// <summary>
    /// RSA公钥格式转换,java->.net
    /// </summary>
    /// <param name="publicKey">java生成的公钥</param>
    /// <returns></returns>
    public static string RSAPublicKeyJava2DotNet(string publicKey)
    {
        RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey));
        return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>",
            Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()),
            Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned()));
    
    }
    
    /// <summary>
    /// RSA私钥格式转换,java->.net
    /// </summary>
    /// <param name="privateKey">java生成的私钥</param>
    /// <returns></returns>
    public static string RSAPrivateKeyJava2DotNet(string privateKey)
    {
        RsaPrivateCrtKeyParameters privateKeyParam = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey));
    
        return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent><P>{2}</P><Q>{3}</Q><DP>{4}</DP><DQ>{5}</DQ><InverseQ>{6}</InverseQ><D>{7}</D></RSAKeyValue>",
            Convert.ToBase64String(privateKeyParam.Modulus.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.PublicExponent.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.P.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.Q.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.DP.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.DQ.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.QInv.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.Exponent.ToByteArrayUnsigned()));
    }
    

    结果证书转换也不过,直接抛异常。然后就反复梳理代码逻辑,总觉得转换方法不对,各种百度。

    众里寻他千百度。蓦然回首,那人却在,灯火阑珊处。

    这大概就是这种比较不可思议问题的纠结吧,证书不会有错吧?
    出于验证的目的我们找到了线上在用的正常证书,进行转换,正常转换,签名验签正常。不至于吧,我又找来了java的转换方法。下面是java代码,转换java证书为C#格式:

    package com.company;
    
    
    import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
    
    import java.security.KeyFactory;
    import java.security.PublicKey;
    import java.security.interfaces.RSAPrivateCrtKey;
    import java.security.interfaces.RSAPublicKey;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    
    public class CerJava2CSharp {
        public static void main(String[] args)
        {
            String tes="java版私钥证书";
            byte[] temp=b64decode(tes);
            String ver=getRSAPrivateKeyAsNetFormat(temp);//转换私钥
    
            String tes1="java版公钥证书";
            byte[] temp1=b64decode(tes1);
            String ver1=getRSAPublicKeyAsNetFormat(temp1);//转换公钥
            String temp2= ver1;
    
        }
        
        public static String getRSAPrivateKeyAsNetFormat(byte[] encodedPrivkey) {
            try {
                StringBuffer buff = new StringBuffer(1024);
    
                PKCS8EncodedKeySpec pvkKeySpec = new PKCS8EncodedKeySpec(
                        encodedPrivkey);
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                RSAPrivateCrtKey pvkKey = (RSAPrivateCrtKey) keyFactory
                        .generatePrivate(pvkKeySpec);
    
                buff.append("<RSAKeyValue>");
                buff.append("<Modulus>"
                        + b64encode(removeMSZero(pvkKey.getModulus().toByteArray()))
                        + "</Modulus>");
    
                buff.append("<Exponent>"
                        + b64encode(removeMSZero(pvkKey.getPublicExponent()
                        .toByteArray())) + "</Exponent>");
    
                buff.append("<P>"
                        + b64encode(removeMSZero(pvkKey.getPrimeP().toByteArray()))
                        + "</P>");
    
                buff.append("<Q>"
                        + b64encode(removeMSZero(pvkKey.getPrimeQ().toByteArray()))
                        + "</Q>");
    
                buff.append("<DP>"
                        + b64encode(removeMSZero(pvkKey.getPrimeExponentP()
                        .toByteArray())) + "</DP>");
    
                buff.append("<DQ>"
                        + b64encode(removeMSZero(pvkKey.getPrimeExponentQ()
                        .toByteArray())) + "</DQ>");
    
                buff.append("<InverseQ>"
                        + b64encode(removeMSZero(pvkKey.getCrtCoefficient()
                        .toByteArray())) + "</InverseQ>");
    
                buff.append("<D>"
                        + b64encode(removeMSZero(pvkKey.getPrivateExponent()
                        .toByteArray())) + "</D>");
                buff.append("</RSAKeyValue>");
    
                return buff.toString().replaceAll("[ 	
    
    ]", "");
            } catch (Exception e) {
                System.err.println(e);
                return null;
            }
        }
    
        public static String getRSAPublicKeyAsNetFormat(byte[] encodedPrivkey) {
            try {
                StringBuffer buff = new StringBuffer(1024);
    
                PKCS8EncodedKeySpec pvkKeySpec = new PKCS8EncodedKeySpec(encodedPrivkey);
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                RSAPublicKey pukKey=(RSAPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(encodedPrivkey));
                // RSAPrivateCrtKey pvkKey = (RSAPrivateCrtKey) keyFactory.generatePrivate(pvkKeySpec);
    
                //PublicKey publicKey =KeyFactory.getInstance("RSA").generatePublic(pvkKeySpec);
                buff.append("<RSAKeyValue>");
                buff.append("<Modulus>"
                        + b64encode(removeMSZero(pukKey.getModulus().toByteArray()))
                        + "</Modulus>");
                buff.append("<Exponent>"
                        + b64encode(removeMSZero(pukKey.getPublicExponent()
                        .toByteArray())) + "</Exponent>");
                buff.append("</RSAKeyValue>");
                return buff.toString().replaceAll("[ 	
    
    ]", "");
            } catch (Exception e) {
                System.err.println(e);
                return null;
            }
        }
    
        public static String encodePublicKeyToXml(PublicKey key) {
            if (!RSAPublicKey.class.isInstance(key)) {
                return null;
            }
            RSAPublicKey pubKey = (RSAPublicKey) key;
            StringBuilder sb = new StringBuilder();
    
            sb.append("<RSAKeyValue>");
            sb.append("<Modulus>")
                    .append(Base64.encode(pubKey.getModulus().toByteArray()))
                    .append("</Modulus>");
            sb.append("<Exponent>")
                    .append(Base64.encode(pubKey.getPublicExponent()
                            .toByteArray())).append("</Exponent>");
            sb.append("</RSAKeyValue>");
            return sb.toString();
        }
    
        public static byte[] removeMSZero(byte[] data) {
            byte[] data1;
            int len = data.length;
            if (data[0] == 0) {
                data1 = new byte[data.length - 1];
                System.arraycopy(data, 1, data1, 0, len - 1);
            } else
                data1 = data;
    
            return data1;
        }
    
        public static String b64encode(byte[] data) {
    
            String b64str = new String(Base64.encode(data));
            return b64str;
        }
    
        public static byte[] b64decode(String data) {
            byte[] decodeData = Base64.decode(data);
            return decodeData;
        }
    }
    

    经验证,仍然无法转换,好吧,实锤了,证书有问题。至于是什么原因造成的证书问题,不太清楚。更换证书后,转换、签名、验签一切OK。

    PS1 RSA证书格式

    我们通常看到的比较多的证书对形式,pfx证书包含了公钥信息和私钥信息。cer证书只包含公钥信息。pfx证书既可以导出为pfx证书,也可以导出为cer证书。pfx证书导出时,会提示是否导出私钥,导出私钥即pfx证书,不到出则是cer证书。pfx证书导入、导出和程序加载时,是需要提供证书密码的。

    • 私钥证书常见格式:pfx p12 pem key
    • 公钥证书常见格式:pem crt/cer key

    参考博文

    OpenSSL中证书格式的区别以及格式的转换

    RSA密钥——JAVA与C#的区别和联系

  • 相关阅读:
    leetcode 746. 使用最小花费爬楼梯
    leetcode 474.一和零
    leetcode 221.最大正方形
    leetcode 525.连续数组
    leetcode 32.最长有效括号
    leetcode 46.全排列
    如何把word文档导入到数据库中——java POI
    leetcode 198.打家劫舍
    leetcode 581.最短无序连续子数组
    02需求工程-软件建模与分析阅读笔记之二
  • 原文地址:https://www.cnblogs.com/zhangdk/p/RSA.html
Copyright © 2011-2022 走看看