背景:接口方开发环境是Java,要求我使用私钥对数据进行加密,他使用公钥进行解密。
开发时遇到的问题:
1).Net平台默认是使用公钥进行加密,私钥进行解密。私钥加密需要自己实现或者使用第三方dll。
2)双方平台不一致,出现了我加密的数据对方不能解密,对方加密的数据我不能解密,但是自身是可以正常加密解密。
解决办法:
1)首先将java提供的密钥转成.net可用的格式。
2)使用第三方dll,此处使用的是C#的BouncyCastle.Crypto进行加密及解密。在官网http://www.bouncycastle.org/csharp/ 下载最新dll。
3)设置Cipher为“RSA/ECB/PKCS1Padding”,与Java平台统一。 这是实现的方法,方法后面是解决过程,有兴趣可以看一看。如果这个代码不能解决你的问题,建议看下后面的解决过程,里面的链接也许对你有帮助。
/// <summary> /// RSA私钥格式转换,java->.net /// </summary> /// <param name="privateKey">java生成的RSA私钥</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())); } /// <summary> /// /// RSA私钥格式转换,.net->java /// </summary> /// <param name="privateKey">.net生成的私钥</param> /// <returns></returns> public static string RSAPrivateKeyDotNet2Java(string privateKey) { XmlDocument doc = new XmlDocument(); doc.LoadXml(privateKey); BigInteger m = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Modulus")[0].InnerText)); BigInteger exp = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Exponent")[0].InnerText)); BigInteger d = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("D")[0].InnerText)); BigInteger p = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("P")[0].InnerText)); BigInteger q = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Q")[0].InnerText)); BigInteger dp = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("DP")[0].InnerText)); BigInteger dq = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("DQ")[0].InnerText)); BigInteger qinv = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("InverseQ")[0].InnerText)); RsaPrivateCrtKeyParameters privateKeyParam = new RsaPrivateCrtKeyParameters(m, exp, d, p, q, dp, dq, qinv); PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKeyParam); byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetEncoded(); return Convert.ToBase64String(serializedPrivateBytes); } /// <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公钥格式转换,.net->java /// </summary> /// <param name="publicKey">.net生成的公钥</param> /// <returns></returns> public static string RSAPublicKeyDotNet2Java(string publicKey) { XmlDocument doc = new XmlDocument(); doc.LoadXml(publicKey); BigInteger m = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Modulus")[0].InnerText)); BigInteger p = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Exponent")[0].InnerText)); RsaKeyParameters pub = new RsaKeyParameters(false, m, p); SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pub); byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded(); return Convert.ToBase64String(serializedPublicBytes); }
/// <summary> /// 用私钥给数据进行RSA加密 /// </summary> /// <param name="xmlPrivateKey">私钥</param> /// <param name="m_strEncryptString">待加密数据</param> /// <returns>加密后的数据(Base64)</returns> public static string RSAEncryptByPrivateKey(string xmlPrivateKey, string strEncryptString) { //加载私钥 RSACryptoServiceProvider privateRsa = new RSACryptoServiceProvider(); privateRsa.FromXmlString(xmlPrivateKey); //转换密钥 AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetKeyPair(privateRsa); IBufferedCipher c = CipherUtilities.GetCipher("RSA/ECB/PKCS1Padding");// 参数与Java中加密解密的参数一致 //IBufferedCipher c = CipherUtilities.GetCipher("RSA/ECB/OAEPWITHSHA-512ANDMGF1PADDING");// 参数与Java中加密解密的参数一致 //IBufferedCipher c = CipherUtilities.GetCipher("RSA/NONE/PKCS1PADDING");// 参数与Java中加密解密的参数一致 //第一个参数为true表示加密,为false表示解密;第二个参数表示密钥 c.Init(true, keyPair.Private); byte[] DataToEncrypt = Encoding.UTF8.GetBytes(strEncryptString); byte[] outBytes = c.DoFinal(DataToEncrypt);//加密 string strBase64 = Convert.ToBase64String(outBytes); return strBase64; }
RSA 是一种非对称加密算法。由于算法特性,加密和解密过程用不同密钥,即公钥和私钥,而被广泛应用于数字证书的安全管理。 在具体应用中,公钥用加密而私钥用于解密,或 私钥用于数字签名而公钥用于签名验证。由于非对称加密算法比价复杂,耗时较长,所以一般在网络环境中RAS 被应用于 签名认证,或小数据传输,如 AES 对称密钥传输。
在.Net 框架中,默认提供的辅助类只能对密钥长度大小相同的数据进行加密解密。 此文提供C# RSA 算法示例一座优化,可以不限数据大小。此实例 仅供交流学习。
/// <summary> /// RSA分段加密 /// </summary> /// <param name="rawInput"></param> /// <param name="publicKey"></param> /// <returns></returns> public static string RsaEncrypt(string rawInput, string publicKey) { if (string.IsNullOrEmpty(rawInput)) { return string.Empty; } if (string.IsNullOrWhiteSpace(publicKey)) { throw new ArgumentException("Invalid Public Key"); } using (var rsaProvider = new RSACryptoServiceProvider()) { var inputBytes = Encoding.UTF8.GetBytes(rawInput);//有含义的字符串转化为字节流 rsaProvider.FromXmlString(publicKey);//载入公钥 int bufferSize = (rsaProvider.KeySize / 8) - 11;//单块最大长度 var buffer = new byte[bufferSize]; using (MemoryStream inputStream = new MemoryStream(inputBytes), outputStream = new MemoryStream()) { while (true) { //分段加密 int readSize = inputStream.Read(buffer, 0, bufferSize); if (readSize <= 0) { break; } var temp = new byte[readSize]; Array.Copy(buffer, 0, temp, 0, readSize); var encryptedBytes = rsaProvider.Encrypt(temp, false); outputStream.Write(encryptedBytes, 0, encryptedBytes.Length); } return Convert.ToBase64String(outputStream.ToArray());//转化为字节流方便传输 } } }
/// <summary> /// RSA分段解密 /// </summary> /// <param name="encryptedInput"></param> /// <param name="privateKey"></param> /// <returns></returns> public string RsaDecrypt(string encryptedInput, string privateKey) { if (string.IsNullOrEmpty(encryptedInput)) { return string.Empty; } if (string.IsNullOrWhiteSpace(privateKey)) { throw new ArgumentException("Invalid Private Key"); } using (var rsaProvider = new RSACryptoServiceProvider()) { var inputBytes = Convert.FromBase64String(encryptedInput); rsaProvider.FromXmlString(privateKey); int bufferSize = rsaProvider.KeySize / 8; var buffer = new byte[bufferSize]; using (MemoryStream inputStream = new MemoryStream(inputBytes), outputStream = new MemoryStream()) { while (true) { int readSize = inputStream.Read(buffer, 0, bufferSize); if (readSize <= 0) { break; } var temp = new byte[readSize]; Array.Copy(buffer, 0, temp, 0, readSize); var rawBytes = rsaProvider.Decrypt(temp, false); outputStream.Write(rawBytes, 0, rawBytes.Length); } return Encoding.UTF8.GetString(outputStream.ToArray()); } } }
-------------------------------------解决过程-------------------------------------
1.确认是使用私钥进行加密后,发现.Net没有使用私钥进行加密的方法。搜索后得知需要自己实现。
2.搜索实现方式,但得到的方法里需要两个BigInteger,由于对私钥的xml并不了解,不清楚参数该用什么值。只好放弃这种方式。不过后来查资料找到私钥xml中各节点的含义以及加密时该用哪些节点的值。不过这是后话了。
微软对公钥私钥各节点的解释:RSAParameters 结构 https://msdn.microsoft.com/zh-cn/library/system.security.cryptography.rsaparameters%28v=vs.110%29.aspx
加密解密时该使用什么节点:http://blog.csdn.NET/a351945755/article/details/21965533
3.找到一个已经封装好的方法,只需要把密钥和加密数据传进去就可以实现的方法。但是加密出来的结果本身可以解密,但是对方无法解密。对方提供的加密数据使用此方法无法解密。
此处使用的代码:http://www.codeproject.com/Articles/38739/RSA-Private-Key-Encryption
4.在前两步搜索资料的时候,得知BouncyCastle。看到相关介绍,于是使用其提供的dll进行加密解密操作。但是此时网上找到的都是BouncyCastle自动生成密钥进行加密解密而不是读取.Net的密钥进行加密解密。
使用BouncyCastle进行加密解密的代码是:http://blog.csdn.Net/popozhu/article/details/5789382
5.查找密钥转换方式,找到以下资料:http://stackoverflow.com/questions/3240222/get-private-key-from-bouncycastle-x509-certificate-c-sharp 使用“CriGoT”或者“majkinetor”提供的代码都可以进行私钥的转换。
6.通过第四步和第五步可以获得完整的加密解密方法。在使用此方法进行私钥加密后对方解密时报错“javax.crypto.BadPaddingException: Blocktype mismatch: 0”,查找资料后得知,也许与Cipher.getInstance("RSA")中的参数有关,需要改为“RSA/ECB/PKCS1Padding”。但对方排查后应当默认就是“RSA/ECB/PKCS1Padding”。最终在 http://www.xuebuyuan.com/301023.html 找到另一种调用的方法可以传入“RSA/ECB/PKCS1Padding”。
至此.Net和Java之间RSA私钥加密公钥解密进行数据交互完成。