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】416. Partition Equal Subset Sum
    【leetcode】893. Groups of Special-Equivalent Strings
    【leetcode】892. Surface Area of 3D Shapes
    【leetcode】883. Projection Area of 3D Shapes
    【leetcode】140. Word Break II
    【leetcode】126. Word Ladder II
    【leetcode】44. Wildcard Matching
    【leetcode】336. Palindrome Pairs
    【leetcode】354. Russian Doll Envelopes
    2017.12.22 英语面试手记
  • 原文地址:https://www.cnblogs.com/zhangdk/p/RSA.html
Copyright © 2011-2022 走看看