7 - RSA 算法

RSA 算法

我的博客

原书:《Understanding Cryptography: A Text book for Students and Practitioners》

在 Whitfield Diffie 与 Martin Hellman 于 1976 年发表关于公钥加密的论文之后,掀起了公钥加密研究热潮。在 1977 年,Ronald Rivest,Adi Shamir 以及 Leonard Adleman 提出了一种被广泛使用的非对称加密策略,并以他们三个人的名字缩写名命为 RSA。

7.1 介绍

RSA 具有很多应用,实际上最常用的功能是:

  • 加密小批量数据,比如进行密钥传输
  • 数字签名,这将在第十章介绍,做为网络上的数字证书

需要注意的是,RSA 加密并不能够完全替代对称加密,因为相较于对称加密算法,RSA 要慢上数倍。这是因为 RSA 中涉及很多计算。因此它主要用在交换对称运算的密钥上。实际上,RSA 通常与对称加密运算如 AES 一起使用,在这个组合中,实际进行块数据加密的是 AES 算法。

RSA 所蕴含的单向函数是,对整数的因数分解问题:对两个大的质数做乘法是十分简单的,但是对这两个质数的积进行因数分解则十分困难。欧拉定理以及欧拉 phi 函数在 RSA 算法中扮演着重要的角色。

7.2 加密与解密

RSA 加密与解密,是在整数环 \(Z_n\) 中完成的,模计算是它的核心。RSA 加密原文 \(x\),这个原文是 \(Z_n = {0,1,...,n-1}\) 中的元素。因此原文 \(x\) 的二进制值必须小于 \(n\)。密文同样符合这一条件。使用公钥加密及使用私钥解密可以表示为:

RSA 加密 给定公钥 \((n,e) = k_{pub}\) 及原文 \(x\),加密函数表示为:

\[y = e_{k_{pub}}(x) \equiv x^e \quad \mod \quad n \]

其中 \(x,y \in Z_n\)

RSA 解密 给定私钥 \(d = k_{pr}\) 及密文 \(y\),解密函数可表示为:

\[x = d_{k_{pr}}(y) \equiv y^d \quad \mod \quad n \]

其中 \(x,y \in Z_n\)

实际上,\(x,y,n,d\) 都是十分长的数字,通常有 1024 位长,甚至更长。\(e\) 为加密指数或公钥指数,私钥 \(d\) 为解密指数或私钥指数。如果发送者希望发送一条加密信息给接收者,发送者需要使用接收者发送给他的公钥 \((n,e)\) 加密,接收者则使用他的私钥 \(d\) 进行解密。

即便现在还不知道算法细节,我们已经可以提出 RSA 算法需要满足下面的需求:

  1. 因为攻击者很容易获得公钥,那么从公钥 \((n,e)\) 计算出私钥的过程应该是极其困难的
  2. 取决于可用的原文空间与密文空间,加密的原文长度是受限的
  3. 进行加密与解密的计算过程要相对容易实现,这意味着我们需要一种方法来快速计算大数值指数运算
  4. 给定 \(n\),应该有许多公私钥对,否则攻击者可以很轻松进行暴力破解

7.3 密钥生成与正确性证明

RSA 密钥生成

输出:公钥 \(k_{pub} = (n,e)\),私钥 \(k_{pr} = (d)\)

  1. 选择两个大的质数 \(p\)\(q\)
  2. 计算 \(n = p\cdot q\)
  3. 计算 \(\Phi (n) = (p-1)(q-1)\)
  4. 选择公钥指数 \(e \in \{1,2,...,\Phi(n) - 1\}\) 从而有 \(gcd(e,\Phi(n)) = 1\)
  5. 计算密钥 \(d\),从而有 \(d\cdot e \equiv 1 \quad mod \quad \Phi(n)\)

\(gcd(e,\Phi(n)) = 1\) 条件能够确保 \(e\) 的逆模 \(\Phi(n)\) 存在,保证总有私钥 \(d\) 存在。

密钥 \(d\)\(e\) 可以使用扩展欧几里得算法 EEA 实现。在实践中,通常首先在 \(0 < e < \Phi(n)\) 范围内选择一个公钥参数 \(e\)。我们使用 EEA 算法,并使用输入参数 \(n\)\(e\),获得下面的关系:

\[gcd(\Phi(n),e) = s\cdot \Phi(n) + t\cdot e \]

如果 \(gcd(e,\Phi(n)) = 1\),我们知道 \(e\) 是一个有效公钥。我们也知道,由扩展欧几里得算法计算的参数 \(t\)\(e\) 的逆,因此:

\[d = t \quad mod \quad \Phi(n) \]

如果 \(e\) 以及 \(\Phi(n)\) 不是互质关系,我们可以简单选择一个新的 \(e\) 值,并重复上面的过程。注意到 EEA 的系数 \(s\) 在 RSA 算法中是不需要的,因此无需计算。

例 7.1 发送者希望发送接密文信息给接收者,接收者首先通过上面的步骤计算他的 RSA 参数,然后,接收者将公钥发送给发送者,发送者使用公钥加密信息 \(x = 4\),并发送密文 \(y\) 给接收者,接收者使用他的私钥解密 \(y\)

接收者计算公私钥对

  1. 选择 \(p = 3,q = 11\)
  2. \(n = p\cdot q = 33\)
  3. \(\Phi(n) = (3-1)(11-1)\) = 20
  4. 选择 \(e = 3\)
  5. \(d \equiv e^{-1} \equiv 7 \quad mod \quad 20\)

计算得到 \(k_{pub} = (33,3)\),私钥 \(d = 7\)

发送者使用公钥加密

加密,\(y = x^e \equiv 4^3 \equiv 31 \quad mod \quad 33\),得到密文 \(y = 31\)

接收者使用私钥解密

解密,\(y^d = 31 ^7 \equiv 4 = x \quad mod \quad 33\),得到原文 \(x = 4\)

注意到,私钥与公钥满足条件 \(e\cdot d = 3\cdot 7 \equiv 1 \quad mod \quad \Phi(n)\)

在实际应用中,RSA 的参数是十分大的,模 \(n\) 通常至少有 1024 位长,导致 \(p\)\(q\) 位长可达 512。

\[p = E0DFD2C2A288ACEBC705EFAB30E4447541A8C5A47A37185C5A9CB98389CE4DE19199AA3069B404FD98C801568CB9170EB712BF10B4955CE9C9DC8CE6855C6123h \]

\[q = EBE0FCF21866FD9A9F0D72F7994875A8D92E67AEE4B515136B2A778A8048B149828AEA30BD0BA34B977982A3D42168F594CA99F3981DDABFAB2369F229640115h \]

\[n = CF33188211FDF6052BDBB1A37235E0ABB5978A45C71FD381A91AD12FC76DA0544C47568AC83D855D47CA8D8A779579AB72E635D0B0AAAC22D28341E998E90F82122A2C06090F43A37E0203C2B72E401FD06890EC8EAD4F07E686E906F01B2468AE7B30CBD670255C1FEDE1A2762CF4392C0759499CC0ABECFF008728D9A11ADFh \]

\[e = 40B028E1E4CCF07537643101FF72444A0BE1D7682F1EDB553E3AB4F6DD8293CA1945DB12D796AE9244D60565C2EB692A89B8881D58D278562ED60066DD8211E67315CF89857167206120405B08B54D10D4EC4ED4253C75FA74098FE3F7FB751FF5121353C554391E114C85B56A9725E9BD5685D6C9C7EED8EE442366353DC39h \]

\[d = C21A93EE751A8D4FBFD77285D79D6768C58EBF283743D2889A395F266C78F4A28E86F545960C2CE01EB8AD5246905163B28D0B8BAABB959CC03F4EC499186168AE9ED6D88058898907E61C7CCCC584D65D801CFE32DFC983707F87F5AA6AE4B9E77B9CE630E2C0DF05841B5E4984D059A35D7270D500514891F7B77B804BED81h \]

有趣的是,信息 \(x\) 在加密时会进行 \(e\) 阶指数运算,而解密时又会进行 \(d\) 阶指数运算,而得到的结果能回到原文 \(x\)。这个过程可以表示为:

\[d_{k_{pr}}(y) = d_{k_{pr}}(e_{k_{pub}}(x)) \equiv (x^e)^d \equiv x^{de} \equiv x \quad mod \quad n \]

