RSA公钥只返回一串base64字符 怎么使用

2025-04-07 17:08:42
推荐回答(1个)
回答1:

正常的情况下RSA公钥是不会只返回一串base64编码的。我们从原理上说起:

RSA密钥对有很多个参数,d /dp/dq/Expoent/InverseQ/Modulus/p/q,原理上我们是知道他是由两个大素数p和q的乘积,正常的情况下含有私钥的文件会反这些参数全部给出,而如果只给出公钥时,事实上是给定了n(Modules)和e(Exponent)两个参数,这两个参数可以用来加密,我们也都知道,如何将这两个参数有效地传递给另一方呢?或者说我们把这两个参数如何给其他人呢,这个就是我们常说的公钥文件,当然有什么pfx等等,但这里我们如果简要地传递给第二方时,我们会经常使用一个对象RSAParameters类,这个对象就是这几个参数,如果我需要传递给你时,有效的方式就是把RSAParameters序列化,然后把序列化的内容给你就可以了。

在C#中有两种常用的序列化形式,一种就是byte序列化,一种就是xml序列化(一般使用JSON序列化的时候不多,因为javascript对实现RSA原生支技较为复杂)。

byte序列化不适合在网络上传输,所以一般都使用XML序列化,xml适合网上传输,他会把二进制自动序列化为base64编码。而事实上RSAParameters类和几个参数全是byte类型的(方便存储超大数字),所以会序列化成如下形式:


   
   
   


   
   
   
   
   

很明显这是一个序列化后的xml形式,其中几个参数都是被自动序列化成base64编码,如果是公钥的话,其中两个参数有值,其他的在序列化过程中如果没有值不序列化,所以你可以最终看到只有这两个参数的一个结构。

PS:序列化与反序列化由RSACryptoServiceProvider类的ToXmlString(bool)执行,其中参数表示是否含有私钥,如果是则全部参数都会存在,否则只含有公钥。拿到这个文件时,可以直接使用FromXmlString(string)进行导入即可使用。

但事实上,RSACryptoServiceProvidder提供的不仅有ToXmlString()方法,还存在有ExportCspBlob()方法和ExportRSAParameter()方法。导出RSAParameter方法不用说了,基本上用于本地版本,就是导出一个RSAParameter结构,而ExportCspBlob方法与ToXmlString()有一定的比较性,它导出的是一个二进制的形式,也就是二进制序列化后的数值,由于其是二进制形的数值。但两种形式的序列化虽然都是序列化RSAParameter结构,但并没有让你直接使用XmlSerializer类进行序列化,而由于RSACryptoServiceProvider中的方法已经实现了,当然反序列化也是同理。

但由我们在使用.net自动生成公私钥时,其Exponent参数永远是65537(ABAQ),其实对于.net自动生成的公私钥对算是一个测试的公私钥对,所以它保证了Exponent为固定的一个值。所以很多人不太明白这个道理,以为真正使用的公私有对的e都一定被选定为该值,所以有一类人为了“节省”带宽,自做主张在传递公钥时只传递了一个Modulus的值!换句话来说,这些非商用的情况下的可行的,而且在商用的情况下也有一定的可行性。这里你可能只看到对方传递的是一个base64编码——这种情况显然不是规范的,所以尽量不使用或少使用,如果某企业有自己的证书或提供自己的公钥时,这种情况显然会导致你重新修改程序。但这也是你见到全是base64编码的原因。(他只传递了modulus的值 ,而不是RSAParameter序列化结构)。

另外一类的程序员,喜欢造轮子,或者是他们喜欢上了ExportCspBlob()和ImportCspBlob()两个方法,不管是本地还是网络传输,可怜他们只会用一对的方法,总是不知道ToXmlString()和FromXmlString()这一对方法是用来干什么的。所以他们爱好就是动不动用ExportCspBlob()方法导出公钥或私钥对。如果是本地程序这当然没有问题,但如果是远程的话,也使用这个,但导出的一堆byte他们没办法通过网络(我指的html之类的通讯,不是CS模式下)传输,当然有,遇到我们没有传递二进制时设计成编码就可以了。所以指出了一堆二进制之后,把这个二进制进行base64编码来传递——这种情况我见到的最多!如果你看到这种的一堆base64时,需要先手式把base64编码转换成二进制,然后使用ImportCspBlob()方法再导入进去。这种情况看起来没有大问题,但实际上是绕了一个圈圈,感情就会使用ExportCspBlob()方法,为什么不直接考虑使用ToXmlString()方法呢?所以我看到很多情况下base64位编码基本上都属于这种情况!

事实上,我的建议是,如果是本地存储的情况下,比如需要存储成一个文件,这时可以使用ExportCspBlob()方法导到成byte数组,然后可以直接将该数组写入到dat文件中,以方便其他时间的使用;如果是网络传输的情况下,因涉及到网络协议的问题,二进制流传输还行,但如果是文本流传输时使用ToXmlString()方法导出,可以直接让其他程序使用。虽然在微软上没有这么规定,但如果你这么使用却是一个很恰当的使用场景(二进制序列化时,由于参数的值本身就是二进制,所以它序列化时其他最为简单,那些个二进制的值不需要任何的编码处理,所以性能会高一些,而xml序列化则涉及到二进制的编码,所以性能上没有直接二进制高,但如果你先导出成二进制,再base64编码,就让人难以想通了,完全属于对.net类库不熟悉而造成的!很多人可能会说,其他语言中导到的二进制——如果基于这种考虑,理由似乎说得过去,但至少常见的语言中还都是支持xml形式的,包括javascript新版语言原生支持RSA,它只支持xml形式和JSON形式,并不支持二进制形式。所以先导出二进制再编码base64形式纯属多余)。

PS:这一点更为重要,不少人把RSA的公私钥对导出后直接保存成文件,事实上我也这么说了,但是在windows中,尽量避免这样做,除非你要对机器进行迁移时或者其他原因需要,否则不要大摇大摆地把公私钥对存储成一个文件,如果被别有用心的人拿了去,你自己想想后果是什么样的!windows本身提供了一种叫密钥容器的机制,可以存储DSA/RSA/DES/TDES等非对称或对称密钥的安全存储。应当使用这种安全存储的机制,而不是把公钥密文件大摇大摆在丢在某个目录下。关于密钥容器的问题,你可以查阅MSDN上,可以直接使用的。类似的情况还有,某些情况下我们从CA(认证中心)获得一个证书时,也是把证书导入到容器中,然后设置导出密码,销毁原证书文件。如果在迁移时,可以使用密钥导出私钥证书,然后再到其他机器上安装。很多人不太理解这样做的目的,其实密钥管理还涉及到一个人员管理的问题,这样证书只会经过某个特定的人进行处理,其他任何人都无法拿到该证书,但他们可以使用,所以这样做是十分安全的。