文章目录
RSA的RFC文档已经更新了很多次,截至本文(2023.1),最新的文档是 RFC 8017: PKCS #1: RSA Cryptography Specifications Version 2.2。在页面开头搜索关键词Obsolete可以链接到历史文档。应用较广的版本是 RFC 2313: PKCS #1: RSA Encryption Version 1.5
也可以参考NIST.SP.800-56B和FIPS 186-4, Digital Signature Standard (DSS) | CSRC (nist.gov)。
PKCS, The Public-Key Cryptography Standards,涉及多个标准,其中PKCS #1为RSA的标准,可以在RFC官网搜索PKCS查阅。
简介:
- 命名取自三位发明者的姓氏字母Ron Rivest、Adi Shamir、Leonard Adleman;
- 官网:https://rsa.com/
- 1983年申请专利,现已过期,所以可以商用;
- 公钥密码基于数学困难问题保证机密性,RSA的基础是,大整数质因数分解十分困难;
- RSA的实现通常会用到Base64,主要是为了防止产生乱码;
- RSA的密钥长度、密文和签名长度与模量n一致,比如2048 bits(256 bytes),参考FIPS记为nlen,RSA的安全强度与模量n的位数相关联;
- 2017年根据ECRYPT报告,建议长度不少于2048 bits;
- FIPS 186-4签名标准中为1024, 2048 和3072 bits;
- 签名和解密(基于私钥)比验签和加密(基于公钥)慢;
- …
文章与PKCS一致。
3. Key Types
RSA Public Key:
- n, the RSA modulus(模量), a positive integer
- e, the RSA public exponent, a positive integer
RSA Private Key:
- n, the RSA modulus, a positive integer, the same as in the corresponding RSA public key.
- d, the RSA private exponent, a positive integer
其中,私钥在RFC 8017中还有第二种表示法,参数很多,感兴趣的可查看原文档。
密钥生成步骤参考FIPS 186-4
- B.3.1 Criteria for IFC Key Pairs
- B.3.3 Generation of Random Primes that are Probably Prime
IFC: Integer Factorization Cryptography
术语:
- LCM, Least Common Multiple, 最小公倍数。
- GCM, Greatest Common , 最大公约数。
公钥质数E
根据FIPS 186-4 B.3.1 1(b),E使用满足以下条件的默认值即可:
2^16 < e < 2^256
65537 == 2^16 + 1 # default
若E采用随机值,则性能不可控。
生成随机大质数p和q
需要使用伪随机数生成器生成这两个大质数,
- (p-1)和(q-1)分别与e互素( relatively prime to e)
- len§ = len(q) = nlen/2
- 2(nlen-1)/2 <= p <= 2nlen/2 - 1 == 2len§ - 1, q一致
- p和q差值 > 2nlen/2-100
N = p x q,生成N后丢弃p和q。
生成方法有两种:
- Provable primes (see Appendix B.3.2)
- Probable primes (see Appendix B.3.3).
判断素数
方法有很多,FIPS 186-4 C.3 PROBABILISTIC PRIMALITY TESTS提供了以下方法:
- Miller-Rabin Probabilistic Primality Test
- Enhanced Miller-Rabin Probabilistic Primality Test
- (General) Lucas Probabilistic Primality Test
其它还有费马素性检测(Fermat Primality Test)等。
参考代码:\Crypto\Math\Primality.py
求最小公倍数L
L = LCM(p-1, q-1)
GCD(E, L) == 1,保证一定存在私钥中的D;
求私钥质数D
2nlen/2 < D < L,若不满足需要重新生成p和q。
E x D mod L == 1,保证可以解密还原明文。
即:D = E-1mod L
等价于:1==(ED) mod L
Miracl库的xgcd可以用来求模逆:
xgcd(x, p, x, x, x,); // x = 1/x mod p (p is prime)
PyCryptodome generate()
\Crypto\PublicKey\RSA.py
def generate(bits, randfunc, e=65537):
# ...
d = n = Integer(1)
e = Integer(e)
while n.size_in_bits() != bits and d < (1 << (bits // 2)):
# Generate the prime factors of n: p and q.
# By construciton, their product is always
# 2^{bits-1} < p*q < 2^bits.
size_q = bits // 2
size_p = bits - size_q
min_p = min_q = (Integer(1) << (2 * size_q - 1)).sqrt()
if size_q != size_p:
min_p = (Integer(1) << (2 * size_p - 1)).sqrt()
def filter_p(candidate):
return candidate > min_p and (candidate - 1).gcd(e) == 1
p = generate_probable_prime(exact_bits=size_p,
randfunc=randfunc,
prime_filter=filter_p)
min_distance = Integer(1) << (bits // 2 - 100)
def filter_q(candidate):
return (candidate > min_q and
(candidate - 1).gcd(e) == 1 and
abs(candidate - p) > min_distance)
q = generate_probable_prime(exact_bits=size_q,
randfunc=randfunc,
prime_filter=filter_q)
n = p * q
lcm = (p - 1).lcm(q - 1)
d = e.inverse(lcm)
4. Data Conversion Primitives
- I2OSP - Integer-to-Octet-String == long_to_bytes
- OS2IP - Octet-String-to-Integer == bytes_to_long
5. Cryptographic Primitives
源码参考PyCryptodome
5.1. Encryption and Decryption Primitives
公钥加密,私钥解密。
5.1.1 RSAEP
RSA Encryption Primitive
def RSAEP ((n, e), m):
# an integer between 0 and n - 1
return c = m**e % n
# pycryptodome \Crypto\PublicKey\RSA.py
def _encrypt(self, plaintext):
if not 0 <= plaintext < self._n:
raise ValueError("Plaintext too large")
return int(pow(Integer(plaintext), self._e, self._n))
5.1.2 RSADP
RSA Decryption Primitive
# def RSADP (K, c):
# K: one of the 2 forms of private key
def RSADP((n,d), c):
return m = c**d % n
def RSADP((p, q, dP, dQ, qInv, r_i, d_i, t_i)
, c):
m_1 = c**dP % p
m_2 = c**dQ % q
if u > 2:
m_i = c**(d_i) % r_i # i = 3, ..., u
h = (m_1 - m_2) * qInv % p
m = m_2 + q * h
if u > 2:
R = r_1
for i in range(3, u+1):
R = R * r_(r-1)
h = (m_i - m) * t_i % r_i
m = m + R * h.
return m
# pycryptodome \Crypto\PublicKey\RSA.py
def _decrypt(self, ciphertext):
if not 0 <= ciphertext < self._n:
raise ValueError("Ciphertext too large")
if not self.has_private():
raise TypeError("This is not a private key")
# Blinded RSA decryption (to prevent timing attacks):
# Step 1: Generate random secret blinding factor r,
# such that 0 < r < n-1
r = Integer.random_range(min_inclusive=1, max_exclusive=self._n)
# Step 2: Compute c' = c * r**e mod n
cp = Integer(ciphertext) * pow(r, self._e, self._n) % self._n
# Step 3: Compute m' = c'**d mod n (normal RSA decryption)
m1 = pow(cp, self._dp, self._p)
m2 = pow(cp, self._dq, self._q)
h = ((m2 - m1) * self._u) % self._q
mp = h * self._p + m1
# Step 4: Compute m = m**(r-1) mod n
result = (r.inverse(self._n) * mp) % self._n
# Verify no faults occurred
if ciphertext != pow(result, self._e, self._n):
raise ValueError("Fault detected in RSA decryption")
return result
5.2. Signature and Verification Primitives
私钥签名(加密),公钥验证(解密)。
其实和5.1加解密是一样的。
5.2.1. RSASP1
RSA Signature Primitive, version 1
# def RSASP1 (K, m):
# K one of the 2 forms of private key
# m message representative, an integer between 0 and n - 1
def RSASP1((n,d), m)
return s = (m**d) % n
def RSASP1((p, q, dP, dQ, qInv, r_i, d_i, t_i)
, m):
s_1 = m**dP % p
s_2 = m**dQ % q
if( u > 2):
s_i = m**(d_i) % r_i # i = 3, ..., u
h = (s_1 - s_2) * qInv % p
s = s_2 + q * h
if ( u > 2 ):
R = r_1
for i in range(3, u+1):
R = R * r_(i-1)
h = (s_i - s) * t_i mod r_i
s = s + R * h
return s
5.2.2. RSAVP1
RSA Verification Primitive, version 1
def RSAVP1 ((n, e), s):
# an integer between 0 and n - 1
return m = s**e % n
6. Overview of Schemes
这一部分仅涉及RSA对数据的处理,实际应用中还要有密钥管理,如密钥获取和验证。
Two types of scheme(方案) :
- encryption schemes
- RSAES-OAEP (Section 7.1)
- RSAES-PKCS1-v1_5 (Section 7.2)
- signature schemes
- RSASSA-PSS (Section 8.1)
- RSASSA-PKCS1-v1_5 (Section 8.2)
一对密钥仅能用于一种应用方案。
加解密示例
# from Crypto.Util.number import inverse
def inverse(u, v):
"""The inverse of :data:`u` *mod* :data:`v`."""
u3, v3 = u, v
u1, v1 = 1, 0
while v3 > 0:
q = u3 // v3
u1, v1 = v1, u1 - v1*q
u3, v3 = v3, u3 - v3*q
while u1<0:
u1 = u1 + v
return u1
def gcd(a:int, b:int):
if ( b > a):
a,b = b, a
while b:
a,b = b, a % b
return a;
def lcm(a:int, b:int):
# 最小公倍数=两数相乘/两数的最大公约数
a1 = a
b1 = b
while b1:
a1,b1 = b1, a1 % b1 #a1为最大公约数
return (a * b // a1)
p = 17
q = 19
n = p * q # 323
L = lcm(p-1, q-1) # 144
e = 5 # gcd(e, L) = 1
d = inverse(e,L) # 29 * 5 mod 323 == 1
# RSAEP Condition: 0 < m < n
m = 123
cipher = (m**e)%n # 255
decrypt = (cipher**d)%n # 123
OpenSSL接口
接口
OpenSSL 3.0以前:
https://www.openssl.org/docs/man3.0/man3/RSA_new.html
https://www.openssl.org/docs/man3.0/man3/RSA_generate_key.html
OpenSSL 3.0以后:
- https://www.openssl.org/docs/man3.0/man7/EVP_PKEY-RSA.html
- https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_generate.html
- https://www.openssl.org/docs/man3.0/man3/PEM_write_PrivateKey.html
Demo:
- \demos\pkey\EVP_PKEY_RSA_keygen.c
命令
openssl genrsa --help
openssl rsautl -help
openssl rsa -help
openssl genrsa -out prikey.pem 1024
openssl rsa -in prikey.pem -RSAPublicKey_out -out pubkey.pem
# -----BEGIN RSA PUBLIC KEY-----
openssl rsa -in prikey.pem -pubout -out pubkey.pem
# -----BEGIN PUBLIC KEY-----
# 用-pubout
openssl rsautl -encrypt -in data.txt -inkey pubkey.pem -pubin -out data_enc.txt
openssl rsautl -decrypt -in data_enc.txt -inkey prikey.pem -out data_dec.txt
实现
https://github.com/C0deStarr/CryptoImp/pubkey/rsa
参考资料
RFC 8017: PKCS #1: RSA Cryptography Specifications Version 2.2
FIPS 186-4, Digital Signature Standard (DSS) | CSRC (nist.gov)