这是 RSA 的关键。现在我们需要证明一下为什么 RSA 能够工作。

证明. 我们需要证明解密加密的逆,\(d_{k_{pr}}(e_{k_{pub}}(x)) = x\)。在我们构建公私钥对时,遵从了 \(d\cdot e \equiv 1 \quad mod \quad \Phi(x)\),通过模操作的定义,这个等式等效于:

\[d\cdot e = 1 + t\cdot \Phi(n) \]

\(t\) 为整数,因此有:

\[d_{k_{pr}}(y) \equiv x^{de} \equiv x^{1+t\cdot \Phi(n)} \equiv x^{t\cdot \Phi(n)}\cdot x^1 \equiv (x^{\Phi(n)})^t \cdot x \quad mod \quad n \]

使用欧拉定理,如果 \(gcd(x,n) = 1\) 那么 \(1 \equiv x^{\Phi(n)} \quad mod \quad n\),马上有:

\[1 \equiv 1^t \equiv (x^{\Phi(n)})^t \quad mod \quad n \]

我们划分成两个情况:

  1. \(gcd(x,n) = 1\)

    这一条件下,满足欧拉定理,因此有:

    \[d_{k_{pr}}(y) \equiv (x^{\Phi(n)})^t \cdot x \equiv 1\cdot x \equiv x \quad mod \quad n \]

    这里证明了,只要原文 \(x\)\(n\) 互质,那么解密函数就是加密函数的逆。

  2. \(gcd(x,n) = gcd(x,p\cdot q) \neq 1\)

    因为 \(p\)\(q\) 互质,那么 \(x\) 必然以它们二者之一做为因子:

    \[x = r\cdot p \\ x = s\cdot q \]

    其中 \(r,s\)\(r <q,s<p\),不失一般性,我们可以设 \(x = r\cdot p\),那么有 \(gcd(x,q) = 1\),欧拉定理满足:

    \[1 \equiv 1^t \equiv (x^{\Phi(q)})^t \quad mod \quad q \]

    其中 \(t\) 为正整数,有:

    \[(x^{\Phi(n)})^t \equiv (x^{(q-1)(p-1)})^t \equiv ((x^{\Phi(q)})^t)^{p-1} \equiv 1^{p-1} = 1 \quad mod \quad q \]

    使用模操作的定义,这个等效于:

    \[(x^{\Phi(n)})^t = 1 + u\cdot q \]

    其中 \(u\) 是整数,那么有:

    \[x\cdot(x^{\Phi(n)})^t = x + x \cdot u\cdot q \\ = x + (r\cdot p)\cdot u \cdot q \\ = x + r\cdot u \cdot (p\cdot q) \\ = x + r\cdot u \cdot n \\ x\cdot (x^{\Phi(n)})^t \equiv x \quad mod \quad n \]

    可以得到:

    \[d_{k_{pr}} = (x^{\Phi(n)})^t \cdot x \equiv x \quad mod \quad n \]

证明完毕。

7.4 加密与解密:快速取幂

公钥算法都是基于超大的数值,如果我们不注意算法实现,那么缓慢地计算速度将会令应用变得不切实际。如果我们查看 RSA 算法的加密与解密运算,我们可以看到,它们都是基于指数模运算,可以表示为:

\[y = e_{k_{pub}}(x) \equiv x^e \quad mod \quad n \quad (加密) \]

\[x = d_{k_{pr}}(y) \equiv y^d \quad mod \quad n \quad (解密) \]

指数运算直观表示为:

\[x\rightarrow x^2 \rightarrow x^3 \rightarrow x^4 \rightarrow x^5 ... \]

RSA 算法中的指数 \(e\)\(d\) 都是非常大的数值,通常是 1024 - 3072 位长,甚至更长(公钥 \(e\) 有时选择比较小的数值,但是私钥 \(d\) 肯定是非常长的)。用上面直观表示的指数运算,需要至少 \(2^{1024}\) 次乘法。可观测宇宙的原子数量是 \(10^{300}\),为了一次加密会话计算 \(2^{1024}\) 次乘法,那显然是疯狂的。那么,是否有快速计算指数的方法呢?答案当然是肯定的,否则我们只能抛弃 RSA 算法了。其中一个方法是重复平方乘算法。在展示真正的算法之前,我们先用几个简单的例子阐述这个算法。

