ASN.1
抽象文法表示(标記)ASN.1(Abstract Syntax Notation One )一種資料定義語言,描述了對資料進行表示、編碼、傳輸和解碼的資料格式。網絡管理系統中的管理資訊庫(MIB)、應用程式的資料結構、協定資料單元(PDU)都是用ASN.1定義的。
可以了解為ASN.1是對密鑰結構定義的一種規範
密鑰結構類型
PKCS#1
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
PKCS#8
PublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
PublicKey BIT STRING ; 其中的BIT STRING是某個算法自己指定的二進制格式
; RSA算法的話,就是上面的RSAPublicKey
}
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
PrivateKeyInfo ::= SEQUENCE {
version Version,
algorithm AlgorithmIdentifier,
PrivateKey BIT STRING
}
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
密鑰編碼類型
der格式
二進制格式
pem格式
把der格式的資料用base64編碼後,然後再在頭尾加上一段“-----”開始的标記
證書類型
X.509證書
X.509隻包含公鑰,沒有私鑰,這種證書一般公開釋出,可用于放在客服端使用,用于加密、驗簽
PKCS#12證書
因為X.509證書隻包含公鑰,但有些時候我們需要把私鑰和公鑰合并成一個證書,放在服務端使用,用于解密、簽名。
PKCS#12就定義了這樣一種證書,它既包含了公鑰有包含了私鑰。典型的入pfx、p12證書就是PKCS#12證書。
PKCS#7證書
當你收到一個網站的證書後,你需要驗證其真實性。因為一個X.509證書包含了公鑰、持有人資訊、簽名。為了驗證其真實性,你需要簽證其簽名,而驗證簽名則需要簽發的CA機構的公鑰證書。同樣原理,當你拿到CA機構的公鑰證書後,你也需要驗證該CA機構的真實性,而驗證該CA機構的證書,你需要該CA上級機構的CA公鑰證書...以此類推,你需要一直驗證到根證書為止。是以為了驗證一個網站證書的真實性,你需要的不僅一張證書,而是一個證書鍊。而PKCS#7就定義了這樣一個證書鍊的類型結構。典型如p7b字尾名的證書就是這樣的格式。
證書字尾
.cer/.crt:存放公鑰,沒有私鑰,就是一個X.509證書,二進制形式存放
.pfx/.p12:存放公鑰和私鑰,通常包含保護密碼,二進制方式
證書與密鑰關系
數字證書和私鑰是比對的關系。就好比鑰匙牌和鑰匙的關系。在數字證書簽發的時候,數字證書簽發系統(CA系統),在生成數字證書的同時,還會随機生成一對密鑰,一個私鑰,一個公鑰。數字證書标示使用者身份, 相比對的私鑰和公鑰,則是用來保障使用者身份的可認證性。就好比咱們拿着一串鑰匙,每個鑰匙上都标明有時某某房間的鑰匙,但是否是真的,還需要看能不能打開相應的房門。
密鑰生成
/// <summary>
/// 取得私鑰和公鑰 XML 格式,傳回數組第一個是私鑰,第二個是公鑰.
/// </summary>
/// <param name="size">密鑰長度,預設1024,可以為2048</param>
/// <returns></returns>
public static string[] CreateXmlKey(int size = 1024)
{
//密鑰格式要生成pkcs#1格式的 而不是pkcs#8格式的
RSACryptoServiceProvider sp = new RSACryptoServiceProvider(size);
string privateKey = sp.ToXmlString(true);//private key
string publicKey = sp.ToXmlString(false);//public key
return new string[] { privateKey, publicKey };
}
/// <summary>
/// 取得私鑰和公鑰 CspBlob 格式,傳回數組第一個是私鑰,第二個是公鑰.
/// </summary>
/// <param name="size"></param>
/// <returns></returns>
public static string[] CreateCspBlobKey(int size = 1024)
{
//密鑰格式要生成pkcs#1格式的 而不是pkcs#8格式的
RSACryptoServiceProvider sp = new RSACryptoServiceProvider(size);
string privateKey = System.Convert.ToBase64String(sp.ExportCspBlob(true));//private key
string publicKey = System.Convert.ToBase64String(sp.ExportCspBlob(false));//public key
return new string[] { privateKey, publicKey };
}
/// <summary>
/// 導出PEM PKCS#1格式密鑰對,傳回數組第一個是私鑰,第二個是公鑰.
/// </summary>
public static string[] CreateKey_PEM_PKCS1(int size = 1024)
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(size);
string privateKey = RSA_PEM.ToPEM(rsa, false, false);
string publicKey = RSA_PEM.ToPEM(rsa, true, false);
return new string[] { privateKey, publicKey };
}
/// <summary>
/// 導出PEM PKCS#8格式密鑰對,傳回數組第一個是私鑰,第二個是公鑰.
/// </summary>
public static string[] CreateKey_PEM_PKCS8(int size = 1024, bool convertToPublic = false)
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(size);
string privateKey = RSA_PEM.ToPEM(rsa, false, true);
string publicKey = RSA_PEM.ToPEM(rsa, true, true);
return new string[] { privateKey, publicKey };
}
後端加/解密方法使用
/// <summary>
/// RSA加密
/// </summary>
/// <param name="Data">原文</param>
/// <param name="PublicKeyString">公鑰</param>
/// <param name="KeyType">密鑰類型XML/PEM</param>
/// <returns></returns>
public static string RSAEncrypt(string Data,string PublicKeyString,string KeyType)
{
byte[] data = Encoding.GetEncoding("UTF-8").GetBytes(Data);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
switch (KeyType)
{
case "XML":
rsa.FromXmlString(PublicKeyString);
break;
case "PEM":
rsa = RSA_PEM.FromPEM(PublicKeyString);
break;
default:
throw new Exception("不支援的密鑰類型");
}
//加密塊最大長度限制,如果加密資料的長度超過 秘鑰長度/8-11,會引發長度不正确的異常,是以進行資料的分塊加密
int MaxBlockSize = rsa.KeySize / 8 - 11;
//正常長度
if (data.Length <= MaxBlockSize)
{
byte[] hashvalueEcy = rsa.Encrypt(data, false); //加密
return System.Convert.ToBase64String(hashvalueEcy);
}
//長度超過正常值
else
{
using (MemoryStream PlaiStream = new MemoryStream(data))
using (MemoryStream CrypStream = new MemoryStream())
{
Byte[] Buffer = new Byte[MaxBlockSize];
int BlockSize = PlaiStream.Read(Buffer, 0, MaxBlockSize);
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, MaxBlockSize);
}
return System.Convert.ToBase64String(CrypStream.ToArray(), Base64FormattingOptions.None);
}
}
}
/// <summary>
/// RSA解密
/// </summary>
/// <param name="Data">密文</param>
/// <param name="PrivateKeyString">私鑰</param>
/// <param name="KeyType">密鑰類型XML/PEM</param>
/// <returns></returns>
public static string RSADecrypt(string Data,string PrivateKeyString, string KeyType)
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
switch (KeyType)
{
case "XML":
rsa.FromXmlString(PrivateKeyString);
break;
case "PEM":
rsa = RSA_PEM.FromPEM(PrivateKeyString);
break;
default:
throw new Exception("不支援的密鑰類型");
}
int MaxBlockSize = rsa.KeySize / 8; //解密塊最大長度限制
//正常解密
if (Data.Length <= MaxBlockSize)
{
byte[] hashvalueDcy = rsa.Decrypt(System.Convert.FromBase64String(Data), false);//解密
return Encoding.GetEncoding("UTF-8").GetString(hashvalueDcy);
}
//分段解密
else
{
using (MemoryStream CrypStream = new MemoryStream(System.Convert.FromBase64String(Data)))
using (MemoryStream PlaiStream = new MemoryStream())
{
Byte[] Buffer = new Byte[MaxBlockSize];
int BlockSize = CrypStream.Read(Buffer, 0, MaxBlockSize);
while (BlockSize > 0)
{
Byte[] ToDecrypt = new Byte[BlockSize];
Array.Copy(Buffer, 0, ToDecrypt, 0, BlockSize);
Byte[] Plaintext = rsa.Decrypt(ToDecrypt, false);
PlaiStream.Write(Plaintext, 0, Plaintext.Length);
BlockSize = CrypStream.Read(Buffer, 0, MaxBlockSize);
}
string output = Encoding.GetEncoding("UTF-8").GetString(PlaiStream.ToArray());
return output;
}
}
}
前端加密方法
注:jsencrypt預設PKCS#1結構,生成密鑰時需要注意
<script src="http://passport.cnblogs.com/scripts/jsencrypt.min.js"></script>
var encryptor = new JSEncrypt() // 建立加密對象執行個體
//之前ssl生成的公鑰,複制的時候要小心不要有空格
var pubKey = '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1QQRl0HlrVv6kGqhgonD6A9SU6ZJpnEN+Q0blT/ue6Ndt97WRfxtS'+
'As0QoquTreaDtfC4RRX4o+CU6BTuHLUm+eSvxZS9TzbwoYZq7ObbQAZAY+SYDgAA5PHf1wNN20dGMFFgVS/y0ZWvv1UNa2laEz0I8Vmr5ZlzIn88GkmSiQIDAQAB-----END PUBLIC KEY-----'
encryptor.setPublicKey(pubKey)//設定公鑰
var rsaPassWord = encryptor.encrypt('要加密的内容') // 對内容進行加密
c#pem格式轉換
注:c#的RSACryptoServiceProvider預設隻支援xml格式的密鑰解析
public class RSA_Unit
{
static public string Base64EncodeBytes(byte[] byts)
{
return System.Convert.ToBase64String(byts);
}
static public byte[] Base64DecodeBytes(string str)
{
try
{
return System.Convert.FromBase64String(str);
}
catch
{
return null;
}
}
/// <summary>
/// 把字元串按每行多少個字斷行
/// </summary>
static public string TextBreak(string text, int line)
{
var idx = 0;
var len = text.Length;
var str = new StringBuilder();
while (idx < len)
{
if (idx > 0)
{
str.Append('\n');
}
if (idx + line >= len)
{
str.Append(text.Substring(idx));
}
else
{
str.Append(text.Substring(idx, line));
}
idx += line;
}
return str.ToString();
}
}
static public class Extensions
{
/// <summary>
/// 從數組start開始到指定長度複制一份
/// </summary>
static public T[] sub<T>(this T[] arr, int start, int count)
{
T[] val = new T[count];
for (var i = 0; i < count; i++)
{
val[i] = arr[start + i];
}
return val;
}
static public void writeAll(this Stream stream, byte[] byts)
{
stream.Write(byts, 0, byts.Length);
}
}
點選并拖拽以移動
public class RSA_PEM
{
public static RSACryptoServiceProvider FromPEM(string pem)
{
var rsaParams = new CspParameters();
rsaParams.Flags = CspProviderFlags.UseMachineKeyStore;
var rsa = new RSACryptoServiceProvider(rsaParams);
var param = new RSAParameters();
var base64 = _PEMCode.Replace(pem, "");
var data = RSA_Unit.Base64DecodeBytes(base64);
if (data == null)
{
throw new Exception("PEM内容無效");
}
var idx = 0;
//讀取長度
Func<byte, int> readLen = (first) =>
{
if (data[idx] == first)
{
idx++;
if (data[idx] == 0x81)
{
idx++;
return data[idx++];
}
else if (data[idx] == 0x82)
{
idx++;
return (((int)data[idx++]) << 8) + data[idx++];
}
else if (data[idx] < 0x80)
{
return data[idx++];
}
}
throw new Exception("PEM未能提取到資料");
};
//讀取塊資料
Func<byte[]> readBlock = () =>
{
var len = readLen(0x02);
if (data[idx] == 0x00)
{
idx++;
len--;
}
var val = data.sub(idx, len);
idx += len;
return val;
};
//比較data從idx位置開始是否是byts内容
Func<byte[], bool> eq = (byts) =>
{
for (var i = 0; i < byts.Length; i++, idx++)
{
if (idx >= data.Length)
{
return false;
}
if (byts[i] != data[idx])
{
return false;
}
}
return true;
};
if (pem.Contains("PUBLIC KEY"))
{
/****使用公鑰****/
//讀取資料總長度
readLen(0x30);
if (!eq(_SeqOID))
{
throw new Exception("PEM未知格式");
}
//讀取1長度
readLen(0x03);
idx++;//跳過0x00
//讀取2長度
readLen(0x30);
//Modulus
param.Modulus = readBlock();
//Exponent
param.Exponent = readBlock();
}
else if (pem.Contains("PRIVATE KEY"))
{
/****使用私鑰****/
//讀取資料總長度
readLen(0x30);
//讀取版本号
if (!eq(_Ver))
{
throw new Exception("PEM未知版本");
}
//檢測PKCS8
var idx2 = idx;
if (eq(_SeqOID))
{
//讀取1長度
readLen(0x04);
//讀取2長度
readLen(0x30);
//讀取版本号
if (!eq(_Ver))
{
throw new Exception("PEM版本無效");
}
}
else
{
idx = idx2;
}
//讀取資料
param.Modulus = readBlock();
param.Exponent = readBlock();
param.D = readBlock();
param.P = readBlock();
param.Q = readBlock();
param.DP = readBlock();
param.DQ = readBlock();
param.InverseQ = readBlock();
}
else
{
throw new Exception("pem需要BEGIN END标頭");
}
rsa.ImportParameters(param);
return rsa;
}
static private Regex _PEMCode = new Regex(@"--+.+?--+|\s+");
static private byte[] _SeqOID = new byte[] { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
static private byte[] _Ver = new byte[] { 0x02, 0x01, 0x00 };
/// <summary>
/// 将RSA中的密鑰對轉換成PEM格式,usePKCS8=false時傳回PKCS#1格式,否則傳回PKCS#8格式,如果convertToPublic含私鑰的RSA将隻傳回公鑰,僅含公鑰的RSA不受影響
/// </summary>
public static string ToPEM(RSACryptoServiceProvider rsa, bool convertToPublic, bool usePKCS8)
{
//https://www.jianshu.com/p/25803dd9527d
//https://www.cnblogs.com/ylz8401/p/8443819.html
//https://blog.csdn.net/jiayanhui2877/article/details/47187077
//https://blog.csdn.net/xuanshao_/article/details/51679824
//https://blog.csdn.net/xuanshao_/article/details/51672547
var ms = new MemoryStream();
//寫入一個長度位元組碼
Action<int> writeLenByte = (len) =>
{
if (len < 0x80)
{
ms.WriteByte((byte)len);
}
else if (len <= 0xff)
{
ms.WriteByte(0x81);
ms.WriteByte((byte)len);
}
else
{
ms.WriteByte(0x82);
ms.WriteByte((byte)(len >> 8 & 0xff));
ms.WriteByte((byte)(len & 0xff));
}
};
//寫入一塊資料
Action<byte[]> writeBlock = (byts) =>
{
var addZero = (byts[0] >> 4) >= 0x8;
ms.WriteByte(0x02);
var len = byts.Length + (addZero ? 1 : 0);
writeLenByte(len);
if (addZero)
{
ms.WriteByte(0x00);
}
ms.Write(byts, 0, byts.Length);
};
//根據後續内容長度寫入長度資料
Func<int, byte[], byte[]> writeLen = (index, byts) =>
{
var len = byts.Length - index;
ms.SetLength(0);
ms.Write(byts, 0, index);
writeLenByte(len);
ms.Write(byts, index, len);
return ms.ToArray();
};
if (rsa.PublicOnly || convertToPublic)
{
/****生成公鑰****/
var param = rsa.ExportParameters(false);
//寫入總位元組數,不含本段長度,額外需要24位元組的頭,後續計算好填入
ms.WriteByte(0x30);
var index1 = (int)ms.Length;
//固定内容
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
ms.writeAll(_SeqOID);
//從0x00開始的後續長度
ms.WriteByte(0x03);
var index2 = (int)ms.Length;
ms.WriteByte(0x00);
//後續内容長度
ms.WriteByte(0x30);
var index3 = (int)ms.Length;
//寫入Modulus
writeBlock(param.Modulus);
//寫入Exponent
writeBlock(param.Exponent);
//計算空缺的長度
var byts = ms.ToArray();
byts = writeLen(index3, byts);
byts = writeLen(index2, byts);
byts = writeLen(index1, byts);
return "-----BEGIN PUBLIC KEY-----\n" + RSA_Unit.TextBreak(RSA_Unit.Base64EncodeBytes(byts), 64) + "\n-----END PUBLIC KEY-----";
}
else
{
/****生成私鑰****/
var param = rsa.ExportParameters(true);
//寫入總位元組數,後續寫入
ms.WriteByte(0x30);
int index1 = (int)ms.Length;
//寫入版本号
ms.writeAll(_Ver);
//PKCS8 多一段資料
int index2 = -1, index3 = -1;
if (usePKCS8)
{
//固定内容
ms.writeAll(_SeqOID);
//後續内容長度
ms.WriteByte(0x04);
index2 = (int)ms.Length;
//後續内容長度
ms.WriteByte(0x30);
index3 = (int)ms.Length;
//寫入版本号
ms.writeAll(_Ver);
}
//寫入資料
writeBlock(param.Modulus);
writeBlock(param.Exponent);
writeBlock(param.D);
writeBlock(param.P);
writeBlock(param.Q);
writeBlock(param.DP);
writeBlock(param.DQ);
writeBlock(param.InverseQ);
//計算空缺的長度
var byts = ms.ToArray();
if (index2 != -1)
{
byts = writeLen(index3, byts);
byts = writeLen(index2, byts);
}
byts = writeLen(index1, byts);
var flag = " PRIVATE KEY";
if (!usePKCS8)
{
flag = " RSA" + flag;
}
return "-----BEGIN" + flag + "-----\n" + RSA_Unit.TextBreak(RSA_Unit.Base64EncodeBytes(byts), 64) + "\n-----END" + flag + "-----";
}
}
}