ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Python从零实现RSA:密钥生成、加解密与工程实践

用Python从零实现RSA:密钥生成、加解密与工程实践 简介这是一份基于Python的RSA密码算法的学士学位论文面向信息安全初学者与Python开发者帮助读者从数论基础到公钥/私钥生成、加密解密全流程理解非对称加密实现。文档完整覆盖研究背景、RSA数学原理、开发环境搭建、密钥生成与加解密代码设计、性能分析等章节并以西南财经大学毕业论文格式呈现可作为课程设计、毕业设计或密码学入门的参考资料。内容具体包括大素数选取、欧拉函数与模逆元计算、模幂运算的Python实现思路同时借助性能分析章节对比不同数据规模下的加密耗时让读者既看懂数学原理也能动手复现完整流程。资源包仅含1个docx文档压缩后约34KB内容精炼便于快速阅读。目前已有202人学习下载适合需要快速理解RSA核心算法并完成Python代码实现的读者参考。1. 从Python到RSA为什么自己实现一遍才算真正理解密码算法很多人在日常开发里调一行rsa.encrypt()就把加密写完了可一旦面试被问“RSA的公钥怎么生成p和q是什么关系为什么npq就能加密”就卡壳。这个标题看似是课程设计或毕设实际上把密码学中最重要的非对称算法和Python的整数运算能力结合在了一起。用Python实现RSA不仅能让软考信息安全工程师的RSA计算题从死记公式变成推导过程还能直接面对工程里的填充、分段、性能和安全问题。这篇博文会从数学原理开始手写核心函数再给出一套可运行的最小实现最后落到文件加密工具和常见报错。适合那些已经会用pip install rsa但想知道底层发生了什么的人。2. RSA的数学核心大整数运算与密钥生成背后的Python代码2.1 费马小定理与欧拉函数RSA的理论支点RSA的安全性建立在“大整数分解困难”上但它的正确性则依赖于数论中的一个小定理。设两个大素数p和q计算npq欧拉函数φ(n)(p-1)(q-1)。选择一个整数e满足1eφ(n)且gcd(e, φ(n))1。再计算e关于φ(n)的模逆元d使得(ed) mod φ(n)1。然后公钥是(n,e)私钥是(n,d)。为什么对任意消息m(m^e mod n)^d mod n m因为根据欧拉定理当m与n互质时m^φ(n) ≡ 1 (mod n)。而ed-1kφ(n)所以m^(ed)m^(1kφ(n))≡m。这个推导就是软考计算题的常客。实际实现时计算d需要使用扩展欧几里得算法求乘法逆元。# 扩展欧几里得算法返回(g, x, y)使得 a*x b*y g def extended_gcd(a, b): if b 0: return a, 1, 0 g, x1, y1 extended_gcd(b, a % b) x y1 y x1 - (a // b) * y1 return g, x, y # 求 a 在模 m 下的逆元 def mod_inverse(a, m): g, x, _ extended_gcd(a, m) if g ! 1: raise ValueError(逆元不存在) return x % m这段代码是RSA私钥导出的关键。extended_gcd递归地计算出贝祖系数mod_inverse将其结果归一化到非负。实际工程中生成私钥前要保证e与φ(n)互质否则此函数会抛异常。软考里经常给p、q和e让你求d用的就是这个算法。2.2 Python大整数支持与快速幂取模实现Python的整数是任意精度的这为RSA提供了天然的沃土。但直接写pow(m, e, n)已经是Python解释器底层优化的快速幂了。如果想自己实现快速幂的核心是二进制分解指数。# 快速幂取模计算 base^exp % mod def fast_pow(base, exp, mod): result 1 base base % mod while exp 0: if exp 1: # 当前位是1 result (result * base) % mod base (base * base) % mod exp 1 return resultfast_pow的时间复杂度为O(log exp)空间O(1)。注意每次乘法后立即取模避免大整数无限膨胀。虽然Python内置的pow(base, exp, mod)自带该优化但自己实现一遍能看清指数的二进制遍历过程。在生成长度超过2048位的密钥时模幂运算占了整个密钥生成时间的90%以上。2.3 生成RSA密钥对从随机素数到公私钥的结构生成密钥对的第一步是找到两个大素数。不能直接随机取整数再试除那样效率太低。常见做法是生成随机奇数然后用Miller-Rabin素性检测。import random # Miller-Rabin素性测试测试次数k越高越可靠 def is_prime(n, k40): if n 2: return False for p in [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]: if n % p 0: return n p # 将 n-1 写成 2^r * d 形式 d n - 1 r 0 while d % 2 0: d // 2 r 1 # 进行k轮测试 for _ in range(k): a random.randrange(2, n - 1) x pow(a, d, n) if x 1 or x n - 1: continue for _ in range(r - 1): x (x * x) % n if x n - 1: break else: return False return True # 生成指定位数的大素数 def generate_prime(bits1024): while True: candidate random.getrandbits(bits) candidate | (1 bits - 1) | 1 # 保证最高位和最低位为1 if is_prime(candidate): return candidate这里用固定小素数试除排除掉大部分合数再用Miller-Rabin做概率检测。getrandbits生成指定比特位的随机数|操作确保最高位为1让素数满足要求的二进制位数最低位为1保证是奇数。k40使得误判概率低于2^-80足够工程使用。接下来构造密钥对def generate_keypair(bits1024, e65537): p generate_prime(bits // 2) q generate_prime(bits // 2) n p * q phi (p - 1) * (q - 1) # e固定为65537因为它是费马素数且二进制中只有两个1运算快 if extended_gcd(e, phi)[0] ! 1: # 极低概率下e与phi不互质重新生成p,q return generate_keypair(bits, e) d mod_inverse(e, phi) return (n, e), (n, d)e65537是业界标准选择安全性好且模幂计算快。d是e的逆元长度与n相当。注意私钥里n是公开的真正保密的是d。实际PKCS#1规范中私钥还需要保存p, q, d mod (p-1), d mod (q-1)等参数用于中国剩余定理CRT加速解密这里先不强求。3. 用Python完成RSA加解密与签名验签从原理到可运行代码3.1 PKCS#1 v1.5填充与OAEP填充的选择裸RSA加密输入消息m必须小于n并且需要让m与n互质。直接使用m^e mod n有潜在的语义安全隐患比如选择明文攻击和填充预言攻击。实际必须采用填充方案。PKCS#1 v1.5是最老的方案结构简单但存在Bleichenbacher攻击风险。OAEPOptimal Asymmetric Encryption Padding更安全随机化使得相同明文每次加密结果不同。填充方案格式安全性编码效率Python标准库支持裸RSAm很低100%powPKCS#1 v1.50x00 0x02 随机非零字节 0x00 m对预言攻击脆弱约85%Crypto.Cipher.PKCS1_v1_5OAEP0x00 随机掩码 0x00 m高CPA安全约65%Crypto.Cipher.PKCS1_OAEP工程上优先用OAEP。但为了理解算法下面先实现裸RSA加解密然后给出用标准库安全填充的写法。3.2 加密解密的核心实现与参数解析加密就是把消息当作大整数计算c m^e mod n。解密则是m c^d mod n。class RSA: def __init__(self, keypair): self.n, self.e_or_d keypair # 公钥加密 def encrypt(self, message_int): return pow(message_int, self.e_or_d, self.n) # 私钥解密 def decrypt(self, cipher_int): return pow(cipher_int, self.e_or_d, self.n)pow是优化的模幂内部用蒙哥马利约减。如果把私钥对象也传入解密同样适用。但这里有个性能问题直接使用d做模幂比使用CRT慢约4倍。在后面章节会优化。实际传递消息时需要把字节串转为整数。标准做法是把bytes转换成int并注意大小端。下面代码演示def bytes_to_int(data: bytes) - int: return int.from_bytes(data, byteorderbig) def int_to_bytes(num: int) - bytes: length (num.bit_length() 7) // 8 return num.to_bytes(length, byteorderbig)注意to_bytes必须指定长度否则会报错int too big to convert。如果num为0长度要设为1。3.3 签名与验签RSA在数字签名中的应用签名本质上是私钥加密私钥的幂次运算验签是公钥幂次运算。为了保证不可伪造签名必须对消息的哈希值进行而不是直接对原文。一个简洁的实现import hashlib def sign(private_key, message: bytes) - int: h int.from_bytes(hashlib.sha256(message).digest(), byteorderbig) n, d private_key return pow(h, d, n) def verify(public_key, message: bytes, signature: int) - bool: h int.from_bytes(hashlib.sha256(message).digest(), byteorderbig) n, e public_key h_prime pow(signature, e, n) return h h_prime这里直接用SHA-256摘要作为“消息代表”。但正规的PKCS#1 v1.5签名需要添加DigestInfo前缀OAEP也需要特定构造。上面的代码只适合理解原理不能用于生产。在软考的计算题中经常要求计算消息的RSA签名考生先算哈希再模幂本质上就是这样。4. RSA实现中的性能优化、安全性加固与常见报错排查4.1 大数运算的性能瓶颈用模幂加速与CRT优化如果私钥只保存(n,d)解密时的模幂指数长度和模数长度都是2048位需要约300万次乘法。用中国剩余定理CRT可以显著加速。私钥额外保存p、q、dpd mod (p-1)、dqd mod (q-1)、qinvq^(-1) mod p。解密时def decrypt_crt(c, p, q, dp, dq, qinv): m1 pow(c, dp, p) m2 pow(c, dq, q) h (qinv * (m1 - m2)) % p return m2 q * hCRT将模从n降到p和q大小各为一半指数也缩短计算量约为原始的1/4。OpenSSL默认使用CRT。如果你要自己实现RSA私钥的保存格式需要写入这五个参数。4.2 安全加固防时序攻击、抗小指数攻击RSA对侧信道攻击敏感。一个经典的错误是在模幂运算中当某一位为0时跳过乘法操作攻击者可以通过测量时间恢复私钥位。上面的fast_pow里if exp 1就存在这个隐患。生产环境里必须使用常数时间算法或者依赖硬件。另一种隐患是小指数攻击。当多个用户共享指数e但模数不同时可以通过广播攻击解密相同消息。解决办法是使用随机填充OAEP确保相同明文每次加密后的密文不同。千万不要自己把消息补零就直接传。4.3 常见报错与处理rsa public key not find、分段加密等在实际开发中rsa public key not find这个错误通常出现在加载密钥时公钥文件格式不对。Python的rsa库读取公钥要区分PEM和DER格式import rsa # 正确加载方式 with open(public.pem, rb) as f: pub_key rsa.PublicKey.load_pkcs1_openssl_pem(f.read()) # 错误示例直接传字符串导致r rsa.PublicKey.load_pkcs1(result, formatPEM) 找不到另一个高频问题是“RSA加密长度限制”。标准RSA一次能加密的消息长度与密钥长度和填充有关。以2048位密钥和OAEP填充为例最大消息长度为(2048/8) - 2*hash_len - 2SHA-256时为190字节。超过后需要分段加密。def encrypt_long_message(public_key, message: bytes, chunk_size190): ciphertext b for i in range(0, len(message), chunk_size): chunk message[i:ichunk_size] ciphertext rsa.encrypt(chunk, public_key) # 实际使用OAEP return ciphertext如果你看到C#代码里用RSA.Encrypt做分段那只是一次加密一块的包装并没有改变RSA的明文上限。Python和C#互操作时还要注意字节序和填充方式的匹配常见错误是Python默认PKCS#1 v1.5而C#用OAEP两边解不开。5. 实战用Python写一个最小可用的RSA文件加密工具5.1 需求与设计我们要写一个命令行工具对文件进行混合加密随机生成AES密钥用RSA加密AES密钥再用AES加密文件内容。这样既保留非对称密钥分发的便利又获得对称算法的高效。工具包含两个命令encrypt和decrypt。密钥使用之前手动生成的RSA密钥对但为了可靠性这里用cryptography库生成和管理密钥文件。5.2 关键代码实现import os from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes def encrypt_file(public_key_path, input_file, output_file): # 加载公钥 with open(public_key_path, rb) as f: pub serialization.load_pem_public_key(f.read()) # 生成AES密钥和IV aes_key os.urandom(32) iv os.urandom(16) # RSA加密AES密钥 enc_key pub.encrypt( aes_key, padding.OAEP(mgfpadding.MGF1(algorithmhashes.SHA256()), algorithmhashes.SHA256(), labelNone) ) # AES-CTR模式加密文件 cipher Cipher(algorithms.AES(aes_key), modes.CTR(iv)) encryptor cipher.encryptor() with open(input_file, rb) as f_in, open(output_file, wb) as f_out: # 写入加密后的AES密钥长度和数据 f_out.write(len(enc_key).to_bytes(4, big)) f_out.write(enc_key) f_out.write(iv) while chunk : f_in.read(65536): f_out.write(encryptor.update(chunk)) f_out.write(encryptor.finalize())代码里先写一个4字节的密钥长度接着写RSA密文的AES密钥和IV然后分块写入数据密文。解密时逆向读取即可。注意CTR模式不需要填充天然支持流式。5.3 验证与测试方法怎么确认这个工具正确先生成一个2048位密钥然后用它加密一个文本文件再解密比对哈希openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048 openssl rsa -in private.pem -pubout -out public.pem python file_crypto.py encrypt public.pem message.txt encrypted.bin python file_crypto.py decrypt private.pem encrypted.bin decrypted.txt sha256sum message.txt decrypted.txt两个哈希一致就通过。再去测试边界情况加密一个空文件验证解密结果为空加密一个大文件比如100MB观察内存占用稳定在几个MB。还可以故意篡改RSA密文的一个字节解密时应该立刻抛异常——因为OAEP填充会校验失败。有了这个工具你已经把RSA算法从数学公式变成了可交付的生产技能。本文还有配套的精品资源点击获取
返回列表