例 7.2 让我们看一下计算 \(x^8\),使用连乘方法有:

\[x\rightarrow x^2 \rightarrow x^3 \rightarrow x^4 \rightarrow x^5 \rightarrow x^6 \rightarrow x^7\rightarrow x^8 \]

不过我们可以更快速实现结果:

\[x \rightarrow x^2 \rightarrow x^4 \rightarrow x^8 \]

这个方法是快速的,但是限制在指数为 2 的指数,既 \(e\)\(d\)\(2^i\) 形式。

例 7.3 这次我们取一般指数 26,我们希望计算 \(x^{26}\),可以使用下面的方法快速计算:

\[x \rightarrow x^2 \rightarrow x^3 \rightarrow x^6 \rightarrow x^{12} \rightarrow x^{13} \rightarrow x^{26} \]

查看上面的例子,我们可以通过执行两个基础的操作实现最终的结果:

  1. 计算当前结果的平方
  2. 当前的结果与基元素 \(x\) 相乘

在上面的例子中,我们执行的顺序是,平方,乘法,平方,平方,乘法,平方,然而在其他的指数计算中,我们不知道具体的平方与乘法的序列。在重复平方乘算法中,提供了一种系统性的方法来查找平方与乘法的序列。简而言之,算法工作如下:

算法扫描最高位到最低位。在每次迭代中,对于每一个指数位,都对当前结果做平方,当且仅当当前扫描的指数位为 1,会在平方后面跟一个乘法。

例7.4 再次考虑 \(x^{26}\),对于重复平方乘法:

\[x^26 = x^{11010_2} = x^{(h_4h_3h_2h_1h_0)_2} \]

算法扫描指数的位,从最高位(最左侧)的 \(h_4\) 开始,到最低位(最右侧) \(h_0\) 结束:

  1. \(x = x^1\),初始化,\(h_4 = 1\)

  2. \(h_3 = 1\),先平方再乘法

    a. \((x^1)^2 = x^2 = x^{10_2}\)

    b. \((x^2)\cdot x = x^3 = x^{10_2}x^{1_2} = x^{11_2}\)

  3. \(h_2 = 0\),只做平方

    a. \((x^3)^2 = x^6 = (x^{11_2})^2 = x^{110_2}\)

  4. \(h_1 = 1\),先平方再乘法

    a. \((x^6)^2 = x^{12} = (x^{110_2})^2 = x^{1100_2}\)

    b. \(x^{12}\cdot x = x^{13} = x^{1100_2}x^{1_2} = x^{1101_2}\)

  5. \(h_0 = 0\),只做平方

    a. \((x^{13})^2 = x^{26} = (x^{1101_2})^2 = x^{11010_2}\)

下面是这个算法的伪代码:

指数模运算的重复平方乘算法

输入:

基元素 \(x\)

指数 \(H = \sum_{i=0}^th_i2^i\),其中 \(h_i \in 0,1,h_t = 1\)

输出:\(x^H \mod n\)

初始化:\(r = x\)

算法:

for i = t-1 downto 0
	r = r^2 mod n
	if h_i = 1
		r = r*x mod n
return(r)

例 7.5 对于 1024 位的指数,重复平方乘算法平均需要多少次计算呢?

直接的连乘操作需要 \(2^{1024} \approx 10^{300}\) 次乘法操作,使用重复平方乘算法平均只需要 \(1.5 \cdot 1024 = 1536\) 次平方与乘法操作。这在现在计算机上是可以实现的。

7.5 RSA 加速技术

RSA 算法使用的指数数值十分长。即便底层使用了重复平方乘算法,执行一个完全体的 RSA 1024 位指数或超过这个位数的指数运算,计算量都是比较大的。因此,需要一种加速计数,这里我们介绍两种最流行的加速技术。

7.5.1 用短公钥指数快速加密

在这个情况下,公钥 \(e\) 可以选一个比较小的数值,在实践中,三个值 \(e = 3,e = 17,e = 2^{16} + 1\) 是十分重要的,使用这些公钥的复杂性在下表列出:

公钥 \(e\) \(e\) 的二进制表示 平方与乘法操作数
\(3\) \(11_2\) 3
\(17\) \(10001_2\) 5
\(2^{16} + 1\) \(10000000000000001_2\) 17

