RSA算法由Ron Rivest、Adi Shamir和Leonard Adleman于1977年提出,算法名字由三人名字开头字母组成。该算法是当前公钥密码体系中的算法基础。RSA算法的安全性依赖于“大整数因字分解问题是NP-hard的问题”这一假设。本文简单介绍RSA算法的数学基础,以及相关的一些算法知识。
RSA算法可以用于公钥加密和数字签名两种场景中。在RSA算法中,需要使用如下方式产生所需的参数
加密过程如下(Bob将消息M加密传给Alice,Alice解密)
RSA算法机制保证了 m ′ = c d m o d N = m e ∗ d m o d N = m m'=c^d \mod N=m^{e*d}\mod N = m m′=cdmodN=me∗dmodN=m,因此Alice得到的消息 M ′ M' M′和原始消息M相同。
签名过程如下(Alice对消息M计算签名S,将(M,S)发送给Bob,Bob对签名做校验)
简单讲,大数分解的难度依赖于数的大小(最朴素的试除算法复杂度为 Θ ( N ) \Theta(\sqrt{N}) Θ(N))。为保证RSA算法的安全性,算法中涉及到的数通常较大。编程语言中将数表示为二进制01序列表示,常见的int性整数多为32bit,以RSA-2048为例,公钥N的位宽为2048bit,因此在工程实现中首先需要解决大数如何表示、如何计算的问题。
工程实现中对大整数表示(有称为任意长度整数)计算有很多很好的库,具体可以参考Wiki中词条List of arbitrary-precision arithmetic software,如GNU MP库等。本文中使用python直接来进行大整数计算,后续算法均以RSA-2048位宽为例进行。
通常产生的两个素数bit长度相近,对于RSA-2048,需要产生长度为1024bit的两个素数p和q。另外为防止攻击者破解,要求产生的素数要“随机”。素数产生的一般性步骤如下
第一步中的奇数产生可以是朴素的递增(next a = a + 2),也可以使用特殊策略提升素性检测通过的几率。第二步中的素性检测是素数产生算法的灵魂,可以是确定性的素性检测(也称为素性证明,生成真素数),也可以是概率性的素性检测(概率性的证明,生成概率素数)。关于素数性质的研究一直是数论中的热点,如著名的黎曼猜想。真素性的证明往往代价高昂,在工程实践中更多的使用概率性证明。
素性证明其实称为合性证明更为合适,因为合性是可以严格证明的。概率素性证明中的概率,指的是连续t次合性证明时,t次证明结果的置信度,或者说“t次合性证明都失败”这一事件的补概率。
通常使用如下框架进行:对于候选奇数n,定义如下集合W(n),满足以下性质
1.对任意正整数 a < n a < n a<n, 可以在多项式时间内判断a是否属于W(n)
2.如果n为素数,则W(n)是空集
3.如果n为合数,则W(n)中元素个数满足 $ |W(n)| \geq n/2$
从合性证明的角度看,如果n为合数,则W(n)中的元素是n合性的证据,而集合 Z n − W ( n ) Z_n-W(n) Zn−W(n)中的元素称为说谎者。对于固定的n,若选取的a满足 a ∈ W ( n ) a\in W(n) a∈W(n),称n未通过基于a的素性检测。使用t个不同的a进行判定,若n均通过对应的素性检测,则n为素数的置信度 ≥ 1 − ( 1 / 2 ) t \geq 1-(1/2)^t ≥1−(1/2)t。
著名的Miller-Rabin素性测试依赖于素数的如下两个性质: