开门见山
网上关于RSA的介绍很多,这里就不再赘述。直接列我们在项目中的应用。
一般导出的公钥私钥格式都是xml或者.pem
我们使用的是.pem格式。
1、读取出.pem的内容;
2、用.pem内容实例化RSA对象;
3、调用RSA对象的加密接口,参数是要加密的内容。
源码如下:
Pem类
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
namespace Utils.StandardUtils.RSA
{
[Serializable]
public class RSA_PEM
{
/// <summary>
/// modulus 模数n,公钥、私钥都有
/// </summary>
public byte[] Key_Modulus;
/// <summary>
/// publicExponent 公钥指数e,公钥、私钥都有
/// </summary>
public byte[] Key_Exponent;
/// <summary>
/// privateExponent 私钥指数d,只有私钥的时候才有
/// </summary>
public byte[] Key_D;
//以下参数只有私钥才有
/// <summary>
/// prime1
/// </summary>
public byte[] Val_P;
/// <summary>
/// prime2
/// </summary>
public byte[] Val_Q;
/// <summary>
/// exponent1
/// </summary>
public byte[] Val_DP;
/// <summary>
/// exponent2
/// </summary>
public byte[] Val_DQ;
/// <summary>
/// coefficient
/// </summary>
public byte[] Val_InverseQ;
private RSA_PEM() {
}
/// <summary>
/// 通过RSA中的公钥和私钥构造一个PEM,如果convertToPublic含私钥的RSA将只读取公钥,仅含公钥的RSA不受影响
/// </summary>
public RSA_PEM(RSACryptoServiceProvider rsa, bool convertToPublic = false) {
var isPublic = convertToPublic || rsa.PublicOnly;
var param = rsa.ExportParameters(!isPublic);
Key_Modulus = param.Modulus;
Key_Exponent = param.Exponent;
if (!isPublic) {
Key_D = param.D;
Val_P = param.P;
Val_Q = param.Q;
Val_DP = param.DP;
Val_DQ = param.DQ;
Val_InverseQ = param.InverseQ;
}
}
/// <summary>
/// 通过全量的PEM字段数据构造一个PEM,除了模数modulus和公钥指数exponent必须提供外,其他私钥指数信息要么全部提供,要么全部不提供(导出的PEM就只包含公钥)
/// 注意:所有参数首字节如果是0,必须先去掉
/// </summary>
public RSA_PEM(byte[] modulus, byte[] exponent, byte[] d, byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] inverseQ) {
Key_Modulus = modulus;
Key_Exponent = exponent;
Key_D = BigL(d, modulus.Length);
int keyLen = modulus.Length / 2;
Val_P = BigL(p, keyLen);
Val_Q = BigL(q, keyLen);
Val_DP = BigL(dp, keyLen);
Val_DQ = BigL(dq, keyLen);
Val_InverseQ = BigL(inverseQ, keyLen);
}
/// <summary>
/// 通过公钥指数和私钥指数构造一个PEM,会反推计算出P、Q但和原始生成密钥的P、Q极小可能相同
/// 注意:所有参数首字节如果是0,必须先去掉
/// 出错将会抛出异常
/// </summary>
/// <param name="modulus">必须提供模数</param>
/// <param name="exponent">必须提供公钥指数</param>
/// <param name="dOrNull">私钥指数可以不提供,导出的PEM就只包含公钥</param>
public RSA_PEM(byte[] modulus, byte[] exponent, byte[] dOrNull) {
Key_Modulus = modulus;//modulus
Key_Exponent = exponent;//publicExponent
if (dOrNull != null) {
Key_D = BigL(dOrNull, modulus.Length);//privateExponent
//反推P、Q
BigInteger n = BigX(modulus);
BigInteger e = BigX(exponent);
BigInteger d = BigX(dOrNull);
BigInteger p = FindFactor(e, d, n);
BigInteger q = n / p;
if (p.CompareTo(q) > 0) {
BigInteger t = p;
p = q;
q = t;
}
BigInteger exp1 = d % (p - BigInteger.One);
BigInteger exp2 = d % (q - BigInteger.One);
BigInteger coeff = BigInteger.ModPow(q, p - 2, p);
int keyLen = modulus.Length / 2;
Val_P = BigL(BigB(p), keyLen);//prime1
Val_Q = BigL(BigB(q), keyLen);//prime2
Val_DP = BigL(BigB(exp1), keyLen);//exponent1
Val_DQ = BigL(BigB(exp2), keyLen);//exponent2
Val_InverseQ = BigL(BigB(coeff), keyLen);//coefficient
}
}
/// <summary>
/// 密钥位数
/// </summary>
public int KeySize {
get {
return Key_Modulus.Length * 8;
}
}
/// <summary>
/// 是否包含私钥
/// </summary>
public bool HasPrivate {
get {
return Key_D != null;
}
}
/// <summary>
/// 将PEM中的公钥私钥转成RSA对象,如果未提供私钥,RSA中就只包含公钥
/// </summary>
public RSACryptoServiceProvider GetRSA() {
var rsaParams = new CspParameters();
rsaParams.Flags = CspProviderFlags.UseMachineKeyStore;
var rsa = new RSACryptoServiceProvider(rsaParams);
var param = new RSAParameters();
param.Modulus = Key_Modulus;
param.Exponent = Key_Exponent;
if (Key_D != null) {
param.D = Key_D;
param.P = Val_P;
param.Q = Val_Q;
param.DP = Val_DP;
param.DQ = Val_DQ;
param.InverseQ = Val_InverseQ;
}
rsa.ImportParameters(param);
return rsa;
}
/// <summary>
/// 转成正整数,如果是负数,需要加前导0转成正整数
/// </summary>
static public BigInteger BigX(byte[] bigb) {
if (bigb[0] > 0x7F) {
byte[] c = new byte[bigb.Length + 1];
Array.Copy(bigb, 0, c, 1, bigb.Length);
bigb = c;
}
return new BigInteger(bigb.Reverse().ToArray());//C#的二进制是反的
}
/// <summary>
/// BigInt导出byte整数首字节>0x7F的会加0前导,保证正整数,因此需要去掉0
/// </summary>
static public byte[] BigB(BigInteger bigx) {
byte[] val = bigx.ToByteArray().Reverse().ToArray();//C#的二进制是反的
if (val[0] == 0) {
byte[] c = new byte[val.Length - 1];
Array.Copy(val, 1, c, 0, c.Length);
val = c;
}
return val;
}
/// <summary>
/// 某些密钥参数可能会少一位(32个byte只有31个,目测是密钥生成器的问题,只在c#生成的密钥中发现这种参数,java中生成的密钥没有这种现象),直接修正一下就行;这个问题与BigB有本质区别,不能动BigB
/// </summary>
static public byte[] BigL(byte[] bytes, int keyLen) {
if (keyLen - bytes.Length == 1) {
byte[] c = new byte[bytes.Length + 1];
Array.Copy(bytes, 0, c, 1, bytes.Length);
bytes = c;
}
return bytes;
}