有趣的是,即便选择这些小数值的公钥,RSA 依旧是安全的。公钥 \(d\) 依然是完全体的,凡是涉及到私钥 \(d\) 的运算,比如解密与签名生成,都是十分缓慢的。其他公钥算法,尤其是椭圆曲线算法,更加快速。

7.5.2 中国剩余定理快速解密

我们不能在不妥协 RSA 安全性的前提下,缩短私钥的长度。如果我们选择一个短的私钥 \(d\),那么攻击者可以简单暴力破解所有可能的数值。通常在实现时,\(e\) 可以选得足够短而 \(d\) 要选完全体长度。

我们的目标是高效实现 \(x^d \mod n\)。首先要注意的是,拥有私钥 \(d\) 的人,同时也会拥有质数 \(p\)\(q\)。中国剩余定理 CRT: Chinese Remainder Theorem,将长的质数模运算,分成两个短质数 \(p\)\(q\) 的指数模运算。这会涉及三个步骤。

将输入转换为 CRT 域

我们首先将 \(x\) 使用质数 \(p\)\(q\) 变为:

\[x_p \equiv x \quad mod \quad p \\ x_q \equiv x \quad mod \quad q \]

在 CRT 域中进行指数运算

我们进行下面的两个质数运算:

\[y_p = x^{d_p}_p \quad mod \quad p \\ y_q = x^{d_q}_q \quad mod \quad q \]

其中两个指数为:

\[d_p \equiv d \quad mod \quad (p-1) \\ d_q \equiv d \quad mod \quad (q-1) \]

因为两个质数 \(p\)\(q\) 在实践中是相同长度的质数,\(y_p\)\(y_q\) 约是 \(n\) 长度的一半。

转换回到问题域

最后,使用下面的方法组合上面的结果 \((y_p,y_q)\)

\[y \equiv [qc_p]y_p + [pc_q]y_q \quad mod \quad n \]

其中系数 \(c_p\) 以及 \(c_q\) 计算如下:

\[c_p \equiv q^{-1} \quad mod \quad p \\ c_q \equiv p^{-1} \quad mod \quad q \]

因为给定 RSA 实现,质数不会频繁变化,上面的系数可以提前计算。

例 7.6 令 RSA 参数如下:

\[p = 11 \\ e = 7 \\ q = 13 \\ d \equiv e^{-1} \equiv 103 \quad mod \quad 120 \\ n = p\cdot q = 143 \]

现在我们使用 CRT 算法计算密文 \(y = 15\) 的解密,\(y^d = 15 ^103 \quad mod \quad 143\)

第一步,我们计算 \(y\) 的模表示:

\[y_p \equiv 15 \equiv 4 \quad mod \quad 11 \\ y_q \equiv 15 \equiv 2 \quad mod \quad 13 \]

第二步,我们在转换域中进行质数运算:

\[d_p \equiv 103 \equiv 3 \quad mod \quad 10 \\ d_q \equiv 103 \equiv 7 \quad mod \quad 12 \]

下面是指数:

\[x_p \equiv y^{d_p}_p = 4^3 = 64 \equiv 9 \quad mod \quad 11 \\ x_q \equiv y^{d_q}_q = 2^7 = 128 \equiv 11 \quad mod \quad 13 \]

最后一步,我们要从 \((x_p,x_q)\) 中计算得到 \(x\),因此我们需要系数:

\[c_p = 13^{-1} \equiv 2^{-1} = 6 \quad mod \quad 11 \\ c_q = 11^{-1} \equiv 6 \quad mod \quad 13 \]

原文 \(x\) 可计算为:

\[x \equiv [qc_p]x_p + [pc_q]x_q \quad mod \quad n \\ x \equiv [13\cdot 6]9 + [11\cdot 6]11 \quad mod \quad 143 \\ x \equiv 702 + 726 = 1428 \equiv 141 \quad mod \quad 143 \]

我们现在建立可计算的 CRT 方法。我们重新描述 CRT 指数计算:

\[y_p = x^{d_p}_p \quad mod \quad p \\ y_q = x^{d_q}_q \quad mod \quad q \]

