Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Decrypt Laravel-encrypted value
import os
import base64
import json
from Crypto.Cipher import AES
from phpserialize import loads
def decrypt(payload):
data = json.loads(base64.b64decode(payload))
value = base64.b64decode(data['value'])
iv = base64.b64decode(data['iv'])
return unserialize(mcrypt_decrypt(value, iv))
def mcrypt_decrypt(value, iv):
AES.key_size=128
key=os.environ['APP_KEY']
crypt_object=AES.new(key=key,mode=AES.MODE_CBC,IV=iv)
return crypt_object.decrypt(value)
def unserialize(serialized):
return loads(serialized)
@fideloper

This comment has been minimized.

Copy link
Owner Author

@fideloper fideloper commented Jun 4, 2015

Usage:

import crypt

print crypt.decrypt('some larave-encrypted value')

Explanation:

Laravel Crypt::encrypt('whatever'); will create a base64's json-encoded array of:

[
    'iv' => 'generated iv',
    'value' => 'encrypted and base64ed value',
    'mac'  => 'generated mac'
]

Our job is to un-base64 encode it, json decode it, grab the IV and Encrypted Value, and use mcrypt to decode that, which requires the key used to encrypt it (generated when you install a new laravel build).

The unencrypted value itself is serialized (as in, php's serialize() function), so we need a handy little python library that can unserialize PHP serialized strings.

@wbond

This comment has been minimized.

Copy link

@wbond wbond commented Jun 4, 2015

Just be sure to check the mac first! That is, before decrypting.

@shamimulalam

This comment has been minimized.

Copy link

@shamimulalam shamimulalam commented Apr 13, 2020

Traceback (most recent call last):
File "/var/www/html/adnsms/SMS-Shooting-Process/Helpers/crypt.py", line 66, in
decrypt(code)
File "/var/www/html/adnsms/SMS-Shooting-Process/Helpers/crypt.py", line 49, in decrypt
return unserialize(mcrypt_decrypt(value, iv))
File "/var/www/html/adnsms/SMS-Shooting-Process/Helpers/crypt.py", line 57, in mcrypt_decrypt
crypt_object=AES.new(key=key,mode=AES.MODE_CBC,IV=iv)
File "/var/www/html/adnsms/SMS-Shooting-Process/venv/lib/python3.8/site-packages/Crypto/Cipher/AES.py", line 232, in new
return _create_cipher(sys.modules[name], key, mode, *args, **kwargs)
File "/var/www/html/adnsms/SMS-Shooting-Process/venv/lib/python3.8/site-packages/Crypto/Cipher/init.py", line 79, in _create_cipher
return modes[mode](factory, **kwargs)
File "/var/www/html/adnsms/SMS-Shooting-Process/venv/lib/python3.8/site-packages/Crypto/Cipher/_mode_cbc.py", line 274, in _create_cbc_cipher
cipher_state = factory._create_base_cipher(kwargs)
File "/var/www/html/adnsms/SMS-Shooting-Process/venv/lib/python3.8/site-packages/Crypto/Cipher/AES.py", line 92, in _create_base_cipher
if len(key) not in key_size:
TypeError: argument of type 'int' is not iterable

I'm getting this error.. please help

@fideloper

This comment has been minimized.

Copy link
Owner Author

@fideloper fideloper commented Apr 13, 2020

@shamimulalam

This comment has been minimized.

Copy link

@shamimulalam shamimulalam commented Apr 21, 2020

Thanks for your reply. Let me check it

@mcfoi

This comment has been minimized.

Copy link

@mcfoi mcfoi commented Apr 21, 2020

Laraves sems to be using 256 bit since a while
https://laravel.com/docs/5.5/encryption#using-the-encrypter

So I tested the above code in the "php artisan tinker" console, by replacing:
key=os.environ['APP_KEY']
with
key = b"tZMp17lQI70EEYqCsQfwLzlHm6tyaYWPAX66n7YA8KI="

where tZMp17lQI70EEYqCsQfwLzlHm6tyaYWPAX66n7YA8KI= is a string generated issuing the Laravel command

php artisan key:generate

that adds to Laravel's .env a line like:

APP_KEY=base64:tZMp17lQI70EEYqCsQfwLzlHm6tyaYWPAX66n7YA8KI=

I took just the part after "base64:".

With these small changes, I got the same error as @adamilleriam:
(repeated with both AES.key_size=256 or AES.key_size=128) : same error.

Traceback (most recent call last):
  File "test.py", line 35, in <module>
    decrypted = decrypt(payload)
  File "test.py", line 14, in decrypt
    return unserialize(mcrypt_decrypt(value, iv))
  File "test.py", line 21, in mcrypt_decrypt
    crypt_object=AES.new(key=key,mode=AES.MODE_CBC,IV=iv)
  File "C:\Users\mcfoi\Desktop\pycrypt\venv\lib\site-packages\Crypto\Cipher\AES.py", line 232, in new
    return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)
  File "C:\Users\mcfoi\Desktop\pycrypt\venv\lib\site-packages\Crypto\Cipher\__init__.py", line 79, in _create_cipher
    return modes[mode](factory, **kwargs)
  File "C:\Users\mcfoi\Desktop\pycrypt\venv\lib\site-packages\Crypto\Cipher\_mode_cbc.py", line 274, in _create_cbc_cipher
    cipher_state = factory._create_base_cipher(kwargs)
  File "C:\Users\mcfoi\Desktop\pycrypt\venv\lib\site-packages\Crypto\Cipher\AES.py", line 92, in _create_base_cipher
    if len(key) not in key_size:
TypeError: argument of type 'int' is not iterable
@mcfoi

This comment has been minimized.

Copy link

