【问题标题】:Implement OpenSSL AES Encryption in Python在 Python 中实现 OpenSSL AES 加密
【发布时间】:2015-01-29 17:16:45
【问题描述】:

我正在尝试在 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 "Secret Passphrase" -in ciphertext.txt -out verification.txt

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

【问题讨论】:

  • 我也有同样的问题,您最终是否使用以下答案作为您的解决方案? (它还没有被接受为答案,所以我想知道你是否以不同的方式解决了这个问题)
  • 如果我没记错的话,我认为这也行不通。我认为我试图解决的问题是在 Python 和 JS 中使用相同的加密算法,所以我进行了一些调整。相反,我使用了密码学和 Fernet 实现。 cryptography.io/en/latest/fernet 然后在 JS 中是这样的:github.com/csquared/fernet.js

标签: python openssl aes pycrypto m2crypto


【解决方案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 中加密所有内容。为此,不要返回结果返回 base64.b64enode(result)。它等于上面添加了 -base64 标志的 openssl 命令。

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

【讨论】:

  • chunk = input[:1024*bs].encode()改成chunk = input.read(1024 * bs)后生效
  • @vvoody 你是绝对正确的。我之前意识到这个错误,但忘记在这里更改它。现在已更正。谢谢
【解决方案2】:

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

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

唯一非标准(也是最困难的)部分是从密码中推导 IV 和密钥。 OpenSSL 通过它自己的 EVP_BytesToKey 函数来实现这一点,该函数在 in this man page 中进行了描述。

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]

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

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

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

因此,加密的步骤是:

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

解密的步骤是相反的:

  1. 将输入数据从 Base64 解码为二进制字符串。
  2. 将解码数据的前 8 个字节视为盐。
  3. 使用步骤 1 中的 salt 从密码中获取 AES 密钥和 IV。
  4. 使用 AES 密钥和第 3 步中的 IV 解密剩余的解码数据。
  5. 验证并从结果中删除 PKCS#7 填充。

【讨论】:

  • 几乎同意。 openssl enc 格式是 8 字节文字“Salted__”,8 字节盐值,然后是密文,所有 base64 编码为一个单元。将 encrypt #5 和 #6 加上该常数结合起来,将 decrypt #2 更改为 [8:16] 并将 #4 更改为 [16:]。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-21
  • 1970-01-01
  • 1970-01-01
  • 2020-11-09
相关资源
最近更新 更多