假设 \(n\) 具有 \(t+1\) 位,\(p\)\(q\) 约为 \(t/2\) 位长。所有在 CRT 中涉及的数值,\(x_p,x_q,d_p,d_q\),长度都被 \(p,q\) 约束,因此都约 \(t/2\) 位长。如果我们对两个指数运算使用重复平方乘算法,大约需要 \(1.5t/2\) 次平方与乘模运算,二者共计约 \(1.5t\) 次运算。

这与不适用 CRT 算法的常规质数运算的计算复杂度相同。 不过,乘法与平方的数值都只有 \(t/2\) 位,这就与没有 CRT 算法的操作不同了。

7.6 查找大质数

我们还没有讨论 RSA 密钥生成的第一步,生成质数 \(p\)\(q\)。因为 \(n = p\cdot q\),两个质数的长度需要是 \(n\) 位数的一半,比如,如果我们希望 RSA 具有 \(log_2n = 1024\) 特性,那么 \(p\)\(q\) 需要具有 512 位。生成方式是,先生成随机整数,之后再检查数值是否为质数。

为了能够实际应用,我们需要回答两个问题:

  1. 在我们查找到质数之前,我们需要测试多少个整数?
  2. 能够以多快的速度检测一个随机整数是否是质数?

7.6.1 质数多否

随机选择一个整数 \(p\) 是一个质数的概率遵从 \(1/ln(p)\) 概率,实际上,我们只需要检测奇数是否是质数,因此一个随机数是质数的可能性是:

\[P(p是质数) \approx 2/ln(p) \]

例 7.7 对于需要有 512 位 \(p\)\(q\) 的 RSA 算法,\(p,q \approx 2^{512}\),一个随机的奇数是质数的概率为:

\[P(p 是质数) \approx 2/ln(2^{512}) = 2/512ln(2) \approx 1/177 \]

这意味着,我们可以期望选择 177 个随机奇数,就可能找到一个质数。

基于上面的函数,整数是质数的概率下降是缓慢的,这意味着,即便是十分长的 RSA 参数,质数的密度依然是很高的。

7.6.2 质数测试

我们需要做的另一项任务,是确定一个整数 \(p\) 是否是质数。我们马上想到的是对数值进行因数分解。然而在 RSA 算法中使用的数值都是超大的数值,因数分解变得不切实际。我们其实并不关注 \(p\) 的因数分解,只需要关注这个数值是否是质数。这样问题就变得简单一点。可以使用费马质数测试或 Miller-Rabin 测试或是它们的变体。

费马质数测试

输入:质数替补 \(p\) 以及安全参数 \(s\)

输出:\(p\) 是否可能是质数

算法:

FOR i = 1 TO s
	choose random a in {2, 3, ... , p - 2}
	IF a^{p-1} != 1
		RETURN(p 是合数)
RETURN(p 可能是质数)

费马质数测试方法依赖于所有质数都遵循的费马定理。如果一个数值 \(a^{p-1} !\equiv 1\) 那么这个数就是合数,但是它的逆命题并不成立,存在合数满足 \(a^{p-1} \equiv 1\),为了探测它们,算法需要使用多个 \(a\) 运行 \(s\) 次。

不幸的是,有一些合数经过多个 \(a\) 的测试之后,依然表现得像个质数,这些数是卡麦尔数。给定一个卡麦尔数 \(C\),对于所有满足 \(gcd(a,C) = 1\) 的整数 \(a\),有:

\[a^{C-1} \equiv 1 \ mod \ C \]

这样的合数是非常少的,比如在 \(10^{15}\) 以下,一共有 100000 个卡麦尔数。

例 7.8 卡麦尔数

\(n = 561 = 3 \cdot 11 \cdot 17\) 是一个卡麦尔数,因为对于所有 \(gcd(a,561) = 1\),有:

\[a^{560} \equiv 1 \ mod \ 561 \]

如果卡麦尔数的所有质因数都非常大,只有很少的 \(a\) 能够通过费马测试检测出这个数是合数。

Miller-Rabin 素数测试

定理 7.6.1 给定一个奇质数候选者 \(p\) 的分解:

\[p - 1 = 2^ur \]

其中 \(r\) 是奇数,如果我们能够找到一个整数 \(a\),使得对于所有的 \(j = \{0,1,...,u-1\}\),满足:

\[a^r \ !\equiv 1 \ mod \ p \\ a^{r2^j} \ !\equiv p - 1 \ mod \ p \]

那么 \(p\) 是一个合数,否则 \(p\) 可能是一个质数。

输入:质数候补 \(p\)\(p - 1 = 2^ur\) 以及安全参数 \(s\)

输出:\(p\) 是否可能是质数

算法:

FOR i = 1 TO s
	choose random a in {2, 3, ... , p - 2}
	z = a^r mod p
	IF z != 1 and z != p - 1
		FOR j = 1 TO u - 1
			z = z^2 mod p
			IF z = 1
				RETURN(p 是合数)
		IF z != p - 1
			RETURN(p 是合数)
RETURN(p 可能是质数)

这个算法依然会有合数 \(p\) 被判断为质数的情况发生。但是,只要我们使用不同的随机基数 \(a\) 多跑几遍,这个误判的概率就会显著降低。运行的次数基于安全参数 \(s\)

下表展示了确保合数被误判为质数的概率低于 \(2^{-80}\),分别需要多少 \(s\) 参数:

\(p\) 位数 安全参数 \(s\)
250 11
300 9
400 6
500 5
600 3

例 7.9 Miller-Rabin 测试

\(p = 91\),将 \(p\) 写作 \(p - 1 = 2^1 \cdot 45\),我们选择一个安全参数 \(s = 4\),现在选择 \(s\) 次随机数 \(a\)

  1. \(a = 12,z = 12^{45} \equiv 90 \ mod \ 91\),因此 \(p\) 可能是一个质数
  2. \(a = 17,z = 17^{45} \equiv 90 \ mod \ 91\),因此 \(p\) 可能是一个质数
  3. \(a = 38,z = 38^{45} \equiv 90 \ mod \ 91\),因此 \(p\) 可能是一个质数
  4. \(a = 39,z = 39^{45} \equiv 78 \ mod \ 91\),因此 \(p\) 是一个合数

可以看到 12,17,38 都给出了错误判定。

7.7 实际的 RSA:填充

我们前面介绍的,是仅停留在课本中的 RSA 算法,具有很多弱点。实际的 RSA 算法必须使用填充策略。填充策略是十分重要的,如果没有合适地使用填充策略,那么 RSA 实现可能并不安全。下面列举出前面提到的 RSA 算法中可能有的问题:

  • RSA 加密是确定性的,既选定一个密钥,原文总是映射到指定的密文。攻击者可以从密文中获取到这种静态属性
  • 原文 \(x = 0,x = 1,x = -1\) 产生的密文等于 \(0,1,-1\)
  • 小的公钥质数 \(e\) 以及小的原文 \(x\),如果没有填充或只进行了弱填充,那么极易受到攻击,不过即便对小的公钥质数 \(e\),也没有已知的攻击手段

RSA 是可再塑的。如果攻击者可以将密文转换成其他密文,而解密出的原文是有意义的,那么运算策略被称作是可再塑的。注意到,攻击者不需要解密密文,只需要以一个可预测的形式进行操作即可。如果攻击者将密文 \(y\) 替换为 \(s^ey\),其中 \(s\) 是某个正整数,如果接收者解密接收到的更改后的密文,得到:

\[(s^ey)^d \equiv s^{ed}x^{ed} \equiv sx \ mod \ n \]

即便攻击者并没有解密密文,这样的修改也是有害的。比如,如果 \(x\) 是要发送的金额,那么选择 \(s = 2\),攻击者可以将这个金额翻倍。

所有这些问题的解决方法是使用填充,在进行加密之前,将一个随机结构嵌入到原文中,可以避免上面的情况发生。现在的策略比如 OAEP: Optimal Asymmetric Encryption PaddingPKCS #1: Public Key Cryptography Standard #1 的规范。

