C#與java TCP通道加密通信實例
1、背景說明
公司收費系統(tǒng)需要與銀行做實時代收對接,業(yè)務(wù)協(xié)議使用我們收費系統(tǒng)的標(biāo)準(zhǔn)。但是銀行要求在業(yè)務(wù)協(xié)議的基礎(chǔ)上,使用銀行的加密規(guī)則。
- 采用
MD5
計算報文摘要,保證數(shù)據(jù)的完整性 - 采用
RSA256
對摘要進行簽名,保證報文的合法性 - 采用
AES
進行對稱加密,保證報文的私密性
我們幾個人一評估,在業(yè)務(wù)報文上加一套加密方案,加密方法又是通用的,這個能有什么問題,沒問題。
2、測試證書
銀行發(fā)來的測試證書在這里
后來才知道這應(yīng)該叫java
版格式,而且這個證書經(jīng)驗證是正確的,可正常簽名、驗簽的。
-----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#
版本證書在這里,這個是后來正確的測試證書轉(zhuǎn)換的C#
版本,與上面的java
版本對應(yīng)
私鑰
<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>
3、復(fù)制粘貼加密算法
百度C#RSA
簽名,大差不差,還簡潔明了,很快簽名、驗簽方法就出來啦。
/// <summary> /// RSA簽名 /// </summary> /// <param name="privateKey">私鑰</param> /// <param name="data">待簽名的內(nèi)容</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(); //導(dǎo)入公鑰,準(zhǔn)備驗證簽名 rsa.FromXmlString(publicKey); //返回數(shù)據(jù)驗證結(jié)果 byte[] Data = Encoding.GetEncoding(encoding).GetBytes(data); byte[] rgbSignature = Convert.FromBase64String(signature); return rsa.VerifyData(Data, hashAlgorithm, rgbSignature); }
王婆賣瓜,自賣自夸。
來波自簽自驗吧,竟然不過。怎么就不過了呢,越簡單越感覺無處下手調(diào)試呀。算法都封裝了,那只可能是參數(shù)的問題了。
4、證書格式轉(zhuǎn)換
主要原因是java
和C#
采用的公鑰、私鑰存儲格式不一致,導(dǎo)致無法直接使用銀行提供的java
版證書,需要進行格式轉(zhuǎn)換,轉(zhuǎn)換成C#
能夠識別的證書。這里是轉(zhuǎn)換方法
/// <summary> /// RSA公鑰格式轉(zhuǎn)換,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私鑰格式轉(zhuǎn)換,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())); }
結(jié)果證書轉(zhuǎn)換也不過,直接拋異常。然后就反復(fù)梳理代碼邏輯,總覺得轉(zhuǎn)換方法不對,各種百度。
眾里尋他千百度。驀然回首,那人卻在,燈火闌珊處。
這大概就是這種比較不可思議問題的糾結(jié)吧,證書不會有錯吧?
出于驗證的目的我們找到了線上在用的正常證書,進行轉(zhuǎn)換,正常轉(zhuǎn)換,簽名驗簽正常。不至于吧,我又找來了java
的轉(zhuǎn)換方法。下面是java
代碼,轉(zhuǎn)換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);//轉(zhuǎn)換私鑰 String tes1="java版公鑰證書"; byte[] temp1=b64decode(tes1); String ver1=getRSAPublicKeyAsNetFormat(temp1);//轉(zhuǎn)換公鑰 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("[ \t\n\r]", ""); } 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("[ \t\n\r]", ""); } 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; } }
經(jīng)驗證,仍然無法轉(zhuǎn)換,好吧,實錘了,證書有問題。至于是什么原因造成的證書問題,不太清楚。更換證書后,轉(zhuǎn)換、簽名、驗簽一切OK。
5、PS1 RSA證書格式
我們通??吹降谋容^多的證書對形式,pfx
證書包含了公鑰信息和私鑰信息。cer
證書只包含公鑰信息。pfx證書既可以導(dǎo)出為pfx證書,也可以導(dǎo)出為cer
證書。pfx證書導(dǎo)出時,會提示是否導(dǎo)出私鑰,導(dǎo)出私鑰即pfx證書,不到出則是cer
證書。pfx證書導(dǎo)入、導(dǎo)出和程序加載時,是需要提供證書密碼的。
- 私鑰證書常見格式:
pfx p12 pem key
- 公鑰證書常見格式:
pem crt/cer key
到此這篇關(guān)于C#
與java TCP
通道加密通信實例的文章就介紹到這了,更多相關(guān)C#
與java TCP
通道加密通信內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
6、參考博文
相關(guān)文章
C#中TreeView實現(xiàn)適合兩級節(jié)點的選中節(jié)點方法
這篇文章主要介紹了C#中TreeView實現(xiàn)適合兩級節(jié)點的選中節(jié)點方法,實例分析了C#中TreeView節(jié)點操作的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-09-09C#實現(xiàn)統(tǒng)計100以內(nèi)所有素數(shù)的個數(shù)
這篇文章介紹了C#實現(xiàn)統(tǒng)計100以內(nèi)所有素數(shù)個數(shù)的方法,文中注釋非常詳細(xì),很適合新手學(xué)習(xí)。對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-11-11