.NET RSA解密、签名、验签

2023-06-12,,

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates; namespace API.Tools
{
/// <summary>
/// 类名:RSAFromPkcs8
/// 功能:RSA解密签名、验签
/// 详细:该类对Java生成的密钥进行解密和签名以及验签专用类,不需要修改
/// 版本:2.0
/// 修改日期:2011-05-10
/// 说明:
/// 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。
/// 该代码仅供学习和研究支付宝接口使用,只是提供一个参考。
/// </summary>
public sealed class Signatory
{
/// <summary>
/// 签名(C#解释证书导出的,会出现“数据不正确”的错误)
/// </summary>
/// <param name="privateKey">私钥</param>
/// <param name="content">需要签名的内容</param>
/// <param name="encode">编码格式</param>
/// <returns></returns>
public static string sign(string privateKey, string content, Encoding encode)
{
byte[] Data = encode.GetBytes(content);
RSACryptoServiceProvider rsa = DecodePemPrivateKey(privateKey);
MD5CryptoServiceProvider m5 = new MD5CryptoServiceProvider(); byte[] signData = rsa.SignData(Data, m5);
return Convert.ToBase64String(signData);
}
/// <summary>
/// 验证签名
/// </summary>
/// <param name="publicKey">公钥</param>
/// <param name="content">需要验证的内容</param>
/// <param name="signedString">签名结果</param>
/// <param name="encode">编码格式</param>
/// <returns></returns>
public static bool verify(string publicKey, string content, string signedString, Encoding encode)
{
bool result = false;
byte[] Data = encode.GetBytes(content);
byte[] data = Convert.FromBase64String(signedString);
RSAParameters paraPub = ConvertFromPublicKey(publicKey);
RSACryptoServiceProvider rsaPub = new RSACryptoServiceProvider();
rsaPub.ImportParameters(paraPub); MD5CryptoServiceProvider m5 = new MD5CryptoServiceProvider();
result = rsaPub.VerifyData(Data, m5, data);
return result;
} /// <summary>
/// 解析java生成的pem文件私钥
/// </summary>
/// <param name="pemstr"></param>
/// <returns></returns>
private static RSACryptoServiceProvider DecodePemPrivateKey(String pemstr)
{
byte[] pkcs8privatekey;
pkcs8privatekey = Convert.FromBase64String(pemstr);
if (pkcs8privatekey != null)
{ RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8privatekey);
return rsa;
}
else
return null;
} private static RSACryptoServiceProvider DecodePrivateKeyInfo(byte[] pkcs8)
{ byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
byte[] seq = new byte[]; MemoryStream mem = new MemoryStream(pkcs8);
int lenstream = (int)mem.Length;
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = ;
ushort twobytes = ; try
{ twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null; bt = binr.ReadByte();
if (bt != 0x02)
return null; twobytes = binr.ReadUInt16(); if (twobytes != 0x0001)
return null; seq = binr.ReadBytes(); //read the Sequence OID
if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct
return null; bt = binr.ReadByte();
if (bt != 0x04) //expect an Octet string
return null; bt = binr.ReadByte(); //read next byte, or next 2 bytes is 0x81 or 0x82; otherwise bt is the byte count
if (bt == 0x81)
binr.ReadByte();
else
if (bt == 0x82)
binr.ReadUInt16();
//------ at this stage, the remaining sequence should be the RSA private key byte[] rsaprivkey = binr.ReadBytes((int)(lenstream - mem.Position));
RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey(rsaprivkey);
return rsacsp;
} catch (Exception)
{
return null;
} finally { binr.Close(); } } private static bool CompareBytearrays(byte[] a, byte[] b)
{
if (a.Length != b.Length)
return false;
int i = ;
foreach (byte c in a)
{
if (c != b[i])
return false;
i++;
}
return true;
} private static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ; // --------- Set up stream to decode the asn.1 encoded RSA private key ------
MemoryStream mem = new MemoryStream(privkey);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = ;
ushort twobytes = ;
int elems = ;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null; twobytes = binr.ReadUInt16();
if (twobytes != 0x0102) //version number
return null;
bt = binr.ReadByte();
if (bt != 0x00)
return null; //------ all private key components are Integer sequences ----
elems = GetIntegerSize(binr);
MODULUS = binr.ReadBytes(elems); elems = GetIntegerSize(binr);
E = binr.ReadBytes(elems); elems = GetIntegerSize(binr);
D = binr.ReadBytes(elems); elems = GetIntegerSize(binr);
P = binr.ReadBytes(elems); elems = GetIntegerSize(binr);
Q = binr.ReadBytes(elems); elems = GetIntegerSize(binr);
DP = binr.ReadBytes(elems); elems = GetIntegerSize(binr);
DQ = binr.ReadBytes(elems); elems = GetIntegerSize(binr);
IQ = binr.ReadBytes(elems); // ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus = MODULUS;
RSAparams.Exponent = E;
RSAparams.D = D;
RSAparams.P = P;
RSAparams.Q = Q;
RSAparams.DP = DP;
RSAparams.DQ = DQ;
RSAparams.InverseQ = IQ;
RSA.ImportParameters(RSAparams);
return RSA;
}
catch (Exception)
{
return null;
}
finally { binr.Close(); }
} private static int GetIntegerSize(BinaryReader binr)
{
byte bt = ;
byte lowbyte = 0x00;
byte highbyte = 0x00;
int count = ;
bt = binr.ReadByte();
if (bt != 0x02) //expect integer
return ;
bt = binr.ReadByte(); if (bt == 0x81)
count = binr.ReadByte(); // data size in next byte
else
if (bt == 0x82)
{
highbyte = binr.ReadByte(); // data size in next 2 bytes
lowbyte = binr.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, );
}
else
{
count = bt; // we already have the data size
} while (binr.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= ;
}
binr.BaseStream.Seek(-, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte
return count;
} private static RSAParameters ConvertFromPublicKey(string pemFileConent)
{ byte[] keyData = Convert.FromBase64String(pemFileConent);
if (keyData.Length < )
{
throw new ArgumentException("pem file content is incorrect.");
}
byte[] pemModulus = new byte[];
byte[] pemPublicExponent = new byte[];
Array.Copy(keyData, , pemModulus, , );
Array.Copy(keyData, , pemPublicExponent, , );
RSAParameters para = new RSAParameters();
para.Modulus = pemModulus;
para.Exponent = pemPublicExponent;
return para;
}
}
}

上面的类在平常使用的时候,是没有问题的,但,我们在对接第三方支付(易联支付)时,遇到一个非常刺手的,签名不通过。

签名证书,是从北京数字认证拿到,里面有一个文件xxxxx-Signature.pfx 和证书密码,我们需要从该文件中,使用openssl.exe工具,导出RSA公钥和私钥
导出的公钥,发给合作平台配上,私钥用于数据签名
签名时,使用导出的私钥进行。

问题来了,调用上面程序的sign()方法,解释私钥时,总报“数据不正解”或者“无效的私钥”之类的。更奇怪的是,该私钥在java下使用没有问题。联系了对方,对方试了好多几证书,只有我们的这个有问题。好吧,算倒霉吧。

解决方法:

.net里可以直接使用证书进行RSA签名:

        /// <summary>
/// 签名,直接使用证书签名
/// </summary>
/// <param name="filePath"></param>
/// <param name="password"></param>
/// <param name="content"></param>
/// <param name="encode"></param>
/// <returns></returns>
public static string sign(string filePath,string password, string content, Encoding encode)
{
byte[] Data = encode.GetBytes(content); X509Certificate2 myX509 = new X509Certificate2(filePath, password, X509KeyStorageFlags.Exportable);
var rsa = (RSACryptoServiceProvider)myX509.PrivateKey;
MD5CryptoServiceProvider m5 = new MD5CryptoServiceProvider(); byte[] signData = rsa.SignData(Data, m5);
return Convert.ToBase64String(signData);
}

问题解决!

.NET RSA解密、签名、验签的相关教程结束。

《.NET RSA解密、签名、验签.doc》

下载本文的Word格式文档,以方便收藏与打印。