RSA算法中PEM与XML密钥相互转换---C#实现(一)
RSA是一种非对称加解密算法,用于实现签名/认证等。在.Net框架中提供了System.Security.Cryptography.RSACryptoServiceProvider类,用于封装实现RSA算法,但这个类使用的公钥/私钥格式为XML,这是.Net特有的格式,而其它语言如Java编程中一般使用PEM或DER等格式,OpenSSL规范中也特荐为PEM格式。项目中经常会遇到XML与PEM格式的密钥相互转换的需求,我将分4篇文章,总结C#中PEM与XML格式密钥的相互转换。今天先从分析PEM公钥开始,后面再分析私钥。
一、PEM公钥的使用
PEM公钥文件格式
PEM文件用Notepad打开,可以看到如下,图一:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArEEtglN1RQ0mNnd7qZko
z4BIpR01NnWw6V98Neqne+NLIcKvimlF8lYfYH+mtbFev4/yFzPIP9DhB18eMbCv
VpqoqoukYFWJkt9hAmLISshrxqc71/1IVV+R6oP8+v3o8hbqeyHv5XaRYVv5kOwy
Om11lqT5P7V9iRCMq7vrTiXeCuCNQ+kse+XJoNzFxJtz9gE/7Q21xmgMuCxHWewn
3hWePBT5hClcmMktqG4NT+iynMO06zNf8+Ay4mp+t3XGwJ03NLVEREctLsFKtpC9
GX5K9CWt9h85eOGzqYu94RLrSVnFP/NwnWzA3P5xD88gbHtEx34lsdeBTUbSoGhs
uQIDAQAB
-----END PUBLIC KEY-----
分三部分:头部标识(第一行),结束标识(最后一行),中间部分是数据,用Base64编码解码,图二:

数据存储的格式见下表总结,图三:

图三表格的“内容”列,红字是文件格式要求的固定值,与图二中红字相对应。浅兰字为示例内容,不同的Key文件,值不同。
图二中绿字字节是长度值,对应图三内容列的长度值,表示后续数据块占用字节长度。其规则为:
- 当长度<128时,用1Byte表示,最高位=0,低7位表示长度值。
- 当长度>=128时,用多Byte表示,其中第1Byte的最高位=1,低7位的值=x,表示后续x字节的值存储长度值,低字节高有效位,BigEndian方式。
表格中各数据段说明:
- 第1部分:PEM头,由标识(0x30),长度区组成,后续Inner数据块。
- 第2部分:Inner块,前16字节为固定标识,长度区,后续bitString数据块
- 第3部分:BitString块,由标识,长度区组成,后续params块。长度区指示后续的参数块总长度。后续的参数块有2个,依次排列分别为:模(modulus)和指数(exponent)。
- 第4部分:模参数值
- 第5部分:指数值
C#实现
理解了PEM公钥文件的格式,如何在C#中使用就比较方便了:
- 定义RSAParameters变量,读入PEM的模数、指数值。
- 使用RSACryptoServiceProvider.ImportParameters方法,创建RSA实例。
- 若需要转换成XML格式,使用RSACryptoServiceProvider.ToXmlString(false),生成XML格式的公钥。
读入PEM文件的代码如下:
其中GetPemBytes方法是把PEM文件的数据部分,按Base64解码,返回字节数组。
internal static RSAParameters GetRSAParametersPub(string fn,delegateShowMsg show)
{
RSAParameters p = new RSAParameters();
byte[] keyBytes = GetPemBytes(fn);
if (keyBytes[0] != 0x30) throw new Exception($"sequence wrong {keyBytes[0]:x2}");
int len = 0;
int id = 1;
len = DecodeLenth(keyBytes, ref id);
int inner0 = id + 1;
int inner1 = inner0 + len - 1;
id = inner0 + 16;
len = DecodeLenth(keyBytes, ref id);
int bitString0 = id + 1;
int bitString1 = bitString0 + len - 1;
id = bitString0 + 2;
len = DecodeLenth(keyBytes, ref id);
int params0 = id + 1;
int params1 = params0 + len - 1;
id = params0;
p.Modulus = DecodeIntegerBigEndian(keyBytes, ref id);
p.Exponent = DecodeIntegerBigEndian(keyBytes, ref id);
return p;
}
private static int DecodeLenth(byte[] stream, ref int id)
{
int len = stream[id];
if (len == 0) throw new ArgumentOutOfRangeException("length", "length should be not 0");
if (len < 0x80)
{
return len;
}
else
{
len = len & 0x7f;
int temp = 0;
for (var i = len - 1; i >= 0; i--)
{
id += 1;
temp += stream[id] << i * 8;
}
return temp;
}
}
private static byte[] DecodeIntegerBigEndian(byte[] stm, ref int id)
{
if (stm[id] != 0x02) throw new Exception("params id invalid");
id +=1;
var len = DecodeLenth(stm, ref id);
int end0 = id + 1;
int end1 = end0 + len - 1;
//删除补零,>7f时补0
id = end0;
while (stm[id] == 0)
{
id++;
}
if (stm[id] > 0x7f)
{
end0 = id;
}
byte[] result= stm.Where((key, index) => index >= end0 & index <= end1).ToArray();
id = end1 + 1;
return result;
}
这是头一次在CSDN上发表文章,这个MarkDown编辑器使用不顺手,很多功能不知如何实现,比如字体颜色、居中等,写作过程插入代码时还导致全篇显示错误,折腾了几次才明白似乎代码中不能有空行,是这样吗?无论如何,如果这篇文章能对大家有所帮助,也不枉这番功夫哈,欢迎点赞打赏鼓励-,下一篇将介绍PEM私钥的使用,敬请期待~
更多推荐



所有评论(0)