5

我正在尝试在 Python 中实现以下内容: openssl enc -e -aes-256-cbc -base64 -k "Secret Passphrase" -in plaintext.txt -out ciphertext.txt

openssl enc -d -aes-256-cbc -base64 -k "秘密密码" -in ciphertext.txt -out verification.txt

我尝试了几个不同的模块,PyCrypto、M2Crypto 等,但似乎无法获得将密码更改为正确大小的密钥和正确编码所有内容的正确组合。我找到了https://github.com/nvie/SimpleAES但它基本上在命令行上运行 OpenSSL,我宁愿避免。

4

2 回答 2

6

Base 64 编码和解码可以通过标准base64模块轻松处理。

PyCrypto 和 M2Crypto 都支持 CBC 模式下的 AES-256 解密和加密。

唯一非标准(也是最困难的)部分是从密码中推导 IV 和密钥。OpenSSL 通过它自己的EVP_BytesToKey函数来完成它,这在本手册页中有描述。

Python 等价物是:

def EVP_BytesToKey(password, salt, key_len, iv_len):
    """
    Derive the key and the IV from the given password and salt.
    """
    from hashlib import md5
    dtot =  md5(password + salt).digest()
    d = [ dtot ]
    while len(dtot)<(iv_len+key_len):
        d.append( md5(d[-1] + password + salt).digest() )
        dtot += d[-1]
    return dtot[:key_len], dtot[key_len:key_len+iv_len]

其中key_len是 32,iv_len对于 AES-256 是 16。该函数返回可用于解密有效负载的密钥和 IV。

OpenSSL 将盐放在加密负载的前 8 个字节中。

最后,CBC 模式下的 AES 只能处理与 16 字节边界对齐的数据。使用的默认填充是 PKCS#7。

因此,加密的步骤是:

  1. 生成 8 字节的随机数据作为盐。
  2. 使用步骤 1 中的盐从密码中派生 AES 密钥和 IV。
  3. 使用 PKCS#7 填充输入数据。
  4. 使用 CBC 模式下的 AES-256 加密填充,使用步骤 2 中的密钥和 IV。
  5. 在 Base64 中编码并输出步骤 1 中的盐。
  6. 在 Base64 中编码并输出步骤 4 中的加密数据。

解密的步骤是相反的:

  1. 将 Base64 中的输入数据解码为二进制字符串。
  2. 将解码数据的前 8 个字节视为盐。
  3. 使用步骤 1 中的盐从密码中派生 AES 密钥和 IV。
  4. 使用步骤 3 中的 AES 密钥和 IV 解密剩余的解码数据。
  5. 验证并从结果中删除 PKCS#7 填充。
于 2012-12-17T22:12:51.793 回答
1

因为现在 base64 标准已被弃用并且 pbkdf2-hashing 是最先进的,所以答案是正确的,但已经过时了。我正在使用这篇文章,因为它作为 DDG 上的第一个结果弹出。为了让你像我一样花几天时间来解决这个问题,这是我加密数据的工作代码,如 openssl。

以下代码类似于echo <in> | openssl aes-256-cbc -pbkdf2 -k <key> -out <out>

def encrypt(self, password, input):
    bs = AES.block_size
    salt = urandom(bs - len(b'Salted__'))
    pbk = pbkdf2_hmac('sha256', password.encode('utf8'), salt, 10000, 48)
    key = pbk[:32]
    iv = pbk[32:48]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    result = (b'Salted__' + salt)
    finished = False
    while not finished:
        chunk = input.read(1024 * bs).encode()
        if len(chunk) == 0 or len(chunk) % bs != 0:
            padding_length = (bs - len(chunk) % bs) or bs
            chunk += (padding_length * chr(padding_length)).encode()
            finished = True
        result += cipher.encrypt(chunk)
    return result

提及/澄清一些事情:

  • 您需要在哈希的开头添加字节“Salted__”(因此盐只有 8 个字节而不是 16 个字节)
  • iv 派生自 pbkdf2 哈希(您经常会发现人们将 iv 添加到结果中。这会导致在解密结果的开头出现随机的、神秘的字符 - 加密的 iv。它是人类可读的,但请记住它们是随机字节和可以破坏一切!通过密码哈希的推导,一切正常。)
  • openssl 目前的标准值为:md=sha256, iter=10000
  • 您需要填充所有内容,以便加密有效(while 循环)
  • 如果您愿意,您也可以在 base64 中加密所有内容。为此,不要返回 result返回 base64.b64enode(result)。它等于上面添加了-base64标志的 openssl 命令。

要使用 openssl 解密所有内容,请使用以下命令:openssl aes-256-cbc -pbkdf2 -d -k <key> -in <in> -out <out>

于 2021-09-02T08:06:06.363 回答