最近工作中,有一个第三方接口,需要对接口参数的部分数据进行RSA加密,接口文档中提供了一个使用Java写的RSA公钥。我也是第一次接触RSA加密算法,在查阅资料中发现,使用RSA加密,同一个数据,加密的结果是不一样的。在使用C#实现RSA加密过程中,一边查资料,一边摸索,也浪费了不少的时间。
注:本文涉及到的代码非原创代码,从网上收集而来,本文记录下来是为了方便下次碰到时能直接拿去用,同时也非常感谢博客园的博主的分享
C#RSA对接JAVA中RSA方式 - 盛开的雨季 - 博客园
好了,废话就不说了,本文的代码在.net framework 4.0和vs2019中测试通过。
第一版代码(能加密,但接口方无法解密)
public class RSAHelper
{
public static string publicKey = @"<RSAKeyValue><Modulus>aaaaaaaaaaaaaaaafUwIDAQAB</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
public static string RsaEncrypt(string value, bool usePkcs8 = false)
{
if (string.IsNullOrEmpty(value)) return value;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048);
rsa.FromXmlString(publicKey);//将公钥导入到RSA对象中,准备加密;
var buffer = Encoding.UTF8.GetBytes(value);
buffer = rsa.Encrypt(buffer,false);
return Convert.ToBase64String(buffer);
}
}
通过与接口方沟通,拿到了java版的加解密代码,如下:
package com.pkuhit.mobile.pay.alipay.api.sign;
import com.founder.smc.util.Base64Util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
public class RSA{
public static final String SIGN_ALGORITHMS = "SHA1WithRSA";
/**
* RSA签名
* @param content 待签名数据
* @param privateKey 商户私钥
* @param input_charset 编码格式
* @return 签名值
*/
public static String sign(String content, String privateKey, String input_charset)
{
try
{
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec( Base64.decode(privateKey) );
KeyFactory keyf = KeyFactory.getInstance("RSA");
PrivateKey priKey = keyf.generatePrivate(priPKCS8);
java.security.Signature signature = java.security.Signature
.getInstance(SIGN_ALGORITHMS);
signature.initSign(priKey);
signature.update( content.getBytes(input_charset) );
byte[] signed = signature.sign();
return Base64.encode(signed);
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
/**
* RSA验签名检查
* @param content 待签名数据
* @param sign 签名值
* @param ali_public_key 支付宝公钥
* @param input_charset 编码格式
* @return 布尔值
*/
public static boolean verify(String content, String sign, String ali_public_key, String input_charset)
{
try
{
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodedKey = Base64.decode(ali_public_key);
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
java.security.Signature signature = java.security.Signature
.getInstance(SIGN_ALGORITHMS);
signature.initVerify(pubKey);
signature.update( content.getBytes(input_charset) );
boolean bverify = signature.verify( Base64.decode(sign) );
return bverify;
}
catch (Exception e)
{
e.printStackTrace();
}
return false;
}
/**
* 解密
* @param content 密文
* @param private_key 商户私钥
* @param input_charset 编码格式
* @return 解密后的字符串
*/
public static String decrypt(String content, String private_key, String input_charset) throws Exception {
PrivateKey prikey = getPrivateKey(private_key);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, prikey);
InputStream ins = new ByteArrayInputStream(Base64.decode(content));
ByteArrayOutputStream writer = new ByteArrayOutputStream();
//rsa解密的字节大小最多是128,将需要解密的内容,按128位拆开解密
byte[] buf = new byte[128];
int bufl;
while ((bufl = ins.read(buf)) != -1) {
byte[] block = null;
if (buf.length == bufl) {
block = buf;
} else {
block = new byte[bufl];
for (int i = 0; i < bufl; i++) {
block[i] = buf[i];
}
}
writer.write(cipher.doFinal(block));
}
return new String(writer.toByteArray(), input_charset);
}
/**
* 得到私钥
* @param key 密钥字符串(经过base64编码)
* @throws Exception
*/
public static PrivateKey getPrivateKey(String key) throws Exception {
byte[] keyBytes;
keyBytes = Base64.decode(key);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
return privateKey;
}
/**
*加密
* @param content 明文
* @param public_key 公钥
* @param input_charset 编码格式(UTF-8)
* @return 加密后的字符串
* @throws Exception
*/
public static String encrypt(String content,String public_key, String input_charset) throws Exception {
PublicKey publicKey = getPublicKey(public_key);
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE,publicKey);
ByteArrayOutputStream writer = new ByteArrayOutputStream();
//加密最大长度为117b(密钥长度为1024时),超过该长度的进行分块处理
byte[] encryptData = null;
byte[] con = content.getBytes("UTF-8");
byte[] cache;
int offSet = 0,i=0;
int dataLength = con.length;
while(dataLength - offSet > 0){
if (dataLength - offSet > 117){
cache = cipher.doFinal(con,offSet,117);
}else {
cache = cipher.doFinal(con,offSet,dataLength-offSet);
}
writer.write(cache,0,cache.length);
i++;
offSet = i*117;
}
encryptData = writer.toByteArray();
writer.close();
//先用RSA加密再用base64加密
return Base64Util.encode(encryptData);
}
/**
* 公钥字符串转PublicKey实例
* @param publicKey
* @return
* @throws Exception
*/
public static PublicKey getPublicKey(String publicKey) throws Exception {
byte[] publicKeyBytes = Base64Util.decode(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
public static void main(String[] args)throws Exception {
String clientPrivateKey="";
String clientPublicKey="";
String context = "老铁";
String encryptData = encrypt(context,clientPublicKey,"UTF-8");
System.out.println("encryptData=>"+encryptData);
String decryptData = decrypt(encryptData,clientPrivateKey,"UTF-8");
System.out.println("decryptData=>"+decryptData);
}
}
反复看了他们写的代码,也没啥特别的,他们就是解密不了,有点见鬼了,后面查阅资料发现,不能直接使用java的公钥,需要使用插件Portable.BouncyCastle进行转换,使用nuget安装Portable.BouncyCastle,如下图:
最终版代码如下:
public class RSAHelper
{
public static string publicKey = @"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLUXFfPGjolZHxO0fc+e26S0Aq9OyI6jvSFjngLXb4V2dfb1DhCXB4y0wWheCvU4jd9b6lklF2kw9d5sPHDMCqssZPKnfcoGPNzGTtzVFjMJXYHHW7bHvwOhBzO33fGVYlKl2MK+NHyFzByovtQhcCWlTM5SEFBqB96eccg8bfUwIDAQAB";
public static string RsaEncrypt(string value, bool usePkcs8 = false)
{
if (string.IsNullOrEmpty(value)) return value;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048);
rsa.FromXmlString(RSAPublicKeyJava2DotNet(publicKey));//将公钥导入到RSA对象中,准备加密;
var buffer = Encoding.UTF8.GetBytes(value);
buffer = rsa.Encrypt(buffer,false);
return Convert.ToBase64String(buffer);
}
/***************以下是java提供的公私钥转成.net公私钥XML的方式*************************************/
/// <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()));
}
}