最近在看范学雷老师的课《深入剖析Java新特性》,里面提到了一些过时算法,看完之后,决定分享一下。
问题:量子计算时代的非对称加密还香吗?
目前来看,答案是否定的。这是由非对称加密的特性决定的,因为大质数分解的计算复杂度不会随着位数的增加而呈指数增长。
如果对非对称加密算法的基本逻辑不太理解,可以看看王建硕老师写的这篇文章《有人没看懂?再次尝试讲解 RSA 加密算法》。
但另一头,量子计算机的计算能力与目前最快的传统计算机完全不在一个数量级上:
2020 年 12 月 4 日,中国量子计算原型机“九章”问世。理论上,这个计算机比目前最快的计算机还要快一百万亿倍。也比 2019 年谷歌发布的量子计算原型机“悬铃木”快一百亿倍。
所以,现在流行的非对称密码算法,都不能抵御量子计算时代的算力。那对称加密呢?
现在破解 128 位安全强度的密钥需要 25000 亿年,换成量子计算机就是:0.025 年,即 9 天。所以,欧洲的 ECRYPT-CSA 建议:
128 位的安全强度的密码学算法可用于 2028 年之前,2028 年之后就要使用 256 位的安全强度了。
也就是说,在量子计算时代,256 位安全强度的对称加密算法和单向散列函数,包括 AES 算法,还是足够安全的。
但我们也知道,对称加密有一个问题搞不定,就是如何分发密钥。所以,目前的主流方案是:使用非对称加密算法传输对称密钥,再用对称密钥来传输数据。为啥不都用非对称加密算法?因为在传统计算机上,非对称加密算法的计算很慢。
但在量子计算时代,非对称加密算法已经不可靠了,那基于非对称加密算法推导出来的对称密钥还安全吗?这就需要用到前向保密性。
在密码学里,前向保密性指的是即使用来协商或者保护数据加密密钥的长期秘密泄漏,也不会导致数据加密密钥的泄漏。换个角度看,虽然数据加密密钥是由长期的秘密衍生出来的或者保护的,但是数据加密密钥不能再一次通过长期秘密推导出来。
举个例子来说就是,即使非对称加密算法的密钥泄露,也不会导致对称加密算法的密钥泄露。
为此,具备前向保密性的对称密钥具有两个特点:
1、密钥的产生需要有即用即弃的随机数参与;
2、密钥本身是即用即弃的密钥,而不能是静态的密钥,也就是不会持久化保存。
让我们回到现实,回到 Java 领域,作为开发人员(尤其是安全相关的),需要关注啥?其中一个就是 JDK 密码路线图,了解哪些密钥算法已经过期,哪些已经很危险,以便及时作出调整。
根据范老师在课程中的总结,笔者将其摘录到这里:
1)应该抛弃的算法(jdk 支持,但不能使用)
MD2, MD5, SHA-1, DES, 3DES, RC4, SSL 3.0, TLS 1.0, TLS 1.1
密钥小于 1024 位的 RSA 算法,密钥小于 1024 位的 DSA 算法,密钥小于 1024 位的 Diffie-Hellman 算法,密钥小于 256 位的 EC 算法
2)应该退役的算法(jdk 支持,可以使用,但应该尽早替换)
密钥大于 1024 位小于 2048 位的 RSA 算法,密钥大于 1024 位小于 2048 位的 DSA 算法,密钥大于 1024 位小于 2048 位的 Diffie-Hellman 算法
RSA 签名算法,基于 RSA 的密钥交换算法,128 位的 AES 算法
3)推荐使用的算法
256 位的 AES 算法,SHA-256、SHA-512 单向散列函数
RSASSA-PSS 签名算法,X25519/X448 密钥交换算法,EdDSA 签名算法
然后呢?升级 jdk,拥抱 jdk 17 (至少 jdk 11):
给你一个保守、粗暴的估计,你如果从 JDK 8 迁移到 JDK 17,并且能够恰当使用 JDK 8 以后的新特性的话,产品的代码量可以减少 20%,代码错误可以减少 20%,产品性能可以提高 20%,维护成本可以降低 20%。这些,都是实实在在的收益。
比如:
public class StreamTest {
@Test
public void stream_list_convert_simple() {
List<Integer> intList = List.of(1, 3, 6, 180, 333);
List<Integer> expectedList = List.of(2, 6, 12, 360, 666);
// jdk-8
List<Integer> outList1 = intList.stream().map(i -> i * 2).collect(Collectors.toList());
Assertions.assertEquals(expectedList, outList1);
Assertions.assertDoesNotThrow(() -> outList1.add(9999));
// jdk-11
List<Integer> outList2 = intList.stream().map(i -> i * 2).collect(Collectors.toUnmodifiableList());
Assertions.assertEquals(expectedList, outList2);
Assertions.assertThrows(UnsupportedOperationException.class, () -> outList2.add(9999));
// jdk-17
List<Integer> outList3 = intList.stream().map(i -> i * 2).toList();
Assertions.assertEquals(expectedList, outList3);
Assertions.assertThrows(UnsupportedOperationException.class, () -> outList3.add(9999));
}
}
延伸阅读: