1. 项目概述:为什么我们需要亲手实现一个RSA工具?
如果你在开发中遇到过“Navicat 15 RSA public key not find”这样的报错,或者在调试某个服务时发现它只支持RSA密钥交换,又或者你只是想搞明白CTF题目里那些RSA挑战到底在玩什么花样,那你来对地方了。网上确实有很多在线的RSA加解密工具,点几下按钮就能出结果,非常方便。但作为一个开发者,仅仅会用工具是远远不够的。当你遇到一个加密的配置文件、一个需要签名的API请求,或者一个需要逆向分析的二进制文件(比如有人想修改Beyond Compare内置的RSA密钥),如果不懂背后的原理和实现细节,你连问题出在哪里都摸不着头脑。
这个项目,就是带你从零开始,用代码“手搓”一个完整的RSA加解密工具。我们不止于调用库函数,而是要深入理解从密钥生成、加密解密到数字签名的每一个步骤。你会明白为什么openssl rsa -in private.key -pubout -out public.pem这条命令能工作,也会知道当遇到“public key not find”时,除了检查文件路径,更应该检查密钥的格式和内容。通过这个实践,RSA对你而言将不再是一个黑盒,而是一套清晰、可操控的数学流程。无论是为了加深理解、应对面试,还是解决实际工作中棘手的加密问题,这都将是一次极有价值的投资。
2. RSA算法核心原理拆解:不只是数学公式
在动手写代码之前,我们必须把RSA的“发动机”拆开看明白。很多人觉得RSA就是一串复杂的数学,但实际上,它的核心思想非常优雅,理解了这一点,后续的所有操作都会变得顺理成章。
2.1 密钥对生成的数学舞台:欧拉函数与模逆元
RSA的安全基石建立在“大数分解难题”上。简单说,给你两个很大的质数p和q,把它们乘起来得到N很容易;但反过来,给你一个巨大的N,让你找出它是由哪两个质数相乘得到的,以目前计算机的计算能力,在有限时间内几乎不可能完成。我们的整个加密体系,就构建在这个“正向简单、逆向极难”的不对称性上。
密钥生成的具体步骤如下,我会用一个非常小的、不安全的数字来演示,以便理解:
- 选择两个不相等的质数p和q。例如,p=61, q=53。
- 计算模数N。
N = p * q = 61 * 53 = 3233。这个N的长度(二进制位数)就是常说的密钥长度,比如2048位。N会被公开,它是公钥和私钥的一部分。 - 计算欧拉函数φ(N)。对于两个质数的情况,
φ(N) = (p-1) * (q-1) = 60 * 52 = 3120。这个φ(N)必须被严格保密,因为它直接关联到私钥。 - 选择一个公钥指数e。e需要是一个整数,且满足两个条件:
1 < e < φ(N),并且e与φ(N)互质(即最大公约数gcd(e, φ(N)) = 1)。通常我们选择65537 (0x10001),因为它二进制表示中1很少,计算效率高,且足够大以保证安全。这里我们选e=17。 - 计算私钥指数d。d是e关于模φ(N)的模逆元。也就是说,d需要满足:
(e * d) % φ(N) = 1。计算d需要使用扩展欧几里得算法。当e=17, φ(N)=3120时,我们可以计算出d=2753(因为17 * 2753 = 46801, 46801 % 3120 = 1)。
至此,我们得到了:
- 公钥:由
(N, e)组成,即 (3233, 17)。 - 私钥:由
(N, d)组成,即 (3233, 2753)。
注意:在实际应用中,p和q在生成后应立即从内存中安全擦除,绝不应该和私钥一起存储。私钥文件里通常只保存
(N, d),有时也会包含p, q, dmp1, dmq1, iqmp等用于中国剩余定理(CRT)加速运算的中间值,但这些都是从p, q派生出来的。
2.2 加密与解密的本质:模幂运算
有了公钥和私钥,加解密过程本质上就是模幂运算。
- 加密:假设明文数据(已经过适当填充,比如PKCS#1 v1.5)对应的整数是
m(必须满足0 <= m < N)。加密过程就是计算密文c = m^e mod N。用公钥(N, e)计算。 - 解密:收到密文
c后,用私钥(N, d)计算明文m = c^d mod N。
为什么这样就能恢复明文?这源于欧拉定理。因为e * d ≡ 1 (mod φ(N)),所以c^d ≡ (m^e)^d ≡ m^(e*d) ≡ m^(k*φ(N)+1) ≡ m (mod N)。这个数学魔法保证了正确性。
这里的关键操作是“模幂运算”,即计算a^b mod n。直接先计算a^b再取模是不可行的,因为a^b会是一个天文数字。必须使用快速模幂算法(如平方-乘算法),在计算过程中不断取模,让中间结果始终保持在一个可控的大小。
2.3 为什么需要填充?裸RSA的危险性
上面描述的c = m^e mod N是“教科书式RSA”或“裸RSA”。它在现实中是极其危险的,存在多种攻击方式(如明文猜测攻击、共模攻击等)。因此,在实际使用前,明文m必须经过一个“填充”步骤,将其转换为一个符合特定结构、具有随机性的、接近N大小的整数。
最常见的填充方案是PKCS#1 v1.5和OAEP。
- PKCS#1 v1.5:结构相对简单,在历史上被广泛使用。它的格式大致是:
0x00 || 0x02 || 随机非零填充字节串 || 0x00 || 原始明文。这种填充提供了随机性,但若实现不当,可能受到Bleichenbacher攻击。 - OAEP:更安全、更复杂的填充方案,结合了哈希函数和掩码生成函数,能提供“选择密文攻击安全性”。现代应用推荐使用OAEP。
当你使用OpenSSL命令行或库函数时,默认通常会使用PKCS#1 v1.5填充。这也是为什么直接对原始字节串进行加密往往会失败的原因——你必须先正确填充。
3. 核心工具链选型与设计思路
理解了原理,我们就要选择实现的武器。我们的目标不是重新发明轮子去实现大数运算和质数生成,而是利用成熟的库,专注于理解流程、处理边界情况和应对实际问题。
3.1 编程语言与密码学库的选择
对于此类工具,Python是绝佳的选择。它语法简洁,拥有强大的密码学库,能让我们快速聚焦于逻辑而非底层细节。核心库是cryptography。
为什么不选pycryptodome或rsa库?cryptography是当前Python生态中维护最积极、API设计更现代、且背后有专业安全团队支持的库。它提供了高层次和低层次的API,既能满足我们“手动”组装流程的学习目的,也能用于生产环境。
# 安装必备库 pip install cryptography3.2 工具的功能模块设计
我们的工具将包含以下核心模块,模拟一个完整的工作流:
- 密钥生成模块:生成指定长度(如2048位)的RSA密钥对,并支持导出为PEM格式(这是遇到“public key not find”错误时最需要检查的格式)。
- 加密模块:支持对文件或直接输入的字符串进行RSA加密,支持选择PKCS#1 v1.5或OAEP填充。
- 解密模块:对应地,对密文进行解密。
- 签名与验证模块:展示RSA如何用于数字签名(这是另一个重要用途)。
- 密钥格式解析模块:这是一个实战关键。能够读取、解析PEM格式的密钥文件,提取出N, e, d等核心参数。这对于调试、理解现有密钥文件、或者解决格式兼容性问题(如Navicat报错)至关重要。
3.3 处理“Navicat RSA Public Key Not Find”类问题的思路
这个常见错误通常有几个原因:
- 文件路径错误:最基础的问题。
- 密钥格式不正确:Navicat可能期望特定格式的PEM公钥(例如,以
-----BEGIN PUBLIC KEY-----开头)。你生成的公钥可能是PKCS#1格式(-----BEGIN RSA PUBLIC KEY-----),而它需要的是PKCS#8格式,反之亦然。 - 密钥内容损坏或不完整:PEM文件是Base64编码的,如果复制粘贴时少了换行符或多了空格,解码就会失败。
- 非RSA密钥:误用了DSA或ECC的密钥。
我们的工具将包含一个“密钥诊断”功能,尝试加载PEM文件并打印其类型和关键参数,这能快速定位上述2、3、4点问题。
4. 分步实现:从密钥生成到完整加解密
现在,让我们开始写代码。我会将关键代码嵌入讲解中,并解释每一行背后的意图。
4.1 生成RSA密钥对并导出
我们首先生成一个2048位的密钥对。在实际中,2048位是目前安全与性能平衡的主流选择。4096位更安全,但加解密速度会慢不少。
from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization def generate_rsa_keypair(key_size=2048): """ 生成RSA私钥和公钥对。 参数: key_size: 密钥长度,单位是比特。推荐2048或以上。 返回: private_key: 私钥对象 public_key: 公钥对象 """ # 生成私钥。public_exponent=65537是标准且安全的选择。 private_key = rsa.generate_private_key( public_exponent=65537, key_size=key_size, ) # 从私钥导出公钥 public_key = private_key.public_key() return private_key, public_key # 生成密钥对 private_key, public_key = generate_rsa_keypair(2048) print("密钥对生成成功!") # 将私钥以PEM格式保存到文件 pem_private = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, # 使用PKCS#8格式,兼容性更好 encryption_algorithm=serialization.NoEncryption() # 不加密私钥,生产环境请使用Password加密! ) with open('private_key.pem', 'wb') as f: f.write(pem_private) print("私钥已保存为 private_key.pem") # 将公钥以PEM格式保存到文件 pem_public = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo # 标准的PKCS#8公钥格式 ) with open('public_key.pem', 'wb') as f: f.write(pem_public) print("公钥已保存为 public_key.pem")实操心得:保存私钥时,
serialization.NoEncryption()意味着私钥文件是明文存储的,这非常不安全,仅用于学习和测试。在生产环境中,务必使用serialization.BestAvailableEncryption(b'your-password')来用密码加密私钥文件。这也是为什么你从服务器上下载的.pem文件有时需要输入密码才能使用的原因。
4.2 实现加密与解密功能
接下来,我们实现使用公钥加密、私钥解密的功能。这里以更安全的OAEP填充为例。
from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import hashes def rsa_encrypt(public_key, plaintext): """ 使用公钥和OAEP填充加密数据。 参数: public_key: 公钥对象 plaintext: 字节串类型的明文 返回: ciphertext: 字节串类型的密文 """ # RSA加密有长度限制。对于OAEP with SHA-256,最大明文长度 = 密钥字节数 - 2*哈希输出字节数 - 2 # 对于2048位密钥(256字节),约等于 256 - 2*32 - 2 = 190字节。 # 如果要加密更长的数据,需要采用“混合加密”:用RSA加密一个随机的对称密钥,再用该对称密钥加密数据。 if len(plaintext) > 190: # 粗略判断,实际应精确计算 raise ValueError("明文过长,请使用混合加密或分段加密。") ciphertext = public_key.encrypt( plaintext, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None # 通常为None ) ) return ciphertext def rsa_decrypt(private_key, ciphertext): """ 使用私钥解密OAEP填充加密的数据。 参数: private_key: 私钥对象 ciphertext: 字节串类型的密文 返回: plaintext: 字节串类型的明文 """ plaintext = private_key.decrypt( ciphertext, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None ) ) return plaintext # 示例:加密一个字符串 message = b"This is a secret message for RSA-OAEP encryption." ciphertext = rsa_encrypt(public_key, message) print(f"加密后的密文 (Hex): {ciphertext.hex()}") # 示例:解密 decrypted_message = rsa_decrypt(private_key, ciphertext) print(f"解密后的明文: {decrypted_message.decode()}")4.3 加载现有密钥文件并解析
这是解决实际问题的关键。我们经常需要加载已有的密钥文件。
def load_key_from_file(key_file_path, is_private=True, password=None): """ 从PEM文件加载RSA密钥。 参数: key_file_path: 密钥文件路径 is_private: 是否为私钥 password: 私钥的密码(如果有的话) 返回: key_object: 加载的密钥对象 """ with open(key_file_path, 'rb') as key_file: key_data = key_file.read() if is_private: key = serialization.load_pem_private_key(key_data, password=password) else: key = serialization.load_pem_public_key(key_data) return key def inspect_key(key): """ 检查密钥的详细信息,用于调试。 """ if isinstance(key, rsa.RSAPrivateKey): print("密钥类型: 私钥") private_numbers = key.private_numbers() public_numbers = key.public_key().public_numbers() print(f"模数 N (hex): {hex(public_numbers.n)}") print(f"公钥指数 e: {public_numbers.e}") print(f"私钥指数 d (hex): {hex(private_numbers.d)}") # 注意:p, q等敏感信息仅在需要时获取 # print(f"质数 p (hex): {hex(private_numbers.p)}") # print(f"质数 q (hex): {hex(private_numbers.q)}") elif isinstance(key, rsa.RSAPublicKey): print("密钥类型: 公钥") public_numbers = key.public_numbers() print(f"模数 N (hex): {hex(public_numbers.n)}") print(f"公钥指数 e: {public_numbers.e}") else: print("未知的密钥类型") # 示例:加载并检查我们刚才生成的公钥文件 loaded_public_key = load_key_from_file('public_key.pem', is_private=False) inspect_key(loaded_public_key)通过inspect_key函数,你可以清晰地看到密钥的核心参数。如果你遇到一个“not found”错误,可以先用这个工具检查一下目标密钥文件是否能被成功加载、类型是否正确、N和e的值是否正常。这能立刻排除掉一大半的格式或内容问题。
5. 数字签名与验证的实现
RSA另一个核心用途是数字签名,用于验证数据的完整性和来源。其原理是“用私钥加密(签名),用公钥解密(验证)”,但实际上是对数据的哈希值进行操作。
def rsa_sign(private_key, data): """ 使用私钥对数据进行PKCS#1 v1.5签名。 参数: private_key: 私钥对象 data: 需要签名的原始数据(字节串) 返回: signature: 签名(字节串) """ # 先计算数据的哈希值 hasher = hashes.Hash(hashes.SHA256()) hasher.update(data) digest = hasher.finalize() # 对哈希值进行签名 signature = private_key.sign( digest, padding.PKSS1v15(), # 签名常用PKCS#1 v1.5填充 hashes.SHA256() ) return signature def rsa_verify(public_key, data, signature): """ 使用公钥验证签名。 参数: public_key: 公钥对象 data: 原始数据(字节串) signature: 待验证的签名(字节串) 返回: is_valid: 布尔值,签名是否有效 """ # 计算数据的哈希值 hasher = hashes.Hash(hashes.SHA256()) hasher.update(data) digest = hasher.finalize() try: # 验证签名 public_key.verify( signature, digest, padding.PKSS1v15(), hashes.SHA256() ) return True except Exception as e: # 通常是InvalidSignature异常 print(f"签名验证失败: {e}") return False # 示例:签名与验证 data_to_sign = b"Important contract content." signature = rsa_sign(private_key, data_to_sign) print(f"生成的签名 (Hex): {signature.hex()}") is_verified = rsa_verify(public_key, data_to_sign, signature) print(f"签名验证结果: {'成功' if is_verified else '失败'}") # 尝试篡改数据后验证 tampered_data = b"Important contract content!" is_verified_tampered = rsa_verify(public_key, tampered_data, signature) print(f"篡改后签名验证结果: {'成功' if is_verified_tampered else '失败'}")6. 实战问题排查与深度解析
掌握了基础功能,我们来看看如何用这些知识解决开头提到的那些热搜词背后的实际问题。
6.1 诊断“Navicat 15 RSA Public Key Not Find”
假设你从服务器拿到了一个id_rsa.pub文件,但Navicat说找不到公钥。你可以用我们的工具这样排查:
def diagnose_public_key_file(file_path): print(f"\n诊断文件: {file_path}") try: with open(file_path, 'r') as f: content = f.read().strip() print("文件内容预览(前100字符):", content[:100]) # 检查文件头 if content.startswith('-----BEGIN OPENSSH PUBLIC KEY-----'): print("警告:这是OpenSSH格式的公钥,Navicat可能不支持。需要转换为PEM格式。") print("转换命令: ssh-keygen -f id_rsa.pub -e -m pem > id_rsa_pem.pub") elif content.startswith('-----BEGIN RSA PUBLIC KEY-----'): print("信息:这是PKCS#1格式的RSA公钥。") print("Navicat可能需要PKCS#8格式。可以尝试用OpenSSL转换:") print(" openssl rsa -RSAPublicKey_in -in id_rsa.pub -pubout -out id_rsa_pkcs8.pub") elif content.startswith('-----BEGIN PUBLIC KEY-----'): print("信息:这是PKCS#8格式的公钥,应该是Navicat支持的格式。") # 尝试加载 key = load_key_from_file(file_path, is_private=False) inspect_key(key) print("密钥加载成功,格式正确。请检查Navicat连接配置中的文件路径。") else: print("错误:无法识别的文件格式。可能不是PEM格式的公钥文件。") print("请确认文件内容是否正确,是否包含完整的BEGIN/END标记。") except Exception as e: print(f"读取或解析文件时发生错误: {e}") # 假设你有一个有问题的公钥文件 # diagnose_public_key_file('problematic_key.pub')这个诊断脚本能帮你快速识别密钥格式问题,这是解决此类报错的第一步,也是最关键的一步。
6.2 理解并操作OpenSSL RSA命令
热搜词里出现了rsa -in private.key -pubout -out public.pem。我们的Python代码在底层做的事情和这条OpenSSL命令是等效的。这条命令的意思是:从私钥文件private.key中提取出公钥部分,并以PEM格式输出到public.pem。我们的public_key.public_bytes(...)函数调用就是在做“提取并编码”这件事。
反过来,如果你有一个PKCS#1格式的公钥(-----BEGIN RSA PUBLIC KEY-----),想转换成PKCS#8格式,OpenSSL命令是:
openssl rsa -RSAPublicKey_in -in public_pkcs1.pem -pubout -out public_pkcs8.pem在我们的Python工具里,当你用serialization.load_pem_public_key加载一个PKCS#1公钥时,cryptography库通常会内部处理并返回一个对象,当你再用public_bytes(..., format=SubjectPublicKeyInfo)导出时,自然就得到了PKCS#8格式。这体现了使用高级库的便利性。
6.3 应对CTF中的RSA挑战(如[RoarCTF2019]RSA, BUUCTF RSA)
CTF中的RSA题目往往不是考你如何调用API,而是考你对算法脆弱点的理解。我们的工具可以作为辅助分析的手段。例如:
- 已知N和e,求d:这需要分解N。我们的工具无法直接分解大整数(这正是RSA安全的基础),但题目常会给出一些提示,比如N很小(可以用网站或工具分解),或者p和q很接近(可以用费马分解法)。你可以将分解得到的p和q,结合我们的密钥解析函数,手动计算出d。
- 共模攻击:如果相同的明文m,用相同的N但不同的e1, e2加密,且e1和e2互质,则可以恢复m。这需要用到扩展欧几里得算法,我们的工具可以作为验证解密结果的工具。
- 低加密指数攻击:如果e很小(比如3),且明文m也很小,使得
m^e < N,那么加密过程c = m^e mod N就等于m^e(因为没超过N,取模无效果)。此时直接对c开e次方根即可得到m。我们的工具可以方便地计算大整数运算。
实现一个辅助函数来处理这类情况:
import math from sympy import integer_nthroot # 需要安装 sympy: pip install sympy def low_exponent_attack(ciphertext, e): """ 尝试低加密指数攻击。 仅当 m^e < N 时有效,即 ciphertext 本身没有经过取模运算。 """ # 尝试开e次方根 m, is_exact = integer_nthroot(ciphertext, e) if is_exact: # 将整数m转换为字节串(需要处理可能的填充) # 注意:这里假设m直接对应明文字节,实际情况可能包含填充,需要进一步解析。 byte_length = (m.bit_length() + 7) // 8 potential_plaintext = m.to_bytes(byte_length, 'big') return potential_plaintext else: return None # 示例:假设在CTF中拿到一个很小的密文c,且知道e=3 # c = 123456789 # 示例值 # result = low_exponent_attack(c, 3) # if result: # print(f"潜在明文: {result}")6.4 关于“修改Beyond Compare可执行文件中内置的RSA密钥”
这是一个非常具体的逆向工程/软件安全场景。其核心思路是:
- 定位:使用反汇编工具(如IDA Pro, Ghidra)或二进制字符串搜索,在可执行文件中找到存储公钥(可能是PEM或DER格式)或模数N的硬编码数据。
- 替换:生成一对你自己的RSA密钥对。用你公钥的二进制数据(可能是DER编码)替换掉程序中原有的公钥数据。这需要精确了解原程序的存储格式和长度,新公钥的DER编码长度必须与原来的完全一致,否则会破坏程序结构导致崩溃。
- 签名:程序可能会用内置的私钥对某些数据(如许可证文件)进行签名。如果你替换了公钥,那么对应的签名也必须用你自己的私钥重新生成,并替换掉程序中或相关数据文件中的旧签名。
这个过程高度依赖于目标程序的具体实现,我们的RSA工具在这里的作用是:生成新密钥对、将公钥导出为特定的DER格式、以及对需要的数据用新私钥进行签名。这属于深度定制化的应用,需要扎实的逆向工程功底。
7. 性能优化与安全注意事项
在实战中,我们不能只关注功能,还要考虑效率和安全性。
7.1 使用中国剩余定理加速解密
RSA私钥操作(解密、签名)是计算密集型操作,因为私钥指数d通常很大。一个重要的优化是使用中国剩余定理。私钥除了(N, d),还可以保存p, q, d mod (p-1), d mod (q-1), q^(-1) mod p这些值。这样可以将一次模N的大指数运算,分解为两次模p和模q的较小指数运算,最后再合成结果,速度可以提升3-4倍。
cryptography库在底层已经自动使用了CRT优化。当你从PEM文件加载一个包含这些额外参数的私钥时,解密速度会更快。这也是为什么完整的私钥PEM文件比只包含(N, d)的文件要大的原因。
# 检查私钥是否包含CRT参数 private_numbers = private_key.private_numbers() if hasattr(private_numbers, 'p'): print("私钥包含CRT参数,解密/签名已优化。")7.2 处理长明文:混合加密方案
如前所述,RSA能直接加密的数据长度受限于密钥大小和填充方案。要加密一个大文件,标准的做法是:
- 生成一个随机的对称密钥(比如AES-256密钥)。
- 用这个对称密钥加密文件数据(AES速度很快)。
- 用RSA公钥加密这个对称密钥。
- 将“RSA加密后的对称密钥”和“AES加密后的文件数据”一起发送或存储。
解密时,先用RSA私钥解密出对称密钥,再用对称密钥解密文件数据。我们的工具可以扩展出这个“封装/解封”功能。
7.3 关键安全准则
- 密钥长度:绝对不要使用1024位以下的RSA密钥。当前最低标准是2048位,对于需要长期安全的数据,建议使用3072或4096位。
- 私钥保护:私钥是王冠上的宝石。必须使用强密码加密存储(
BestAvailableEncryption)。在代码中,避免硬编码私钥。在服务器上,使用密钥管理服务或硬件安全模块。 - 填充方案:永远不要使用“教科书式RSA”(无填充)。对于加密,优先使用OAEP填充。对于签名,可以使用PSS填充(比PKCS#1 v1.5更安全)或PKCS#1 v1.5。
- 随机性:密钥生成、OAEP填充中的随机字节等,都必须使用密码学安全的随机数生成器(CSPRNG)。
cryptography库默认会处理好。 - 库的更新:始终使用最新版本的密码学库,以获取安全补丁。
8. 将工具封装为命令行程序
最后,让我们把上面的功能整合成一个方便的命令行工具,比如叫rsa_tool.py。
# rsa_tool.py import argparse import sys from pathlib import Path # ... 导入之前定义的所有函数 ... def main(): parser = argparse.ArgumentParser(description='RSA加解密与密钥管理工具') subparsers = parser.add_subparsers(dest='command', help='子命令') # 生成密钥对 parser_gen = subparsers.add_parser('generate', help='生成RSA密钥对') parser_gen.add_argument('-s', '--size', type=int, default=2048, help='密钥长度(比特),默认2048') parser_gen.add_argument('-p', '--private', default='private.pem', help='私钥输出文件名') parser_gen.add_argument('-u', '--public', default='public.pem', help='公钥输出文件名') # 加密 parser_enc = subparsers.add_parser('encrypt', help='使用公钥加密文件或字符串') parser_enc.add_argument('-k', '--key', required=True, help='公钥文件路径') parser_enc.add_argument('-i', '--input', help='输入文件路径。若不指定,则从标准输入读取字符串') parser_enc.add_argument('-o', '--output', help='输出文件路径。若不指定,输出到标准输出(十六进制)') parser_enc.add_argument('--padding', choices=['oaep', 'pkcs1'], default='oaep', help='填充方案') # 解密 parser_dec = subparsers.add_parser('decrypt', help='使用私钥解密文件') parser_dec.add_argument('-k', '--key', required=True, help='私钥文件路径') parser_dec.add_argument('--password', help='私钥密码(如果需要)') parser_dec.add_argument('-i', '--input', required=True, help='输入密文文件路径') parser_dec.add_argument('-o', '--output', help='输出明文文件路径。若不指定,输出到标准输出') parser_dec.add_argument('--padding', choices=['oaep', 'pkcs1'], default='oaep', help='填充方案') # 检查密钥 parser_inspect = subparsers.add_parser('inspect', help='检查密钥文件信息') parser_inspect.add_argument('key_file', help='密钥文件路径') parser_inspect.add_argument('--private', action='store_true', help='强制按私钥解析') args = parser.parse_args() if args.command == 'generate': priv, pub = generate_rsa_keypair(args.size) # ... 保存密钥 ... print(f"密钥对已生成。私钥: {args.private}, 公钥: {args.public}") elif args.command == 'encrypt': pub_key = load_key_from_file(args.key, is_private=False) # ... 读取输入,加密,写入输出 ... elif args.command == 'decrypt': priv_key = load_key_from_file(args.key, is_private=True, password=args.password.encode() if args.password else None) # ... 读取输入,解密,写入输出 ... elif args.command == 'inspect': try: # 先尝试按公钥加载 key = load_key_from_file(args.key, is_private=False) except: try: # 如果失败,尝试按私钥加载 key = load_key_from_file(args.key, is_private=True) except Exception as e: print(f"无法加载密钥文件: {e}") sys.exit(1) inspect_key(key) else: parser.print_help() if __name__ == '__main__': main()这样,你就可以在终端里使用诸如python rsa_tool.py generate -s 2048、python rsa_tool.py encrypt -k public.pem -i message.txt -o encrypted.bin、python rsa_tool.py inspect suspicious_key.pem这样的命令了,非常方便。
走到这里,你已经不仅仅是一个RSA工具的使用者,而是成为了一个理解其内部机理,并能动手构建、诊断和解决实际问题的实践者。下次再遇到“RSA public key not find”,你脑海中会立刻浮现出检查格式、加载解析、验证参数的完整排查路径。面对一个加密数据块,你也能清晰地知道从填充、模幂运算到最终解密的每一步发生了什么。这种从原理到实现,再从实现反哺问题解决能力的闭环,正是深入一个技术领域最有价值的收获。