现有请求方A,与接收方B,以下是请求方的操作:
以下是请求方的操作:
假设 A 传输的内容为 Mark
1.组装报文,如: RSA_Mark (按照消息头_非业务参数_业务参数排序),使用固定的消息头 RSA 方便知道对方解密成功
2.用 A的私钥 对报文 RSA_Mark 签名,假设签名结果为 XJ9B5D1
3.把签名结果组装在原报文末尾,如:RSA_Mark_XJ9B5D1
4.用 B的公钥 对报文 RSA_Mark_XJ9B5D1 加密,结果假设为: NE03WBEN12=
5.将加密结果 NE03WBEN12= 发送给 B
以下是接收方的操作:
1.接到密文 NE03WBEN12=
2.用 B的私钥 进行解密,得到:RSA_Mark_XJ9B5D1
3.检验报文消息头是否为 RSA ,以检验是否是用 B的公钥 进行加密
4.解密成功后, 截取签名 消息尾得到: XJ9B5D1
5.用 A的公钥 对 消息体 进行验签,待验证的消息体为 RSA_Mark ,签名值 XJ9B5D1
6.若成功验签,贼说明该消息来自 A 合法数据
(注:报文排序规则,根据非业务参数和业务参数拼接字符串并按照首字母排序,如果首字母相同,则按照第二个字母排序,以此类推
如:rp_13510103189_1540803537222_1_1540803537_10
: rp_{mobile.value}{sn.value}{source.value}{timestamp.value}{ua.value}
private const int RsaKeySize = 1024; //要使用的密钥的大小(以位为单位)
private const string publicKeyFileName = "ServerRSA.Pub"; //公钥
private const string privateKeyFileName = "ServerRSA.Private"; //私钥
private static string basePathToStoreKeys = ConfigurationManager.AppSettings["basePathToStoreServerKeys"]; //从配置文件中读取密钥存放路径
public static string GenerateKeys()
{
string path = basePathToStoreKeys;
using (var rsa = new RSACryptoServiceProvider(RsaKeySize))
{
try
{
// 获取私钥和公钥。
var publicKey = rsa.ToXmlString(false);
var privateKey = rsa.ToXmlString(true);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
bool result = false;
string resultMsg = "该路径已存在密钥对,生成失败";
// 保存到磁盘
if (!File.Exists(Path.Combine(path, publicKeyFileName)))
{
File.WriteAllText(Path.Combine(path, publicKeyFileName), publicKey);
result = true;
}
if (!File.Exists(Path.Combine(path, privateKeyFileName)))
{
File.WriteAllText(Path.Combine(path, privateKeyFileName), privateKey);
result = true;
}
if (result)
{
resultMsg = string.Format("生成的RSA密钥对的路径: {0}\\ [{1}, {2}]", path, publicKeyFileName, privateKeyFileName);
}
return resultMsg;
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
}
这里的方法是把对象转为字典,然后对Key值进行升序输出字符串
public static Dictionary<string, object> ObjConvertDic(Dictionary<string, object> dic, T obj)
{
//判空
if (obj == null)
{
return dic;
}
Type t = obj.GetType(); // 获取对象对应的类, 对应的类型
PropertyInfo[] pi = t.GetProperties(BindingFlags.Public | BindingFlags.Instance); // 获取当前type公共属性
string dickeyname = string.Empty; //用于存储 表名+字段名
foreach (PropertyInfo p in pi)
{
MethodInfo m = p.GetGetMethod();
if (m != null && m.IsPublic)
{
dickeyname = t.Name + "_" + p.Name;
// 进行判NULL处理 以及 重复键处理
if (m.Invoke(obj, new object[] { }) != null && !dic.ContainsKey(dickeyname))
{
dic.Add(dickeyname, m.Invoke(obj, new object[] { })); // 向字典添加元素
}
}
}
return dic;
}
/// <summary>
/// 字典中将key值进行升序排序,并将对应的value值拼接为字符串输出
/// </summary>
/// <param name="dic"></param>
/// <returns></returns>
public static string DicSortToString(Dictionary<string, object> dic)
{
//根据字典键升序
var ascDic = from objDic in dic orderby objDic.Key ascending select objDic;
//报文消息头
string str = ConfigurationManager.AppSettings["messageHeader"];
foreach (var item in ascDic)
{
str += "_" + item.Value;
}
return str;
}
public static string privateToSign(string str)
{
//判空
if(string.IsNullOrEmpty(str))
{
return null;
}
//要签名文本编码为base64
byte[] hashByteSignture = System.Text.Encoding.Unicode.GetBytes(str);
//加载私钥
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
var privateXmlKey = File.ReadAllText(Path.Combine(basePathToStoreKeys, privateKeyFileName));
rsa.FromXmlString(privateXmlKey);
//哈希算法:SHA1(160bit)、SHA256(256bit)、MD5(128bit)
byte[] sign = rsa.SignData(hashByteSignture, CryptoConfig.MapNameToOID("SHA1"));
return Convert.ToBase64String(sign);
}
将生成的签名追加到原报文尾部得到新报文
这里加密使用了分段加密,因为1024位的证书,加密时最大支持117个字节,解密时为128;2048位的证书,加密时最大支持245个字节,解密时为256。
/// <summary>
/// 用给定路径的RSA公钥文件加密纯文本。
/// </summary>
/// <param name="plainText">要加密的文本</param>
/// <param name="pathToPublicKey">用于加密的公钥路径.</param>
/// <returns>表示加密数据的64位编码字符串.</returns>
private static string Encrypt(string plainText, string pathToPublicKey)
{
using (var rsa = new RSACryptoServiceProvider(RsaKeySize))
{
try
{
//加载公钥读取xml
string publicXmlKey = File.ReadAllText(pathToPublicKey);
rsa.FromXmlString(publicXmlKey);
byte[] bytesToEncrypt = Encoding.Unicode.GetBytes(plainText);
//分段加密
int keySize = rsa.KeySize / 8;
int bufferSize = keySize - 11;
byte[] buffer = new byte[bufferSize];
//内存流,为系统内存提供读写操作
MemoryStream msInput = new MemoryStream(bytesToEncrypt);
MemoryStream msOuput = new MemoryStream();
int readLen = msInput.Read(buffer, 0, bufferSize);
while (readLen > 0)
{
byte[] dataToEnc = new byte[readLen];
Array.Copy(buffer, 0, dataToEnc, 0, readLen);
//加密 使用从缓冲区读取的数据将字节块写入当前流
byte[] encData = rsa.Encrypt(dataToEnc, false);
msOuput.Write(encData, 0, encData.Length);
readLen = msInput.Read(buffer, 0, bufferSize);
}
msInput.Close();
byte[] result = msOuput.ToArray(); //得到加密结果
msOuput.Close();
//var bytesEncrypted = rsa.Encrypt(bytesToEncrypt, false);
return Convert.ToBase64String(result);
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
}
注:非业务参数使用HTTP请求的header头解析。
/// <summary>
/// Decrypts encrypted text given a RSA private key file path.给定路径的RSA私钥文件解密 加密文本
/// </summary>
/// <param name="encryptedText">密文</param>
/// <param name="pathToPrivateKey">用于解密的私钥路径.</param>
/// <returns>未加密数据的字符串</returns>
private static string Decrypt(string encryptedText, string pathToPrivateKey)
{
using (var rsa = new RSACryptoServiceProvider(RsaKeySize))
{
try
{
//加载私钥
string privateXmlKey = File.ReadAllText(pathToPrivateKey);
rsa.FromXmlString(privateXmlKey);
byte[] bytesEncrypted = Convert.FromBase64String(encryptedText);
//分段解密
int keySize = rsa.KeySize / 8;
byte[] buffer = new byte[keySize];
//内存流,为系统内存提供读写操作
MemoryStream msInput = new MemoryStream(bytesEncrypted);
MemoryStream msOuput = new MemoryStream();
int readLen = msInput.Read(buffer, 0, keySize);
while (readLen > 0)
{
byte[] dataToDec = new byte[readLen];
Array.Copy(buffer, 0, dataToDec, 0, readLen);
//解密 使用从缓冲区读取的数据将字节块写入当前流
byte[] encData = rsa.Decrypt(dataToDec, false);
msOuput.Write(encData, 0, encData.Length);
readLen = msInput.Read(buffer, 0, keySize);
}
//关闭内存流
msInput.Close();
byte[] result = msOuput.ToArray(); //得到解密结果
msOuput.Close();
return System.Text.Encoding.Unicode.GetString(result);
}
catch (Exception ex)
{
return ex.ToString();
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
}
/// <summary>
/// 验签
/// </summary>
/// <param name="sign"></param>
/// <returns></returns>
public static string CheckSign(string message)
{
//获取非业务参数header对象的长度
HeadersInfo headersInfo = new HeadersInfo();
PropertyInfo[] propertyInfo = headersInfo.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
//截取第一个下划线'_'前的文本为消息头,最后一个下划线'_'后的文本为签名
string[] list = message.Split('_');
//判断长度是否合法
if (list.Length < propertyInfo.Length)
{
return UtilityEnum.InspectionResult.Invalid.ToString();
}
string messageHeader = list[0];
string timestamp = list[1];
//要验证的签名数据
string signature = list[list.Length - 1];
byte[] hashByteSignature = Convert.FromBase64String(signature);
//查看消息头是否正确
if (messageHeader != ConfigurationManager.AppSettings["messageHeader"])
{
return UtilityEnum.InspectionResult.Invalid.ToString();
}
//文本截取签名(含下划线'_')后,是已签名的数据
string buffer = message.Substring(0, message.Length - signature.Length - 1);
byte[] fromBase64Buffer = Encoding.Unicode.GetBytes(buffer);
//加载发送方的公钥进行验签
var rsa = new RSACryptoServiceProvider();
var publicXmlKey = File.ReadAllText(Path.Combine(ConfigurationManager.AppSettings["basePathToStoreClientKeys"], "ClientRSA.Pub"));
rsa.FromXmlString(publicXmlKey);
//MD5 mD5 = new MD5CryptoServiceProvider();
//rsa.VerifyData(hashByteSignature, mD5, Convert.FromBase64String(buffer));
//rsa.VerifyData(hashByteSignature, CryptoConfig.MapNameToOID("MD5"), Convert.FromBase64String(buffer));
//哈希算法:SHA1(160bit)、SHA256(256bit)、MD5(128bit)
if (rsa.VerifyData(fromBase64Buffer, CryptoConfig.MapNameToOID("SHA1"), hashByteSignature))
{
//判断timestamp是否超时
if (UtilityHelper.IsTimestampValidity(timestamp))
{
return UtilityEnum.InspectionResult.Timeout.ToString();
}
}
else
{
return UtilityEnum.InspectionResult.Invalid.ToString();
}
return UtilityEnum.InspectionResult.Validity.ToString();
}
对加密方法返回的byte[],用Convert.ToBase64String
对普通的文字操作,用Encoding.UTF8.GetBytes()
Encoding.UTF8.GetString是针对使用utf8编码得到的字符串对应的byte[]使用,可以还原我们能看懂的字符串
而Convert.ToBase64String是对任意byte[]都可使用,得到的是用字符串表示的byte[]信息 内容类似"QJ5/EYPtJPZ5Inyv="
如果一个地方用Convert.ToBase64String来操作byte[]获得string,而另一个地方要用相同的byte[],最好对应用 Convert.FromBase64String(string);
一般 Base64 用于转格式,如:图片。
Encoding 用于转换编码,如:文字(普通文字不是Base64 编码)
以上。
如有不合理的地方或更好的建议,请不吝赐教,谢谢!