@mcfoi mcfoi commented Apr 21, 2020

Some hints:

  1. In 2020 we should rely on
    https://pycryptodome.readthedocs.io/en/latest/src/installation.html
    as original pakage pycrypt is no longer developed
    so use
    pip install pycryptodome

  2. While relying on pycryptodome, using in code:
    AES.key_size=256
    is EVIL as causes over-writing an internal list meant to check key size and this ultimately causes the reported error:

  File "C:\Users\mcfoi\Desktop\pycrypt\venv\lib\site-packages\Crypto\Cipher\AES.py", line 97, in _create_base_cipher
    if len(key) not in key_size:
TypeError: argument of type 'int' is not iterable

..stay tuned.. .. I am adding details here in future EDITS

@mcfoi

This comment has been minimized.

Copy link

@mcfoi mcfoi commented Apr 21, 2020

HERE IS A TESTED WORKING SCRIPT

"""
Decode in Python a string encoded in Laravel 6.x

String ENCODED in Laravel 6.x with default pakage
Illuminate\Support\Facades\Crypt
and command like:
$encrypted = Crypt::encrypt('Hello world.');
Test in 
php artisan tinker
>>use Illuminate\Support\Facades\Crypt;
>>$encrypted = Crypt::encrypt('Hello world.');

Strings DECODED in Python 3.7 (64bit) with just following requirements:

phpserialize==1.3
pycryptodome==3.9.7

NOTE: avoid using in Laravel
$encrypted = Crypt::encryptString('Hello world.');
as this does NOT serializes strings :  not-serialized strings are not handled by this script.
"""
import os
import base64
import json
from Crypto.Cipher import AES
from phpserialize import loads


def decrypt(laravelEncrypedStringBase64, laravelAppKeyBase64):
    # Decode from base64 Laravel encrypted string
    dataJson = base64.b64decode(laravelEncrypedStringBase64)
    # Load JSON
    data = json.loads(dataJson)
    # Extract actual encrypted message from JSON (other parts are IV and Signature)
    value =  base64.b64decode(data['value'])
    # Extract Initialization Vector from JSON (required to create an AES decypher)
    iv = base64.b64decode(data['iv'])
    # Decode 
    key = base64.b64decode(laravelAppKeyBase64)  # Laravel KEY comes base64Encoded from .env!
    # Create an AES decypher
    decrypter = aesDecrypterCBC(iv, key)
    # Finally decypher the message
    decriptedSerializedMessage = decrypter.decrypt(value)
    # deserialize message
    try :
        # Attempt to deserialize message incase it was created in Laravel with Crypt::encrypt('Hello world.');
        decriptedMessage = unserialize(decriptedSerializedMessage)
        return str(decriptedMessage)
    except:
        raise Exception("Check you cyphered strings in Laravel using Crypt::encrypt() and NOT Crypt::encryptString()")

def aesDecrypterCBC(iv, _key):
    decrypterAES_CBC = AES.new(key=_key,mode=AES.MODE_CBC,IV=iv)
    return decrypterAES_CBC

def unserialize(serialized):
    return loads(serialized)
    
if __name__ == "__main__":
    
    laravelAppKeyBase64 = b"tZMp17lQI70EEYqCsQfwLzlHm6tyaYWPAX66n7YA8KI="
    # Following string is obtained with: $encrypted = Crypt::encrypt('Hello world.');
    laravelEncrypedString = b"eyJpdiI6ImZTQnQ0VEF1NkdWVXdneXRjXC85RjdBPT0iLCJ2YWx1ZSI6IlIxcjhkNDVOZFV3djZLMVVmK0RZQkFYTjBOelpxMEtEYmRRdlBlbHhIcnM9IiwibWFjIjoiNzk3NzI2NTQyOGZkYWRlN2NjZjBiYTUxNWI0YWJlOGU0YjI4MDg2YzI3ZDRlNmMzZTQwOTk3ZTI0YmI2ZTBmYiJ9"
    # Following string is obtained with: $encrypted = Crypt::encryptString('Hello world.'); WILL NOT WORK!!
    #laravelEncrypedString = b"eyJpdiI6Iko0aWpwNFdKU0g2WE95TFlWY2dHaFE9PSIsInZhbHVlIjoiRTFtTG14eTZQbTMrVzZxS0R6OFBEZz09IiwibWFjIjoiYzhhN2VlNThmNDczNGM2M2M5ZDJiNzQ4ZjEzM2MxMDg2M2FmMzFmZTgwNjE3NDYyOWEzYzU1NTNmMmU2OWRjYSJ9"
    decrypted = decrypt(laravelEncrypedString, laravelAppKeyBase64)
    print(decrypted)
@shamimulalam

This comment has been minimized.

Copy link

@shamimulalam shamimulalam commented Apr 22, 2020

Thanks @mcfoi
it's working for me also :)

@temkovs

This comment has been minimized.

Copy link

@temkovs temkovs commented Nov 24, 2020

Hello ^_^

I am seeing this a bit later, but I was looking for this same thing and now that I have tried it it has problems with the "json.loads" part.

Here is the exception I am getting

Traceback (most recent call last):
File "C:\Users\PC\AppData\Roaming\npm\node_modules\serverless\lib\plugins\aws
invokeLocal\runtimeWrappers\invoke.py", line 86, in

result = handler(input['event'], context)

File ".\sendMail.py", line 52, in sendMailSMTP
print(decrypt(secret, app_key))
File ".\sendMail.py", line 98, in decrypt
data = json.loads(dataJson)
File "C:\Users\PC\Anaconda3\lib\json_init_.py", line 343, in loads

s = s.decode(detect_encoding(s), 'surrogatepass')

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

Can someone please help me with this, thanк you in advance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment