主要算法简介
RSA 的原理是在于能够找到三个很大的正整数 \(e,d,n\) 使得对于任何 \(0 \le m \lt n\):
\[ (m^e)^d \equiv m \pmod{n} \]
其中公钥为 \((n,e)\),可以发送给任何人;私钥为 \((n,d)\),只能由所有者掌握。RSA 支持 4 种操作:加密/解密,签名/验证签名。
加密
首先用一些编码手段把需要加密的信息转换成整数 \(m\),用公钥中的两个参数计算
\[ c = m^e \pmod{n} \]
其中 \(c\) 就是加密后的信息了。
解密
由私钥所有者计算
\[ \begin{align} c^d \pmod{n} &= (m^e \pmod{n})^d \pmod{n} \\ &= (m^e)^d \pmod{n} \\ &= m \pmod{n} \end{align} \]
就能还原原来的信息了(乘积与其余数乘积同余,见同余命题 a
)
签名
- 首先对要签名的信息作一个 message digest,常用的如 MD5 和 SHA 系列;
- 然后把这个 digest 转换成整数 \(m\) 使得 \(1 \le m \lt n\);
- 最后用私钥 \((n,d)\) 计算 \(s = m^d \pmod{n}\)
验签
- 用公钥 \((n,e)\) 计算 \(v = s^e \pmod{n}\)
- 把 \(v\) 转换回 digest
- 独立对要签名的信息再作一次 digest
- 对比这两个 digest,如果相同的话,那这个签名就是有效的
Note
可以看到加解密和签名验签刚好是两个方向:前者是公钥加密发送给所有者解密,后者是用私钥签名发送给其他人用公钥验签;所以这就是为何一般不建议使用相同的 key 同时用作加密和签名:
Using the same key for encryption and signing
Given that the underlying mathematics is the same for encryption and signing, only in reverse, if an attacker can convince a key holder to sign an unformatted encrypted messageusing the same key then she gets the original.
公私钥生成
生成 \(e,d,n\) 的算法大致如下:
- 生成两个大的随机质数 \(p\) 和 \(q\)
- 计算 \(n = pq\),\(n\) 是公钥的一部分
- 计算 \(n\) 的欧拉函数 \(\varphi(n) = (p - 1)(q - 1)\)
- 选择公钥幂 \(1 < e < \varphi(n)\),且 \(e\) 和 \(\varphi(n)\) 互质
- 计算私钥幂 \(d\) 使得 \(ed \equiv 1 \pmod{\varphi(n)}\)
- \((e, n)\) 是公钥,其余 \(p,q,d,\varphi(n)\) 这些都需要保密
通过这样算法生成出来的 \(e,d,n\) 即可满足最开始的要求,证明可看下面
注意 \(e\) 和 \(n\) 是公开的,而通过 \(\varphi(n)\) 和 \(e\) 则可以算出 \(d\),故其实 RSA 的安全性即来自于 从 \(n\) 难以计算出 \(\varphi(n) = (p - 1)(q - 1) = (n + 1) - (p + q)\), 这是因为大数质数分解目前没有有效算法, 也就是从 \(n\) 难以分解出 \(p,q\),也就难以算出 \(\varphi(n)\) 了
实现细节
更多细节有兴趣可以看一下下面参考的链接,稍微摘录一些:
- 计算 \(y = x^e \pmod{n}\) (所谓 modular exponentiation)的复杂度大约为 \(O(k^3)\),其中 \(k\) 是 \(n\) 的长度(例如 1024 bits),且当 \(e\) 的二进制表示中 1 的数量越多则越慢。
- 公钥幂 \(e\) 一般选取固定的:例如 \[ \begin{split} 3 = 2^{2^0}+1 \\ 5 = 2^{2^1}+1 \\ 17 = 2^{2^2}+1 \\ 257 = 2^{2^3}+1 \\ 65537 = 2^{2^4}+1 \end{split} \] 这5个是前五个 fermat number:\(F_x = 2^{2^x}+1\) ,恰好都是素数,但 \(F_5\) 以及后面的 fermat number 不是素数。显然这些数字的二进制表示只有两个 1,如上述原因,在加密的过程中能节省计算时间。实际应用中一般都是使用 65537 ,因为小的 \(e\) 不太安全。
- 多个素数相乘结果作为模的一个好处是可以更快速的计算 \(m = c^d
\pmod{n}\)
- 使用中国剩余定理(Chinese Remainder Theorem,CRT)可以把这个计算转换成对 \(p,q\) 的 modular exponentiation 计算,而 \(p,q\) 的长度都只有 \(n\) 的一半,所以复杂度会降低到原来的四分之一:\(O(2(\cfrac{k}{2})^3) = O(\cfrac{k^3}{4})\)
存储格式
如上所述,RSA 的公私钥实际上就是些数字,但我们平时实际使用的是 .pem
格式(或 .der
)的 key 。
.pem 格式头部和尾部的有一些标识符(-----BEGIN XXXX-----
)这些是为了让
parser 可以马上知道这个文件包含的是公钥私钥,或是证书等。
中间的数据其实是 base64 编码过后的 DER(Distinguished Encoding Rules) 编码的 ASN.1(Abstract Syntax Notation One) 数据。
不妨写个程序看一下(dec.py
):
import sys
from base64 import b64decode
from pyasn1.codec.der.decoder import decode as der_decode
from pyasn1_modules import rfc2437
def read_key_pem(src):
if isinstance(src, bytes):
src = src.decode('ascii')
data = ''.join((line.strip() for line in src.split('\n') if not line.startswith('-----')))
return der_decode(b64decode(data), asn1Spec=rfc2437.RSAPrivateKey())[0]
obj = read_key_pem(open(sys.argv[1]).read())
print(obj.prettyPrint())
生成一个 rsa private key 来跑一下:
$ openssl genrsa -out pri.pem 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
...........................+++++
............+++++
e is 65537 (0x010001)
$ python ./dec.py pri.pem
RSAPrivateKey:
version=0
modulus=30078218357432552470030675464869....
publicExponent=65537
privateExponent=5666198999662210....
prime1=17622717115310961559542629....
...
可以看到实际上存储的就是上面所述的各种数字,完整的定义是在 PKCS#1 标准中:
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
其中 modulus/publicExponent/privateExponent 即模/公钥幂/私钥幂,可以看到实际中的 RSA 私钥也是包含 publicExponent 的; 所以这就是为什么 openssl 工具中能从私钥中导出公钥的原因。
更新 2022-09-10: 现在也可以用 jupyterlite 来跑上述程序啦~
相关数学
同余命题
仅列出一些会用到同余式命题
a
\(ab \equiv r_ar_b \pmod{n}\),其中 \(r_a = a \pmod{n}\),\(r_b = b \pmod{n}\)
设 \(a = k_an + r_a\),\(b = k_bn + r_b\),则 \(ab = k_ak_bn^2 + k_ar_bn + k_br_an + r_ar_b\), 则有 \(ab \equiv r_ar_b \pmod{n}\)
b
若 \(a\) 和 \(n\) 互质,且 \(ab \equiv ac \pmod{n}\),则有 \(b \equiv c \pmod{n}\)
- 由于 \(ab \equiv ac \pmod{n}\),故有 \(ab = kn + r\) 和 \(ac = qn + r\)
- 两式相减得 \(a(b - c) = (k - q)n\)
- 由于 \(a\) 和 \(n\) 互质,则 \(n\) 必然是 \(b - c\) 的因子,即 \(b - c \equiv 0 \pmod{n}\),即 \(b \equiv c \pmod{n}\)
c
若 \(a\) 和 \(b\) 均与 \(n\) 互质,则 \(ab \pmod{n}\) 也与 \(n\) 互质
令 \(ab = kn + r\),若余数 \(r\) 与 \(n\) 不互质,则存在一个 \(n\) 和 \(r\) 的公共因子 \(x\) 且 \(x > 1\), 于是有 \(ab = (kn_x + r_x)x\),但无论 \(a\) 还是 \(b\) 都跟 \(n\) 互质,不可能有 \(x\) 作为因子,矛盾
d
若 \(a \equiv b \pmod{p}\) 且 \(a \equiv b \pmod{q}\),又 \(p\) 和 \(q\) 互质, 则有 \(a \equiv b \pmod{pq}\)
由于 \(a - b\) 能同时被 \(p\) 和 \(q\) 整除,所以也能被 \(lcm(p, q)\) (最小公倍数)整除,又因为 \(p\) 和 \(q\) 互质, 故 \(lcm(p, q) = pq\)
Euler’s totient function
- 欧拉函数
- \(\varphi(n)\) 定义为所有小于等于 \(n\) 且和 \(n\) 互质的自然数总数
例如
\[\begin{eqnarray} \varphi(1) &= |\{1\}| &= 1 \\ \varphi(2) &= |\{1\}| &= 1 \\ \varphi(3) &= |\{1, 2\}| &= 2 \\ \varphi(4) &= |\{1, 3\}| &= 2 \\ \varphi(5) &= |\{1, 2, 3, 4\}| &= 4 \\ \varphi(6) &= |\{1, 5\}| &= 2 \\ \varphi(7) &= |\{1, 2, 3, 4, 5, 6\}| &= 6 \\ \varphi(8) &= |\{1, 3, 5, 7\}| &= 4 \\ \varphi(9) &= |\{1, 2, 4, 5, 7, 8\}| &= 6 \\ \end{eqnarray}\]
特别地
若 \(n\) 是质数,则 \(\varphi(n) = n - 1\)
因为除了 \(n\) 自己外,其他小于它的数均与之互质
若 \(n = pq\) 且 \(p,q\) 均为质数,则 \(\varphi(n) = (p - 1)(q - 1)\)
这是因为少于等于 \(n\) 且跟 \(n\) 不互质的只能是:
- \(p\) 的倍数,即 \(p, 2p, ...., qp\) 共 \(q\) 个
- 或者是 \(q\) 的倍数,即 \(q, 2q, ...., pq\) 共 \(p\) 个
于是 \(\varphi(n) = n - q - p + 1 = (p - 1)(q - 1)\)(\(pq\) 多减了一次所以加 1)
Euler’s theorem
欧拉定理
若 \(a\) 和 \(n\) 互质,有 \[a^{\varphi(n)} \equiv 1 \pmod{n}\]
假设所有小于等于 \(n\) 且和 \(n\) 互质的自然数集合为 \(S_n = \{s_1, s_2, ..., s_{\varphi(n)}\}\),令 \(a\) 为任意与 \(n\) 互质的自然数, 再令 \(r_i = as_i \pmod{n}\),则所有 \(r_i\) 组成集合 \(R_n = \{r_1, r_2, ..., r_{\varphi(n)}\}\),下面证明实际上 \(S_n = R_n\):
- 任一 \(r_i\) 也与 \(n\) 互质,因此 \(r_i \in S_n\)
;这是根据同余命题
c
得出的,因为 \(a\) 和 \(s_i\) 均与 \(n\) 互质 - 若 \(i \neq j\),则 \(as_i \not\equiv as_j \pmod{n}\),因此
\(r_i \neq r_j\),否则若 \(as_i \equiv as_j \pmod{n}\),又
\(a\) 和 \(n\) 互质, 根据同余命题
b
推出 \(s_i \equiv s_j \pmod{n}\) 矛盾
因此 \(R_n\) 中也有 \(\varphi(n)\) 个各不相同又都是 \(S_n\) 中的元素,于是只能有 \(S_n = R_n\) 了
若把这两个集合中的所有元素相乘就有
\[ (ar_1 \pmod{n})(ar_2 \pmod{n})...(ar_{\varphi(n)} \pmod{n}) = r_1r_2...r_{\varphi(n)} \]
取模且根据同余命题 a
\[ a^{\varphi(n)}r_1r_2...r_{\varphi(n)} \equiv r_1r_2...r_{\varphi(n)} \pmod{n} \]
使用同余命题 b
约掉
\(r_1r_2...r_{\varphi(n)}\),因为这些都与 \(n\) 互质
\[ a^{\varphi(n)} \equiv 1 \pmod{n} \]
RSA 正确性证明
若 \(p,q\) 均为质数,\(n = pq\),又 \(ed \equiv 1 \pmod{\varphi(n)}\),则对于任意 \(0 \le m \lt n\), 有 \(m^{ed} \equiv m \pmod{n}\)
\(ed \equiv 1 \pmod{\varphi(n)}\) 即存在 \(k \ge 0\) 使得 \(ed = k\varphi(n) + 1 = k(p - 1)(q - 1) + 1\)
若 \(m = 0\),显然
若 \(m > 0\) 且与 \(n\) 互质,则由上面欧拉定理可得 \(m^{\varphi(n)} \equiv 1 \pmod{n}\),于是有
\[ m^{ed} = m^{k\varphi(n) + 1} = m \cdot (m^{\varphi(n)})^k \equiv m \cdot (1)^k \equiv m \pmod{n} \]
若 \(m > 0\) 且不与 \(n\) 互质,则 \(m\) 要么是 \(p\) 的倍数,要么是 \(q\) 的倍数,但不能同时为 \(p\) 和 \(q\) 的倍数, 因为这两个质数的最小公倍数即 \(n\),而 \(m < n\)
不妨假设是 \(p\) 的倍数,且不是 \(q\) 的倍数
\(m\) 是 \(p\) 倍数则 \[ m^{ed} \equiv 0 \equiv m \pmod{p} \]
\(m\) 不是 \(q\) 倍数故与之互质,再次由欧拉定理得出 \(m^{\varphi(q)} = m^{q - 1} \equiv 1 \pmod{q}\),于是
\[ m^{ed} = m \cdot (m^{(q - 1)})^{k(p - 1)} \equiv m \cdot (1)^{k(p - 1)} \equiv m \pmod{q} \]
由同余命题
d
就能得出 \(m^{ed} \equiv m \pmod{pq}\)
参考
- http://www.di-mgt.com.au/rsa_alg.html
- http://www.di-mgt.com.au/rsa_theory.html
- https://en.wikipedia.org/wiki/RSA_(cryptosystem)
- https://en.wikipedia.org/wiki/Euler%27s_totient_function
- http://crypto.stackexchange.com/questions/1729/why-does-the-pkcs1-rsa-private-key-structure-contain-more-than-just-exponent-and