V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
felix021
V2EX  ›  程序员

关于 RSA 的一些趣事

  •  
  •   felix021 ·
    felix021 · 2020-03-22 15:23:36 +08:00 · 6107 次点击
    这是一个创建于 1712 天前的主题,其中的信息可能已经有所发展或是发生改变。

    文章有点长(一共 2300 字), 但最后一个故事最有意思, 看不完的话可以直接拉到底

    == 1 ==

    从面试题说起好了。

    在考察到网络这一块的时候,可能会问问 http 协议,聊安全相关问题时,就顺便聊聊 https 。

    大多数候选人知道非对称加密,了解客户端会用 RSA 公钥进行加密。

    那么,服务器在返回响应报文之前,会用什么来进行加密呢?

    有些候选人回答:“用服务器私钥进行加密”。

    内心呵呵一笑

    接着问,那服务器返回的信息岂不是可以被中间人拦截并解密吗?

    候选人一般就放弃挣扎,只能强颜欢笑了。

    有进一步了解过 https 的同学,能够说出在 SSL/TLS 握手以后,会生成一个对称加密密钥。

    那么,既然有非对称加密,为什么还需要使用对称加密呢?

    有些候选人就回答不上来了,只能强颜欢笑+1 。

    实际上这是因为非对称加密的性能通常比对称加密算法差几个数量级。

    以 RSA 为例,在加解密的时候,需要对大整数(典型值是 2048bit,256 字节)做大量乘法、取模等运算;相比之下如 AES 这样的非对称加密算法会简单很多,一些 XOR 、移位,以及在 4x4 的矩阵上做些变换,还可以通过查表来加速。

    此外,由于 AES 的广泛应用,主流 CPU ( Intel, AMD, ARM )都有相应的扩展指令集,可以将性能提升一个数量级,实际每秒能处理的数据在数百 MB 这个量级上。

    有些硬盘号称有全盘加密功能,实际上就是硬盘的主控芯片在写入前通过 AES 进行加密,在电脑启动时 BIOS 会要求输入密码。这样即使电脑丢了,或者硬盘被人拆下挂到其他机器上也不用担心数据泄露。

    关于 RSA 算法的实现细节,推荐阮一峰写的《 RSA 算法原理》

    https://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html

    == 2 ==

    另一个有趣的事情是 2017 年,当时在钱厂,对接某银行系统的时候,在通信协议的加密这块,对方给了一个 jar 包(不给源码),以及不知什么编码的公钥、私钥文件,既不是 PEM 也不是 X509,是个奇怪的二进制文件。

    然鹅,钱厂用的是 PHP,这就有点尴尬了。

    幸好这个 jar 包没有经过混淆,用安卓开发小伙伴提供的反编译工具,得到了源码,并经过一番努力重写成了 PHP 的源码。

    然后发现那个公私钥是 java object 序列化后得到的字节码。

    更有趣的还在后面。

    为了方便测试,我们按银行给的 API 写了一套 mock 系统,这样就可以在不依赖银行在内部完成全流程自测,大幅提高了开发效率。

    在部署 mock 系统的时候,没想太多,就用银行提供的这对公私钥,然后竟然调通了

    也就是说,银行给的公钥和私钥文件竟然是一对,把他们的私钥直接给我们了……

    我猜,应该是银行的安全审计部门在项目需求中要求用非对称加密,但是又没有对最终代码进行审查吧

    顺便一提,正式上线时,对方给的 API url 是 https 的,但是 url 中的域名是 IP,钱厂在代码中只能把 CURLOPT_SSL_VERIFYHOST 设为 false 。

    过了一段时间,他们决定用个正式的域名,才给安上了 https 证书。

    又过了一段时间,他们的证书过期了。

    并且在故障期间不允许我们忽略证书进行访问。

    == 3 ==

    故事 2 里提到,把那段 java 代码“经过一番努力”重写成了 PHP,其实中间还是遇到了个不大不小的麻烦。

    Java 代码里用了一个叫 bouncycastle 的库来进行 RSA 的加解密,而我用 PHP 的 openssl_private_encrypt 加密的文本,并不能被他们提供的 java 代码正常解密。

    经过多次尝试,我发现了一个现象:对同一个消息,java 代码加密生成的密文,每次都一样,而 PHP 生成的密文,则总是在变化。

    作为一个信息安全专业的毕业生,我竟然不知道这是为什么,真是愧对国家愧对党,只好默默点开桌面上的小飞机,在一些不存在的网站上摸索。

    根据这个现象,我在 stackoverflow 找到了 "data encrypted with openssl_public_encrypt is different every time?" 这个问题,答案中给了个线索:

    The PKCS#1 encryption algorithm uses some random seed to make the cipher-text different every time. This protects the cipher-text against several attacks, like frequency analysis, ciphertext matching.

    FROM stackoverflow.com/questions/36627518

    经过进一步的搜索,终于找到了如何在 PHP 中解决这个问题。

    具体解决办法后面说,这里先介绍一些背景知识。

    RSA 加密的基本流程是:将一个和密钥长度相同的输入(明文),通过一系列运算(加密),得到一个和密钥长度相同的输出(密文)。

    以 1024 bit 的 RSA 密钥为例,每次输入 128 字节,输出 128 字节。

    对于超过 128 字节的情况,就需要将原始数据切成 128 字节的块,分别加密后再拼起来;解密时,按 128 字节拆开解密。

    但是不足 128 字节的情况,比如像密码这种短数据,或者长数据也并不总是 128 的倍数,会留下小尾巴,这就有点尴尬。

    因此我们还需要用某种方法,将不足 128 字节的数据拼( padding )到 128 字节,再进行加密;解密得到的数据,需要把 padding 的数据去掉,才能得原始数据。

    真是让人头秃。

    继续。

    对于普通文本,一个简单的做法是用 ASCII 0 进行填充。

    但这会带来 2 个问题:

    1. 如果原文中包含了 ASCII 0,就无法有效识别

    2. 对于相同的输入,总是能得到相同的密文。

    问题 2 可能招致某些类型的攻击,例如前面引用中提到的 "frequency analysis",以一个简化的场景为例,假设每个单词是单独加密的,在英文中单词 a 出现的次数最多,通过统计密文出现的频率,可以破译对应的明文。

    一个改进的方案是,使用一些随机数进行填充,这样可以保证相同明文每次加密得到不同的密文。

    基于这个思路,RFC 2313 制定了 RSA 的加密标准 PKCS #1: RSA Encryption Version 1.5,通过在 128 字节中的前 11 个字节里加入一些随机数,保证每次加密得到的密文不同。

    回到最初的问题,通过查看 PHP 的 openssl_public_encrypt 文档,可以发现它有一个 $padding 参数,默认值是 OPENSSL_PKCS1_PADDING 。

    而银行给的 Java 代码是 Cipher.getInstance("RSA", new BouncyCastleProvider()); 按照官方文档的说明,这里的 RSA 等于 "RSA/NONE/NoPadding"。

    最后,通过在 PHP 代码中给数据手动填充前导 ASCII 0,并指定 OPENSSL_NO_PADDING,终于和 Java 代码兼容了。

    问题圆满解决。

    等等……

    银行用的是 NoPadding ?

    == THE END ==

    其实关于 RSA 还有一些其他有趣的事情,这次就先写到这里,下次(如果我还记得的话),可以聊聊 RSA 和币圈的一点小八卦。

    按照前几篇的套路,文末还是要贴一下招聘广告:

    我在网盟广告业务线(穿山甲),由于业务持续高速发展,长期缺人、不限 HC 。关于字节跳动面试的详情,可参考我之前写的《程序员面试指北:面试官视角》

    https://mp.weixin.qq .com/s/Byvu-w7kyby-L7FBCE24Uw ~ 投递链接 ~

    后端开发(上海) https://job.toutiao.com/s/sBAvKe

    后端开发(北京) https://job.toutiao.com/s/sBMyxk

    广告策略研发(上海) https://job.toutiao.com/s/sBDMAK

    其他地区、职能线 https://job.toutiao.com/s/sB9Jqk

    49 条回复    2020-03-24 21:05:18 +08:00
    wildlynx
        1
    wildlynx  
       2020-03-22 16:05:17 +08:00 via iPhone
    除了 RSA,还有 ECC 椭圆曲线加密。
    neilp
        2
    neilp  
       2020-03-22 16:14:53 +08:00 via iPhone
    握手最开始是 dh 或者 ecdh, 然后才是 rsa 或者 ecc 。 最后才是 aes
    felix021
        3
    felix021  
    OP
       2020-03-22 16:32:02 +08:00
    @wildlynx 对,币圈基本都用 ECDSA 来做签名,这篇写累了就没有继续讲……
    felix021
        4
    felix021  
    OP
       2020-03-22 16:33:50 +08:00
    @neilp 用 RSA 握手还是 DH 握手取决于实现吧,都可以的,不是非得用 DH
    marcoxuu
        5
    marcoxuu  
       2020-03-22 16:44:24 +08:00   ❤️ 1
    同信安毕业,感觉问题一老师讲过很多遍。握手过程用 wireshark 抓包可以看到完整的算法选择协商过程。RSA 和 ecdsa 的签名因为两者的 Sign 和 Verify 算法消耗差距也适用于不同的场景 :P
    wdlth
        6
    wdlth  
       2020-03-22 16:52:56 +08:00   ❤️ 1
    应该说说密钥交换
    mywaiting
        7
    mywaiting  
       2020-03-22 16:55:01 +08:00
    对接 rsa 都这么多趣事了,对接 sm2 岂不是..........
    iamundefined
        8
    iamundefined  
       2020-03-22 17:07:07 +08:00
    @mywaiting 手动狗头,你调皮 hhh
    azenk
        9
    azenk  
       2020-03-22 18:05:25 +08:00
    你这个还好吧,有很多开源库可以研究,比如 openssl,embed-tls 等等,我做嵌入式的,48MHz 主频的处理器,用 RSA 算法必须得用内置的硬件 RSA 计算单元,那才让人头秃呢
    stevenhawking
        10
    stevenhawking  
       2020-03-22 18:12:01 +08:00
    讲完了故事, 有不忘安利招聘, 给楼主点赞
    wuwentao1998
        11
    wuwentao1998  
       2020-03-22 18:18:17 +08:00
    问个问题:既然第一阶段通过 DH 算法生成了对称秘钥,整个通信过程已经加密了,为什么使用密码登录的时候还需要用服务器的公钥对密码加密?
    msg7086
        12
    msg7086  
       2020-03-22 18:40:22 +08:00
    @wuwentao1998
    三步:
    DH 保证初始安全信道;
    公私钥和证书和 CA 保证对方身份可信;
    最后对称加密保证数据传输安全信道。
    ThirdFlame
        13
    ThirdFlame  
       2020-03-22 19:25:55 +08:00
    忽略证书,并不是不用证书,而是不验证证书的有效性吧。
    那银行怎么知道你没有验证证书的有效性呢?
    yulihao
        14
    yulihao  
       2020-03-22 19:26:56 +08:00
    我只能说我爱 ecc❀
    aneureka
        15
    aneureka  
       2020-03-22 20:42:23 +08:00 via iPhone
    关于 https 握手的细节楼主讲的大概过程我都还清楚(因为最近春招实习哈哈哈),不过还是很有趣,等有天来抓包详细看下过程;期待楼主的下一个故事
    justin2018
        16
    justin2018  
       2020-03-22 20:50:54 +08:00
    😅 看成 RNA 了 😅
    felix021
        17
    felix021  
    OP
       2020-03-22 20:58:48 +08:00
    @ThirdFlame 确实不知道,就是个沟通问题,不让用就不让用吧。。。毕竟确实安全点儿
    felix021
        18
    felix021  
    OP
       2020-03-22 20:59:21 +08:00
    @azenk 壮士加油
    felix021
        19
    felix021  
    OP
       2020-03-22 21:00:06 +08:00
    @wdlth 这个讲起来比较枯燥,没遇到趣事。。
    felix021
        20
    felix021  
    OP
       2020-03-22 21:00:33 +08:00
    @mywaiting 我也觉得 sm 更有趣
    shuianfendi6
        21
    shuianfendi6  
       2020-03-22 21:11:58 +08:00
    @mywaiting sm9 密钥交换.....
    zts1993
        22
    zts1993  
       2020-03-22 23:22:13 +08:00
    @mywaiting 那是简直是一场灾难。。。
    shawnsh
        23
    shawnsh  
       2020-03-22 23:31:09 +08:00 via Android
    原文加点 salt,增加破解难度?
    whoami9894
        24
    whoami9894  
       2020-03-22 23:33:59 +08:00 via Android
    公钥密码最佳实践:别用 RSA
    lostpg
        25
    lostpg  
       2020-03-22 23:58:07 +08:00 via Android
    现在打开看浏览器证书,新的加密套件用的是 x25519 了,私钥公钥都好短啊
    felix021
        26
    felix021  
    OP
       2020-03-23 00:49:49 +08:00
    @lostpg 对,椭圆曲线的密钥短了很多,计算量也比 RSA 要小很多
    felix021
        27
    felix021  
    OP
       2020-03-23 00:50:30 +08:00
    @whoami9894 用来学习比较好,普通人能看懂,代码写起来也简单,实用性上确实和 ECC 不能比了
    hacher
        28
    hacher  
       2020-03-23 01:30:27 +08:00
    @msg7086 请问为什么一开始要用 DH 生成 session key? 客户端可以直接用 RSA 公钥加密 session key,发给服务器私钥解密.这样两端也有相同 session key 进行加密通信了
    snnn
        29
    snnn  
       2020-03-23 01:32:34 +08:00 via Android
    AES 是基于 block 的,block 怎么做 padding 有专门的规范。
    jinliming2
        30
    jinliming2  
       2020-03-23 08:50:17 +08:00 via iPhone   ❤️ 2
    > 顺便一提,正式上线时,对方给的 API url 是 https 的,但是 url 中的域名是 IP,钱厂在代码中只能把 CURLOPT_SSL_VERIFYHOST 设为 false 。

    这一段描述的不太准确吧?需要忽略证书原因应该是自签名或是证书与访问域名 / IP 不匹配,而不是 IP 访问的缘故……
    因为 IP 也是可以颁发证书的,比如 https://1.1.1.1
    est
        31
    est  
       2020-03-23 10:13:51 +08:00
    其实还有个客户端证书。
    farseeraliens
        32
    farseeraliens  
       2020-03-23 12:14:29 +08:00 via iPhone
    头一次知道 byte dance 叫钱厂……
    felix021
        33
    felix021  
    OP
       2020-03-23 12:24:59 +08:00
    @farseeraliens 不是,那是我的前东家……
    lostpg
        34
    lostpg  
       2020-03-23 12:33:22 +08:00 via Android
    @felix021 但安全性也依然很好,而且背景非常干净没有 NIST 的痕迹🤣
    felix021
        35
    felix021  
    OP
       2020-03-23 12:38:35 +08:00
    @jinliming2 对,但是他们没有提供针对 IP 颁发的证书,所以只能是先忽略
    0x5e
        36
    0x5e  
       2020-03-23 12:47:55 +08:00
    「在 PHP 代码中给数据手动填充前导 ASCII 0 」,是说把 ASCII 0 填充在前面吗?最近要用一个老的后端接口,也是 no padding 的,用 oc 和 java 都可以调通,但是用 pycrypto 或者 openssl 命令行就不行,原文尾部填充 ASCII 0 也不行
    felix021
        37
    felix021  
    OP
       2020-03-23 14:09:56 +08:00
    @0x5e 对,填充在前面,更多细节你参考我提到的 RFC 2313
    0x5e
        38
    0x5e  
       2020-03-23 18:06:48 +08:00 via iPhone
    @felix021 感谢,还是不行,我再试试
    warcraft1236
        39
    warcraft1236  
       2020-03-23 18:18:01 +08:00
    我之前做自动化的时候也是,后端用的 Java groovy,我们用 Python,对于 aes 加密的数据,也是学习了一下 padding 这个事情然后才成功的用 Python 实现了加解密
    msg7086
        40
    msg7086  
       2020-03-23 18:28:23 +08:00
    @hacher 初始信道不安全。
    RSA 公钥哪来的,不是服务器给的么,那中间人把公钥换掉就行了。
    lechain
        41
    lechain  
       2020-03-23 19:04:45 +08:00 via Android   ❤️ 1
    故事不错,不过我还是得挑一个错误 🐶
    >> 以 RSA 为例,在加解密的时候,需要对大整数(典型值是 2048bit,256 字节)做大量乘法、取模等运算;相比之下如 AES 这样的非对称加密算法会简单很多,一些 XOR 、移位,以及在 4x4 的矩阵上做些变换,还可以通过查表来加速。

    道理我都懂,但是为什么 AES 是非对称加密,我如果没有记错,RSA 才是非对称加密吧?🐶
    felix021
        42
    felix021  
    OP
       2020-03-23 20:24:14 +08:00
    @lechain 我打错了,你赢了,奖励一个感谢
    felix021
        43
    felix021  
    OP
       2020-03-23 20:24:59 +08:00
    @msg7086 换掉的话服务器就无法解密,通信就无法建立,不影响通信安全。
    felix021
        44
    felix021  
    OP
       2020-03-23 20:29:55 +08:00
    @0x5e 之前 php 的代码供参考: https://www.felix021.com/blog/read.php?2169
    CRVV
        45
    CRVV  
       2020-03-23 23:42:29 +08:00
    > 大多数候选人知道非对称加密,了解客户端会用 RSA 公钥进行加密。
    > 那么,服务器在返回响应报文之前,会用什么来进行加密呢?

    1. "服务器在返回响应报文之前,会用什么来进行加密呢"
    这句话有歧义,另一种理解方式是,服务器在返回之后,客户端要用什么来加密。
    因为你的前一句话说客户端要用 RSA 公钥,那么必须让服务器先给一个公钥,那么很自然的问题就是在拿到公钥之前要怎么做。

    2. 按照你想表达的含义,这个问题是要怎么加密服务器发送的报文。
    你给的答案居然是用 AES,所以客户端用 RSA 加密,服务器用 AES 加密,你自己不觉得这里有问题么?为啥两边要用不同的算法?

    3. 既然你要用这个来当面试题,至少把 wikipedia 的 TLS 浏览一下吧。
    felix021
        46
    felix021  
    OP
       2020-03-23 23:49:15 +08:00
    @CRVV

    1. 有没有歧义是在面试中可以沟通的问题。大部分候选人并不认为有歧义,因为他们认为客户端全程都在用 RSA 加密,这个问法无非是确认下他们是否这么想。

    2. 我给的答案不是 AES,我只是以 AES 为例对比对称加密和非对称加密。

    3. 你怎么知道我没看呢?以及为什么非要看 wikipedia 呢?再者,我面试中也会用我不熟悉的问题和候选人探讨,并不都是“碾压式”的提问。
    balamiao
        47
    balamiao  
       2020-03-24 11:20:02 +08:00
    CRVV
        48
    CRVV  
       2020-03-24 17:53:04 +08:00
    @felix021

    客户端会用服务端的非对称加密算法的公钥进行加密。服务器要用什么来进行加密?

    如果这里是说 handshake 阶段做 RSA Key Exchange,服务器发送不加密的公钥,客户端要用服务器的公钥加密 premaster key 再发过去。
    如果这里说的不是 handshake 阶段,那么双方都用对称加密算法,对称算法的密钥是从 handshake 来的,和非对称算法根本没关系。

    你的问题在 TLS 的框架下面本身就不成立。
    看一下 wikipedia 只是为了知道相关的常识,不要问出来这种奇怪的问题。
    felix021
        49
    felix021  
    OP
       2020-03-24 21:05:18 +08:00
    @CRVV 感谢你提供的常识。这种奇怪的问题能够识别出不知道这个问题奇怪的候选人,我在面试中用不用不劳费心。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3114 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 13:46 · PVG 21:46 · LAX 05:46 · JFK 08:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.