【问题标题】:Encode in Laravel, decode in PythonLaravel 编码,Python 解码
【发布时间】:2019-03-09 10:21:33
【问题描述】:

我正在使用 Laravel 的 encryptString 方法来加密我网站上的一些数据。这使用 OpenSSL 的 256 位 AES-CBC 加密,无需任何序列化。我现在正尝试在 Python 中解密该数据,但我不断收到有关密钥长度的错误,似乎无法弄清楚原因。

Example data to decrypt: eyJpdiI6ImdxY0VcLzFodmpISFV4allSWmJDdEpRPT0iLCJ2YWx1ZSI6IkxXd0ZJaUd2bTUweW5pNm0wUjQwOFM2N1wvWEs5SlYrNB4xNlR7Qkh1U3FvPSIsIm1hYyI6Ijc5ZWM0YTYxYjljZGFiNzgwNjY2NDU1ZmQ5Yjc1ZmJlOGU4NzBkMjQzMzA3MmVhYzE3NzY4ZmU1MWIyMjZlOTQifQ==

Example Key to use for decryption (from laravel .env):
base64:/AZejP0lh3McL/+Vy5yZcADdTcR65qnx5Jqinuw7raK=

我改变了这些值,所以实际上用这些值解密不会给出任何真实数据,只是认为它会很好。然后我尝试在 Python 3.7 中解密这些数据:

import base64
from Crypto.Cipher import AES

def decrypt(enc, key):
    IV = 16 * '\x00'
    decobj = AES.new(key, AES.MODE_CBC, IV)
    data = decobj.decrypt(base64.b64decode(enc))
    print(str(data.decode()))

if __name__ == "__main__":
    key = b"/AZejP0lh3McL/+Vy5yZcADdTcR65qnx5Jqinuw7raK="
    decrypt("eyJpdiI6ImdxY0VcLzFodmpISFV4allSWmJDdEpRPT0iLCJ2YWx1ZSI6IkxXd0ZJaUd2bTUweW5pNm0wUjQwOFM2N1wvWEs5SlYrNB4xNlR7Qkh1U3FvPSIsIm1hYyI6Ijc5ZWM0YTYxYjljZGFiNzgwNjY2NDU1ZmQ5Yjc1ZmJlOGU4NzBkMjQzMzA3MmVhYzE3NzY4ZmU1MWIyMjZlOTQifQ==", key)

这似乎应该可以工作,但是当我运行它时,我得到了错误:ValueError: Incorrect AES key length (60 bytes) 所以我不确定我做错了什么。我试过填充/取消填充数据/键,但这似乎并没有改变任何东西。我想知道我是否从 Laravel 获得了用于解密的错误密钥,但从链接文档中可以看出,它应该只是我的 .env 文件中的 APP_KEY。

如果有人可以帮助我或为我指明正确的方向,那就太棒了!

这个问题是其他类似问题所独有的,因为我主要想弄清楚我是否从 Laravel 获得了正确的 AES 密钥,我实际上并不需要帮助解密,我只是想我正在抓住Laravel 的密钥错误。

编辑:似乎可行的新代码:

import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def decrypt(enc, key):
    IV = 16 * '\x00'.encode()
    decobj = AES.new(key, AES.MODE_CBC, IV)
    data = decobj.decrypt(pad(base64.b64decode(enc), 16))
    print(base64.b64decode(data))

if __name__ == "__main__":
    key = base64.b64decode(b"/AZejP0lh3McL/+Vy5yZcADdTcR65qnx5Jqinuw7raK=")
    decrypt("eyJpdiI6ImdxY0VcLzFodmpISFV4allSWmJDdEpRPT0iLCJ2YWx1ZSI6IkxXd0ZJaUd2bTUweW5pNm0wUjQwOFM2N1wvWEs5SlYrNB4xNlR7Qkh1U3FvPSIsIm1hYyI6Ijc5ZWM0YTYxYjljZGFiNzgwNjY2NDU1ZmQ5Yjc1ZmJlOGU4NzBkMjQzMzA3MmVhYzE3NzY4ZmU1MWIyMjZlOTQifQ==", key)

print 语句现在打印一些字节,但是当我在其上运行 .decode() 时出现错误:UnicodeDecodeError: 'utf-8' codec can't decode byte 0xfa in position 0: invalid start byte 并且似乎无法弄清楚我需要做什么才能使其能够被打印作为一个字符串。