假设 \(M\) 是待填充的信息,令 \(k\) 做为 \(n\) 字节长度的模数。令 \(|H|\) 做为哈希函数输出的字节长度,令 \(|M|\) 做为信息的字节长度。哈希函数计算出每一个输入信息的固定长度摘要(160/256位)。令 \(L\) 是信息的可选标识(默认空字串)。使用最新的 PKCS #2 (v2.1),在 RSA 加密中使用填充策略通过如下方式实现:

  1. 生成长度 \(k-|M|-2|H| -2\) 归零字串 \(PS\)\(PS\) 的长度可能是 0

  2. \(Hash[L],PS\) 以及十六进制的值 0x01 以及信息 \(M\) 串到一起,形成一个 \(k-|H|-1\) 字节长度的信息块 \(DB\)

    \[DB = Hash(L)||PS||0x01||M \]

  3. 生成长度为 \(|H|\) 的随机字串种子

  4. \(dbMask = MGF(seed,k-|H|-1)\),其中 \(MGF\) 是掩码生成函数,实际上,哈希函数如 SHA-1 常用作 \(MFG\)

  5. 令掩码后的数据 \(maskedDB = DB \oplus dbMask\)

  6. 令种子掩码 \(seedMask = MGF(maskedDB,|H|)\)

  7. 令掩码后的种子 \(maskedSeed = seed\oplus seedMask\)

  8. 将掩码后的种子与掩码后的数据与单个字节 0x00 串在一起,生成长度为 \(k\) 字节的编码后的信息 \(EM\)

    \[EM = 0x00||maskedSeed||maskedDB \]

在解密侧,校验解密后的信息结构。比如,如果没有分割 \(PS\) 以及 \(M\) 的 0x01,那么会生成解密错误。任何情况的解密错误都不会提供原文有关的信息。

7.8 攻击

自从 RSA 算法在 1977 年提出到现在,已经有数不清的对 RSA 的攻击。但是都没能从根本上撼动 RSA 算法本身的安全性。对 RSA 的攻击主要有下面机中方式:

  1. 协议攻击
  2. 数学攻击
  3. 边带攻击

下面,我们简单点评一下这些攻击方式。

协议攻击

攻击者攻击某个 RSA 算法实现,比如前面提到的可再塑性。可以通过填充避免被攻击。

数学攻击

已知对 RSA 的最好的攻击手段是对模数进行因数分解。如果攻击者知道模数 \(n\),公钥 \(e\) 以及密文 \(y\)。他的目标是计算私钥 \(d\),这个私钥具有属性 \(e\cdot d \equiv 1 \ mod \ \Phi(n)\)。似乎只要他使用扩展欧拉算法就能计算得到 \(d\),但是现实是,他并不知道 \(\Phi(n)\) 的值。这时来到了因数分解,如果攻击者能够解出 \(n\) 得到质数 \(p\)\(q\),如果攻击者能够实现这个目标,那么解密简单异常:

\[\Phi(n) = (p-1)(q-1) \\ d^{-1} \equiv e \ mod \ \Phi(n) \\ x \equiv y^d \ mod \ n \]

为了防止攻击者实现这样的攻击,模数必须足够大,这也是为什么 RSA 算法的模数在 1024 位或更长。

边带攻击

比如功耗轨迹。详见原文。

7.9 软件与硬件实现

先看一下运算难度,假设我们使用 2048 位的模数,解密时我们使用完全长度的私钥,大概需要 3072 次平方与乘法,每一个都会涉及 2048 位的操作数。假设我们使用 32 位的 CPU,操作数可以使用 64 个寄存器表示,单次乘法需要进行 \(64^2 = 4096\) 次乘法操作,因为我们需要对两个操作数的寄存器做乘法。另外,我们还需要进行模运算,最好的算法也需要 \(64^2 = 4096\) 次乘操作。因此,单次乘操作 CPU 要进行 \(4096 + 4096 = 8192\) 次整数乘法操作。因为我们有 3072 次运算,单次解密需要进行的乘操作是:

\[$(32-bit) = 3072 \times 8192 = 25165824 \]

这显然是一个很大的计算开销,其他公钥策略也有相似的复杂度。

如果使用高性能 VLSI 芯片,可以在高速硬件上以 100us 实现 RSA 操作。

如果以软件实现,在现今的 2 GHz CPU 上,对 2048 位 RSA 需要约 10ms 进行一次解密运算,这样的带宽约为 204800 bps。相比于现在的网速,这显然是很慢的速度。因此 RSA 并不怎么用作块数据加密。

posted @ 2023-03-31 23:39  ArvinDu  阅读(768)  评论(0编辑  收藏  举报