当我们给定公钥e与n时,对于很大的n,其欧拉函数的值是很难求解的(只有采取质因数分解的方法,而大数的质因数分解几乎是计算上不可行的)。
因此便很难求出私钥。
如何使用RSA加密
那么接收方Bob,通过选取合适的p和q,那么通过RSA算法,可以获得公钥和私钥,那么将公钥公布出去,私钥自己保存,那么别人就可以向接收方Bob发送密文,而只有拥有密钥的Bob才可以解密。
n为大数(1024bits)的RSA加密的python实现
面临的问题有:
1.如何创建两个不相等且足够大的素数:使用费马素性检验
2.如何求得e对于模数为n欧拉函数的乘法逆元:扩展欧几里得算法exgcd(a,b)求得模逆元d
3.对于加密解密,可以使用蒙哥马利算法 montgomery(n, p, m):快速计算(n^p)%m的值,即逐次平方法
值得注意的是:RSA加解密算法本身是操作于整数的模幂运算,而要加密的消息明文通常以字节序列表示,所以需要两个转换函数。
1.创建两个不相等且足够大的素数
先引进费马定理:
# 费马算法进行素数测试
test_times = 20
def isPrime(n):
for i in range(0, test_times):
a = random.randint(1, n - 1)
if montgomery(a, n - 1, n) != 1:
return False
return True
快速指数取模的实现算法
逐次平方法:
# 蒙哥马利算法
# 快速计算(n^p)%m的值,即逐次平方法
def montgomery(n, p, m):
k = 1
n %= m #如果n过大可以先与m模一下
while p != 1:
if 0 != (p & 1):#使用与运算(p & 1)来判断是否二进制末尾为1,也就是是否为奇数
k = (k * n) % m
n = (n * n) % m
p >>= 1
return (k * n) % m
求得e对于模数为n欧拉函数的乘法逆元:扩展欧几里得算法exgcd(a,b)求得模逆元d
扩展欧几里得具体例子:
# 计算方程 gcd(a,b)=ax+by 的其中一个解
# 返回值是一个元祖,分别为最大公约数gcd(a,b),以及一组x,y的解
def exgcd(a, b):
x0, y0 = 1, 0
x1, y1 = 0, 1
x, y = 0, 1
r = a % b#余数
q = (a - r) // b#商
while r != 0:#余数不等于0
x = x0 - q * x1
y = y0 - q * y1
x0, y0 = x1, y1
x1, y1 = x, y
a = b#除数赋给被除数
b = r#余数赋给除数
r = a % b
q = (a - r) // b
return (b, x, y)
整个代码:
ZRSA
import random
# 根据指定的bits位数,生成
# 返回一个元组,分别为公钥和密钥
def generateKey(bits):
p, q = _generatePQ(bits)
n, e, d = _calcNED(p, q)
return ((e, n), (d, p, q))
# 生成两个不相等的随机大质数
# bits表示要生成的大质数的二进制位数
# 两个大质数以元祖形式返回
def _generatePQ(bits):
bits >>= 1
p = randomPrimeBits(bits)
# 采用Miller_rabin算法进行素数判定
q = randomPrimeBits(bits)
while p == q:
q = randomPrimeBits(bits)
# print('random prime finished. %d, %d'%(p, q))
return (p, q)
# 生成指定位数的随机大质数
def randomPrimeBits(bits):
base1 = 1 << (bits - 1) # 1 * 2^(bits - 1)
base2 = (1 << bits) - 1 # 1 * 2^bits - 1
i = random.randint(base1, base2)
# print('Random range: %d ~ %d. Base: %d'%(base1, base2, i))
if i % 2 == 0: i += 1 # 如果生成的随机数是偶数,那么加 1
while True:
i += 2
if i > base2:
i = base1
if isPrime(i):
return i
# print(i, flush=True)
## 素数判断
# 费马算法进行素数测试
test_times = 20
def isPrime(n):
for i in range(0, test_times):
a = random.randint(1, n - 1)
if montgomery(a, n - 1, n) != 1:
return False
return True
# 蒙哥马利算法
# 快速计算(n^p)%m的值,即逐次平方法
def montgomery(n, p, m):
k = 1
n %= m #如果n过大可以先与m模一下
while p != 1:
if 0 != (p & 1):#使用与运算(p & 1)来判断是否二进制末尾为1,也就是是否为奇数
k = (k * n) % m
n = (n * n) % m
p >>= 1
return (k * n) % m
# 根据两个大质数P,Q计算出N,E,D
# 以元祖形式返回(n, e, d)
def _calcNED(p, q):
t = (p - 1) * (q - 1)
n = p * q
e, d = _calcED(t)
return (n, e, d)
def _calcED(t):
recommande = (65537, 3, 17)
e = 1
# 其实因为t是合数,所以e只要选择任意一个质数都会通过
for i in recommande:
if i < t:
e = i
break
d = modular_linear_equation(e, 1, t)[0]
return (e, d) # 私钥中的较大,解密和签名操作较慢,默认
# return (d, e) #公钥中的较大,加密和验证签名操作较慢
# 同余方程 ax≡b (mod n) 对于未知数 x 有解,当且仅当 gcd(a,n) | b。
# 且方程有解时,方程有 gcd(a,n) 个解
def modular_linear_equation(a, b, n):
r = []
d, x, y = exgcd(a, n)
if b % d != 0:
return r
x0 = x * (b // d) % n # 特解
r.append(x0)
for i in range(1, d): # 剩余解
r.append((x0 + i * (n - d)) % n)
return r
# 计算方程 gcd(a,b)=ax+by 的其中一个解
# 返回值是一个元祖,分别为最大公约数gcd(a,b),以及一组x,y的解
def exgcd(a, b):
x0, y0 = 1, 0
x1, y1 = 0, 1
x, y = 0, 1
r = a % b#余数
q = (a - r) // b#商
while r != 0:#余数不等于0
x = x0 - q * x1
y = y0 - q * y1
x0, y0 = x1, y1
x1, y1 = x, y
a = b#除数赋给被除数
b = r#余数赋给除数
r = a % b
q = (a - r) // b
return (b, x, y)
def rsaBits(key):
n = 0
if len(key) == 2: n = key[1]
if len(key) == 3: n = key[1] * key[2]
if n == 0: raise ValueError('key is not either public key nor private key')
return _integerBits(n)
# 获取一个整数至少为多少位的整数
def _integerBits(n):
count = 0
while n != 0:
count += 1
n >>= 1
return count
# return n.bit_length() #这个是python内置的函数,结果与我自己写的相同
# 因为明文不一定刚好能被等分完毕。所以开头用n个字节标记
# 剩下那一段的长度的。
codeTitle = b"Rsa:"
codeLeftLen = 4
def encrypt(data, pubkey):
if len(pubkey) != 2: raise ValueError('pubkey error.')
n = pubkey[1]
if isinstance(data, type(b'')): # Isinstance的用法是用来判断一个量是否是相应的类型
# 加密时,对于每个分段,密文比明文多1字节
srcSectionLen = (_integerBits(n) - 1) >> 3
tgtSectionLen = srcSectionLen + 1
# 求出分段数和多余字节数base
lenData = len(data)
base = lenData % srcSectionLen
# print(base)
ret = bytearray()
# print(ret)
# 加密第一段,仅当明文不能被分成整数份时
if base > 0:
a = int.from_bytes(data[0:base], 'big') # byte类型转换为int类型函数
# print('a:%d'%a)
b = montgomery(a, pubkey[0], n)
# print('b:%d'%b)
# 蒙哥马利算法 montgomery(n, p, m):
# 快速计算(n^p)%m的值,即逐次平方法
ret.extend(codeTitle)
# 要转化成bytes的数字num,要转化成的bytes的长度length,以字节为单位
# def _intToBytes(num, length):
# return num.to_bytes(length, 'big', signed=False)
ret.extend(_intToBytes(base, codeLeftLen))
ret.extend(_intToBytes(b, tgtSectionLen)) # 写入第一段密文
for i in range(base, lenData, srcSectionLen):
sectionData = data[i: i + srcSectionLen]
a = int.from_bytes(sectionData, 'big')
b = montgomery(a, pubkey[0], n)
ret.extend(_intToBytes(b, tgtSectionLen))
# print(ret)
return bytes(ret)
else:
raise TypeError("data must be 'bytes' type")
def _intToBytes(num, length):
return num.to_bytes(length, 'big', signed=False)
def decrypt(data, prikey):
if len(prikey) != 3: raise ValueError('prikey error.')
n = prikey[1] * prikey[2]
if isinstance(data, type(b'')):
# 解密时,对于每个分段,密文比明文多1字节
tgtSectionLen = (_integerBits(n) - 1) >> 3
srcSectionLen = tgtSectionLen + 1
# 求出分段数和多余字节数base
lenData = len(data)
ret = bytearray()
# 解密第一段没有完整长度的密文
pos = 0
if data[0:len(codeTitle)] == codeTitle:
pos = len(codeTitle)
lena = int.from_bytes(data[pos:pos + codeLeftLen], 'big')
pos += codeLeftLen
a = int.from_bytes(data[pos:pos + srcSectionLen], 'big')
pos += srcSectionLen
b = montgomery(a, prikey[0], n)
ret.extend(_intToBytes(b, lena)) # 写入第一段解密后的明文
for i in range(pos, lenData, srcSectionLen):
sectionData = data[i: i + srcSectionLen]
a = int.from_bytes(sectionData, 'big')
b = montgomery(a, prikey[0], n)
ret.extend(_intToBytes(b, tgtSectionLen))
return bytes(ret)
else:
raise TypeError("data must be 'bytes' type")
import ZRSA
if __name__ == '__main__':
bits = 1024
pubKey, priKey = ZRSA.generateKey(bits) # 返回两个元组 公钥(e,n) 私钥#(d,p,q)
# 1.首先生成两个随机的大质数: _generatePQ返回p、q
# 2.计算n、欧拉函数t、公钥、私钥
# 欧拉函数t = (p - 1) * (q - 1)
# n = p * q
# recommande = (65537, 3, 17)三者生成一个公钥e
# modular_linear_equation(e, 1, t) 计算同余方程 ed≡1 (mod t) 生成私钥
# pubfmt = '%x,%x'#输出为十六进制
# prifmt = '%x,%x,%x'
actBits = ZRSA.rsaBits(pubKey) # 计算n的位数
# print("n的位数为:")
# print(actBits)
print('---------------------计算结果如下:-----------------------------------------')
print('公钥e:十六进制为%x 十进制为%d' % (pubKey[0], pubKey[0])) # (e,n)
print('私钥d:十六进制为%x 十进制为%d' % (priKey[0], priKey[0])) # (d,p,q)
print('n的实际位数: %s' % actBits)
# print('公钥:' + pubfmt%pubKey)
# print('密钥:' + prifmt%priKey)
print('--------------------------------------------------------------------------')
# # 输入原文M
# #hex()函数用于将10进制整数转换成16进制,以字符串形式表示
# #0001002304050607080910111213为转化好的16进制
# M = ZRSA.hexStrToBytes('0001002304050607080910111213')
# # print(M) b'\x00\x01\x00#\x04\x05\x06\x07\x08\t\x10\x11\x12\x13'
# MD = ZRSA.hexStrToBytes('0001020304050607080910111212')
# print('原文:(%d bytes)\n%s'%(len(M),M))
M = b'hjhhhhhhhhhhhhhhhhjhj'
print(M)
C = ZRSA.encrypt(M, pubKey) # M只要为比特就行
# b'Textbook RSA in Python'
print('密文:(%d bytes)\n%s' % (len(C), C))
m = ZRSA.decrypt(C, priKey)
print('解密:(%d bytes)\n%s' % (len(m), m))
部分内容参考:https://www.packetmania.net/2021/03/01/Python-Textbook-RSA/#%E5%B7%A5%E5%85%B7%E5%87%BD%E6%95%B0