【问题讨论】:

  • @stovfl 这个问题看起来可能有助于回答我的问题,但我觉得我的问题主要与 Laravel/环境文件有关,而不仅仅是试图修复错误,在我问的问题中如果有人知道我是否真的从 laravel 获取了正确的 AES 密钥,而不仅仅是如何修复 Python 错误。
  • 我明白你的意思。是什么让你觉得你弄错了钥匙? `php artisan key:generate* 命令生成什么。
  • @stovfl 它根据 stackoverflow.com/a/44839772/8093698 生成用于加密的密钥,我也只是尝试重新运行它以查看会发生什么,但是当我将它输入到我的 Python 代码中时,它仍然是相同的不正确位数.
  • 您未能对密钥进行 base64 解码。

标签: python laravel encryption openssl aes


【解决方案1】:

问题:...尝试在 Python 中解密该数据,但我不断收到有关密钥长度的错误

在执行.b64decode(... 之后,我可以在链接答案的代码中使用您的key。
示例代码 .encode(... 和 decode(... 可以正常工作。
结论:您的 Key 没有问题!

key = b"/AZejP0lh3McL/+Vy5yZcADdTcR65qnx5Jqinuw7raK="
key = base64.b64decode(key)

但是使用您的代码,我得到了 TypeError,与 IV 参数有关:

  expect_byte_string(iv)
File "/usr/local/lib/python3.4/dist-packages/Crypto/Util/_raw_api.py", line 172, in expect_byte_string
  TypeError: Only byte strings can be passed to C code

用IV = 16 * '\x00'.encode()修复,导致ValueError,与enc相关:

  data = decobj.decrypt(base64.b64decode(enc))
File "/usr/local/lib/python3.4/dist-packages/Crypto/Cipher/_mode_cbc.py", line 209, in decrypt
  ValueError: Error 3 while decrypting in CBC mode

这导致github issues:10

错误 3 表示“ERR_NOT_ENOUGH_DATA”

根据链接的 GitHub 页面,您必须在编码时重新阅读文档,关于填充数据。


来自 GitHub 的工作示例:

import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

key = b"/AZejP0lh3McL/+Vy5yZcADdTcR65qnx5Jqinuw7raK="
key = base64.b64decode(key)

BLOCK_SIZE = 32
encryption_suite = AES.new(key, AES.MODE_CBC, b'This is an IV...')
cipher_text = encryption_suite.encrypt(pad(b'A really secret message...', BLOCK_SIZE))

decryption_suite = AES.new(key, AES.MODE_CBC, b'This is an IV...')
print(unpad(decryption_suite.decrypt(cipher_text), BLOCK_SIZE).decode())
>>> A really secret message...

用 Python 测试:3.4.2

【讨论】:

  • 嗯,我将您的代码添加到我的代码中并设法达到打印字节的地步,但是当我去解码它时,我得到了一个错误。我将使用新代码更新我原来的问题。感谢您迄今为止的帮助!
  • @Pecans:用一个工作示例更新了我的答案。 注意:你必须pad(...之前加密和unpad(...解密之后。 utf-8 错误是你 pad(... 编码的结果。
【解决方案2】:

带有 MAC 验证的完整工作代码

除了 Gonzalo 的提交,Laravel 的加密消息是一个 base64 json 编码的数组,由以下密钥对组成:

[
      'iv' => 'generated initialization vector (iv)',
      'value' => 'encrypted, base64ed, signed value',
      'mac'  => 'message authentication code (mac)'
]

使用消息验证码 (MAC) 对“值”进行签名,以验证该值在传输过程中没有更改。

应检查从有效负载(加密消息)中提取的 MAC mac 从“值”中提取(这可以使用键、iv 和值来完成)。 Laravel 的加密方案可以在 GitHub 上找到:src/Illuminate/Encryption/Encrypter.php

参考 Laravel 上的讨论 thread,我在 Github 上找到了部分解决方案:orian/crypt.py(这是 fideloper/crypt.py 的一个分支)。

我已经分叉了 Orian 的代码并修复了一个输入参数问题。只要加密密钥(作为解密()的输入传递)经过 base64 解码并且不包括通常附加到 .env 中的 APP_KEY 环境变量字符串分配的“base64:”,代码就会按预期工作文件。

解决方案:pilatuspc12ng/crypt.py

crypt.py的代码sn-p如下所示:

import base64
import json
from Crypto.Cipher import AES
from phpserialize import loads
import hashlib
import hmac


def decrypt(payload, key):
    """
    Decrypt strings that have been encrypted using Laravel's encrypter (AES-256 encryption).
    Plain text is encrypted in Laravel using the following code:
    >>> ciphertext = Crypt::encrypt('hello world');
    The ciphertext is a base64's json-encoded array consisting of the following keys:
       [
          'iv' => 'generated initialization vector (iv)',
          'value' => 'encrypted, base64ed, signed value',
          'mac'  => 'message authentication code (mac)'
       ]
    The 'value' is signed using a message authentication code (MAC) so verify that the value has not changed during
    transit.
    Parameters:
    payload (str): Laravel encrypted text.
    key (str): Encryption key (base64 decoded). Make sure 'base64:' has been removed from string.
    Returns:
    str: plaintext
    """

    data = json.loads(base64.b64decode(payload))
    if not valid_mac(key, data):
        return None

    value = base64.b64decode(data['value'])
    iv = base64.b64decode(data['iv'])

    return unserialize(mcrypt_decrypt(value, iv, key)).decode("utf-8")

def mcrypt_decrypt(value, iv, key):
    AES.key_size=128
    crypt_object=AES.new(key=key,mode=AES.MODE_CBC,IV=iv)
    return crypt_object.decrypt(value)

def unserialize(serialized):
    return loads(serialized)

def valid_mac(key, data):
    dig = hmac.new(key, digestmod=hashlib.sha256)
    dig.update(data['iv'].encode('utf8'))
    dig.update(data['value'].encode('utf8'))
    dig = dig.hexdigest()
    return dig==data['mac']

【讨论】:

    【解决方案3】:

    我注意到它已经有一段时间没有激活了,但我正在尝试做同样的事情,但似乎无法让它发挥作用。

    我注意到 Laravel 存储的编码密码字符串是一个 base 64 编码的 json 对象,原始问题中没有考虑到这一点:

    pass_obj = base64.b64decode('eyJpdiI6ImdxY0VcLzFodmpISFV4allSWmJDdEpRPT0iLCJ2YWx1ZSI6IkxXd0ZJaUd2bTUweW5pNm0wUjQwOFM2N1wvWEs5SlYrNB4xNlR7Qkh1U3FvPSIsIm1hYyI6Ijc5ZWM0YTYxYjljZGFiNzgwNjY2NDU1ZmQ5Yjc1ZmJlOGU4NzBkMjQzMzA3MmVhYzE3NzY4ZmU1MWIyMjZlOTQifQ==')
    print(pass_obj)
    >>> b'{"iv":"gqcE\\/1hvjHHUxjYRZbCtJQ==","value":"LWwFIiGvm50yni6m0R408S67\\/XK9JV+4\x1e16T{BHuSqo=","mac":"79ec4a61b9cdab780666455fd9b75fbe8e870d2433072eac17768fe51b226e94"}'
    

    从中可以得到 IV 和加密值,两者似乎都是 base64 编码的。但我最后还是会遇到解码错误;

    UnicodeDecodeError: 'utf-8' codec can't decode byte 0xfa in position 0: invalid start byte

    这是我的完整代码;

    password = 'eyJpdiI6ImJGNDNNZjN3YWtpcDQ5VEJVXC9IazF3PT0iLCJ2YWx1ZSI6IkNVRW1VQUY1dXArYlFkU3NlY1pnZUE9PSIsIm1hYyI6ImM3ODk0NWQ0NjgxMzM4YjE0M2JhN2MzZWRmOWEwMWJiMjI2Y2FhYmUxYjFhYzAyYjY4YWZkZGE3N2EyMDYwNWYifQ=='
    key = 'some secret key that i can't share'.encode()
    p_obj = json.loads(base64.b64decode(password).decode())
    decobj = AES.new(key, AES.MODE_CBC, base64.b64decode(p_obj['iv']))
    data = decobj.decrypt(base64.b64decode(p_obj['value']))
    print(data)
    >>> b'l\xee:f\x9eZ\x90rP\x99\xca&@\x1d1\x9f'
    data.decode()
    >>> Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    UnicodeDecodeError: 'utf-8' codec can't decode byte 0xee in position 1: invalid continuation byte
    

    @Pecans 你有想过这个吗?

    谢谢。


    完整的工作代码编辑

    我想通了,我的钥匙最初有问题。所以这里是完整的 sn-p 供将来参考;

    import base64
    import json
    from phpserialize import unserialize
    from Crypto.Cipher import AES
    from Crypto.Util.Padding import pad, unpad
    
    key = b'my secret key'
    enc_pass = 'eyJpdiI6ImJGNDNNZjN3YWtpcDQ5VEJVXC9IazF3PT0iLCJ2YWx1ZSI6IkNVRW1VQUY1dXArYlFkU3NlY1pnZUE9PSIsIm1hYyI6ImM3ODk0NWQ0NjgxMzM4YjE0M2JhN2MzZWRmOWEwMWJiMjI2Y2FhYmUxYjFhYzAyYjY4YWZkZGE3N2EyMDYwNWYifQ=='
    
    p_obj = json.loads(base64.b64decode(password).decode())
    decobj = AES.new(key, AES.MODE_CBC, base64.b64decode(p_obj['iv']))
    data = decobj.decrypt(base64.b64decode(p_obj['value']))
    dec_pass = unserialize(unpad(data, 16)).decode()
    

    您将在 dec_pass 中获得解密后的密码。

    请注意,有时 Laravel 会在 base64 中生成密钥。在这种情况下,字符串将类似于base64:sdfsdjfhjsdf32,那么您必须先解码。

    干杯!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-19
      • 2012-04-10
      • 2015-03-15
      • 1970-01-01
      • 2017-03-01
      • 1970-01-01
      • 2011-05-08
      • 2017-12-25
      相关资源
      最近更新 更多