一般对接第三方的接口时,接口提供方如果要求以RSA方式进行加密传输,并且给了你一串字符串说是RSA加密公钥,那么该公钥一般是PEM格式文件的base64字符串表现形式。
完整PEM格式示例:
1:示例证书:
-----BEGIN RSA PRIVATE KEY-----
base64字符串
-----END RSA PRIVATE KEY-----
注意其格式,接口提供方有可能只给你中间的base64字符串,也有可能将完整的格式内容都给你,那么你要留意去除除了base64字符串内容之外的头尾以及空行。
那么问题来了,在C#的RSA加密工具类RSACryptoServiceProvider中,并没有支持PEM格式公钥为基准的加密方法,只能用一种.net平台认可的xml字符串的公钥才能够进行加密。
RSACryptoServiceProvider _rsa = new RSACryptoServiceProvider();
_rsa.FromXmlString(publickey);//这里的公钥指定只能是平台认可的xml字符串格式,直接传入pem的base64是不行的
byte[] cipherbytes = _rsa.Encrypt(_b, false);//进行加密
想要从pem公钥转为xml公钥,只能依赖于一个第三方库,叫做BouncyCastle
然后用以下方法进行转换:
///
/// RSA公钥pem的base64字符串-->XML格式字符串转换,
///
/// pem公钥base64字符串
///
public static string RSAPublicKey(string publicKey)
{
RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey));
string XML = string.Format("{0} {1} ",
Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()),
Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned()));
return XML;
}
另外如果在之后加密时报长度超长之类的错误,说明需要分段加密,因为RSA的加密机制要求:待加密的字节数不能超过密钥的长度值除以 8 再减去 11。
可以用如下方法进行分段加密:
///
/// RSA加密并且返回加密后内容的base64字符串
///
/// 公钥xml格式字符串
/// 加密内容
///
public static string RSAEncrypt(string publickey, string content)
{
RSACryptoServiceProvider _rsa = new RSACryptoServiceProvider();
_rsa.FromXmlString(publickey);
var _b = Encoding.Default.GetBytes(content);
//将字符编码转为utf8
_b = Encoding.Convert(Encoding.Default, new UTF8Encoding(), _b);
int _maxSize = _rsa.KeySize/8-11;
if (_b.Length <= _maxSize)
{
byte[] cipherbytes = _rsa.Encrypt(_b, false);
return Convert.ToBase64String(cipherbytes);
}
else//分块加密
{
using (MemoryStream PlaiStream = new MemoryStream(_b))
using (MemoryStream CrypStream = new MemoryStream())
{
Byte[] Buffer = new Byte[_maxSize];
int BlockSize = PlaiStream.Read(Buffer, 0, _maxSize);
while (BlockSize > 0)
{
Byte[] ToEncrypt = new Byte[BlockSize];
Array.Copy(Buffer, 0, ToEncrypt, 0, BlockSize);
Byte[] Cryptograph = _rsa.Encrypt(ToEncrypt, false);
CrypStream.Write(Cryptograph, 0, Cryptograph.Length);
BlockSize = PlaiStream.Read(Buffer, 0, _maxSize);
}
return Convert.ToBase64String(CrypStream.ToArray(), Base64FormattingOptions.None);
}
}
}
但是上面的加密方式有一个问题,就是同样的待加密内容和公钥,每次加密之后的结果都不相同,虽然都能用私钥进行解密。如果是那种涉及到MD5验证之类的摘要算法进行验证,那么这种“加密结果不固定”的加密方式就不可取了。
之所以出现这种问题是与RSA加密算法的“填充(Padding)”有关,这里的东西我也不懂,所以略过,有兴趣的同学可以深入研究一下。
所以基于这种情况,下面的方法就是一种无填充的公钥加密方式,可以让每一次的加密结果都一致:
///
/// RSA公钥加密(无填充,结果不变)
///
/// 待加密内容的字节数组
/// 公钥(无pem格式头尾,仅base64部分)
/// 加密结果的字节数组
public byte[] RsaEncryptWithPublic(byte[] srcData, string publicKey)
{
var encryptEngine = new RsaEngine(); // new Pkcs1Encoding (new RsaEngine());
using (var txtreader = new StringReader("-----BEGIN PUBLIC KEY-----\n" + publicKey + "\n-----END PUBLIC KEY-----"))
{
var keyParameter = (AsymmetricKeyParameter)new Org.BouncyCastle.OpenSsl.PemReader(txtreader).ReadObject();
encryptEngine.Init(true, keyParameter);
}
byte[] _resultBytes;
var _maxSize = encryptEngine.GetInputBlockSize();
if (srcData.Length <= _maxSize)
{
_resultBytes = encryptEngine.ProcessBlock(srcData, 0, srcData.Length);
}
else//分块加密
{
using (MemoryStream PlaiStream = new MemoryStream(srcData))
using (MemoryStream CrypStream = new MemoryStream())
{
Byte[] Buffer = new Byte[_maxSize];
int BlockSize = PlaiStream.Read(Buffer, 0, _maxSize);
while (BlockSize > 0)
{
Byte[] ToEncrypt = new Byte[BlockSize];
Array.Copy(Buffer, 0, ToEncrypt, 0, BlockSize);
Byte[] Cryptograph = encryptEngine.ProcessBlock(ToEncrypt, 0, ToEncrypt.Length);
CrypStream.Write(Cryptograph, 0, Cryptograph.Length);
BlockSize = PlaiStream.Read(Buffer, 0, _maxSize);
}
_resultBytes = CrypStream.ToArray();
}
}
return _resultBytes;
}