在Python中用RSA加密文件

17 投票
1 回答
32089 浏览
提问于 2025-04-16 19:20

我正在用RSA算法实现文件加密,使用的是PyCrypto这个库。

我知道这样做有点问题,首先RSA加密速度非常慢,其次PyCrypto的RSA只能加密128个字符,所以你必须把文件分成128个字符的小块来处理。

这是我目前写的代码:

from Crypto.PublicKey import RSA

file_to_encrypt = open('my_file.ext', 'rb').read()
pub_key = open('my_pub_key.pem', 'rb').read()
o = RSA.importKey(pub_key)

to_join = []
step = 0

while 1:
    # Read 128 characters at a time.
    s = file_to_encrypt[step*128:(step+1)*128]
    if not s: break
    # Encrypt with RSA and append the result to list.
    # RSA encryption returns a tuple containing 1 string, so i fetch the string.
    to_join.append(o.encrypt(s, 0)[0])
    step += 1

# Join the results.
# I hope the \r\r\r sequence won't appear in the encrypted result,
# when i explode the string back for decryption.
encrypted = '\r\r\r'.join(to_join)
# Write the encrypted file.
open('encrypted_file.ext', 'wb').write(encrypted)

所以我想问一下:有没有更好的方法可以用公钥/私钥加密文件呢?

我听说过Mcrypt和OpenSSL,但我不知道它们是否能加密文件。

1 个回答

35

公钥加密通常只用于处理少量数据。它的速度比较慢,而且使用起来可能会有点复杂。一般的做法是先用其他方法把不对称加密的问题转化为一个可以用共享密钥来保证安全的问题,然后再用公钥加密来保护这个共享密钥。比如:

  • 要加密一个文件,首先随机生成一个用于块加密或流加密的秘密密钥(例如 AES)。然后用这个加密算法加密数据,同时把用公钥加密的秘密密钥和加密后的数据一起存储。
  • 要给文件签名,先计算一个加密摘要(例如 SHA-256)。然后用私钥对这个文件的摘要进行签名,并把签名和文件一起存储。

下面是加密过程的一个简单示例(注意,这段代码未经测试,是直接在浏览器中输入的):

import os
from Crypto.Cipher import AES
from Crypto.PublicKey import RSA
import Crypto.Util.number
def encrypt_file(rsa, input, output):
    # Generate secret key
    secret_key = os.urandom(16)
    # Padding (see explanations below)
    plaintext_length = (Crypto.Util.number.size(rsa.n) - 2) / 8
    padding = '\xff' + os.urandom(16)
    padding += '\0' * (plaintext_length - len(padding) - len(secret_key))
    # Encrypt the secret key with RSA
    encrypted_secret_key = rsa.encrypt(padding + secret_key, None)
    # Write out the encrypted secret key, preceded by a length indication
    output.write(str(len(encrypted_secret_key)) + '\n')
    output.write(encrypted_secret_key)
    # Encrypt the file (see below regarding iv)
    iv = '\x00' * 16
    aes_engine = AES.new(secret_key, AES.MODE_CBC, iv)
    output.write(aes_engine.encrypt(input.read()))

这里的 iv 是一个初始化向量,用于 CBC 工作模式。每个密钥每条消息都需要一个唯一的初始化向量。通常,它会和数据一起以明文形式发送。在这里,由于密钥只使用一次,所以可以使用一个已知的初始化向量。

块加密的 API 在 PEP 272 中有描述。不幸的是,它只支持一次性加密。对于大文件,逐块加密会更好;你可以一次加密一个块(AES 的块大小是 16 字节),但这需要一个更好的加密库。

需要注意的是,通常不应该直接用 RSA 加密数据。最明显的问题是攻击者知道公钥,因此可以尝试猜测明文(如果攻击者认为明文可能是 swordfish,那么他可以用 RSA 公钥加密 swordfish,并将结果与 RSA 加密的输出进行比较)。另一个问题是,如果你想把文件发送给多个接收者,而 RSA 加密步骤是确定性的,那么攻击者可以通过相同的密文判断明文是相同的。通常的防御方法是使用填充方案,即在明文中添加一些随机的秘密数据;这些数据称为填充。这样,攻击者就无法猜测随机数据,并且每次加密的结果都不同,因为相同的明文不会被加密两次;对于合法的接收者来说,填充数据只是可以丢弃的内容。

在这里,似乎上述问题在这个场景中并不适用。然而,使用未保护的 RSA 仍然可能会出现其他弱点。特别是,如果公钥指数非常小(在这里不是这种情况,因为 PyCrypto 使用的是 65537),或者你为多个不同的接收者加密相同的内容(同样,这里可能也不是这种情况,因为每条消息都有自己的秘密密钥),那么一个简单的数学计算可能会让攻击者恢复 RSA 明文。为了避免这种攻击,使用 RSA 加密的值需要“足够接近”RSA 模数,以便加密操作实际上执行模幂运算。我建议的填充方法确保了这一点,通过使最高位字节为 0xff;这被认为是安全的,不过在现实中你应该使用经过批准的填充模式(OAEP)。

撰写回答