RSA 经验之谈

  写代码这么多年了,在涉及RSA跨语言的功能时,总会让人经历一番折磨。因此有必要把如今我掌握的这些零碎的知识总结一下公布出来,省得组内同事再走弯路。因为RSA实在太复杂,还有不少内容没弄懂,也没精力全了解,暂且把问题留下,将来也许能回答。因此这篇文章的标题为“经验之谈”java

  本质上是数字运算git

  RSA算法的本质是找到几个很是大的数,而后作运算,加密解密都是运算而已。进行加解密以前你总要产生一个密钥对,这就是查找这几个数的过程。在产生的数字中,p,q是两个大质数,其乘积为n,通过挑选(你不要管究竟是怎么挑选的)又会获得e和d,公钥就是(n, e),私钥就是(n, d),这个n的位数就是咱们所说的密钥长度, 转换成为二进制,则要为64的倍数github

  从上面你能够了解到,其实所谓密钥最初就是几个很大的数,给咱们带来不少麻烦的实际上是如何保存和传输这些数字。而所谓的加密解密,签名验签,也不过是指数操做而后取模的数字运算而已(这里排除了摘要运算)算法

  基于字节的存储编程

  平时咱们看到被加密或者签名的明文都是字符串,公私钥保存成的也是字符串。实际上在进行RSA运算时,都是基于数字的。在编程语言中,大数字可能很差表示,因此咱们看到的SDK API每每都是以字节数组体现(byte[]),这样一来,在保存和传输时,字节数组和字符串之间的转换就成了容易出错的地方。数组

  待加密/签名的明文的字符串要根据其编码方式转换成为字节数组,好比ascii或者UTF-8。解密以后的要作逆运算转换为明文。而公私钥则要看具体遵守的规范来决定如何转换,这其中涉及到不仅一步编解码处理,不过基本上最后/第一步都是base64和字节之间的转换安全

  C#的那一大堆参数框架

  C#标准库的RSACryptoServiceProvider产生的密钥对儿要以xml字符串的形式导出,联调时候咱们发现不少参数不知道是干什么用的,dp, dq, InverseQ...这些多出来的东西对公钥没影响,但一股脑的都放到私钥中了编程语言

  在PKCS#1 version 2.2中解释其为Chinese Remainder Theorem(CRT),具体这种CRT是怎么计算的我并没深刻研究,但其再也不是上面是说的n是两个大质数的乘积,而是多个的乘积ide

  最要命的是RSACryptoServiceProvider好像并没提供把这个xml转换成为我咱们经常使用的字节数组的方法,因此我在demo中使用的BouncyCastle库

  公钥与私钥的关系

  公钥用来加密私钥解密。签名则是私钥签名,公钥验签。不用硬背,你记住私钥是要本身藏起来的,公钥是分配出去,而后本身推导就好:有人想把信息隐秘的发送给你,就要求只有你能解密。而你想把信息发出的同时证实本身的身份,则要求你可以签名

  也许你会由于你用过的一些框架而疑惑:私钥也能够用来加密,公钥也能够用来解密。我以为用“加密”“解密”这个字眼并不合适,仍是用运算比较好。理论上这一对密钥彻底能够倒过来使用,但这样一来私钥就要公布出去,公钥反倒要妥善保存,可生成密钥对的实际状况是,各语言框架会考虑运算时的复杂程度,公钥中的e通常会取3或者65537, 而私钥中有n了,整个公钥很容易猜到了

  我听到过一种说法,是“私钥能够生成公钥”,这是错误的,上面一条说私钥能够“猜”出公钥,也并非“生成”。我想这说法应该是看到pfx文件导入到IIS中能够再导出公钥,其实pfx文件里本就包含公钥信息,不过这种包含全部参数信息(n, e, d, p, q, dp, dq...)的密钥格式彷佛还很多,不少“私钥文件能够导出公钥”

  签名操做

  签名操做近似于对明文作哈希操做,而后再进行基于私钥的加密,说近似是由于我曾尝试基于上面流程本身实现签名,结果不正确,也许是缺乏某些步骤的处理好比填充

  要作哈希是由于一方面RSA运算比较费时,另外一方面其没有AES那么多种分组算法,RSA只能作其长度(字节数)- 11的这么长的明文的运算(听说这个公式也和填充方式有关,未必准),超过的部分截断,再进行一次运算。因此基本上没人用RSA进行加密解密,都是把明文哈希以后缩短了,再进行签名验签

  PKCS是啥

  咱们在调用SDK时候常常看到的PKCS#1,PKCS#8,甚至到PKCS#12,实际上是“Public-Key Cryptography Standards”,其不一样编号涉及的内容也不相同,以前写代码时候发现PKCS#1常常做为padding的一个参数,其实#1中规范的内容不止如此(这里的Public-Key我认为不是说这个标准仅涉及公钥),甚至包含了怎么把数字转化为字符串的方式

  每一个编号都作了一些事情,你能够从wiki上看到列表

  咱们经常能看见PKCS#1做为Padding的参数,对应还有经常使用的NoPadding,不填充。Java的Demo中,传入cipher algorithm 参数为"RSA"则会默认使用PKCS#1,C#的Demo对应RSAUtils类的Encrypt和Decrypt方法,使用了PKCS1Encoding类,而C的Demo则明确指定了PKCS1/SSL V23(鬼才知道啥是SSL V23),若是你想改为NoPadding,Java cipher algorithm 参数传入“RSA/ECB/NoPadding” (基于Java 8), C#则要去掉PKCS1Encoding类,直接使用RsaEngine类

  其余padding方式均未试验成功

  带padding的方式会致使每次加密结果不同,由于填充了随机数

 

  RSA是否更安全

  RSA未必更难破解,听说等价于AES256安全级别的RSA,密钥长度要远大于如今咱们经常使用的2048。反过来,同安全级别的RSA,听说,听说,要比其余算法慢1000倍。之因此咱们要用它主要仍是由于密钥泄露的概率很低

    还有一大堆没弄懂的事

  为何Java读取公钥都用X509来封装,而读取私钥则用PKCS8封装?哪怕我生成密钥对根本没作指定?

  ASN.1规定了什么,DER和BER格式是怎样?

  还有一大堆crt,cer, pfx格式的证书文件都是啥?

  签名的流程究竟是啥,为何不用指定padding方式?我本身写的签名方法差了哪一步?

  C#标准库到底要怎么导出可供别的语言用的公私钥字符串而不是证书?

 

最后献上4个能够互通的demo(虽然我不太知道是怎么通的)仅供参考

相关文章
相关标签/搜索