Skip to content

Instantly share code, notes, and snippets.

@fideloper
Created June 4, 2015 13:52
Show Gist options
  • Save fideloper/c4806c504e46e8cdb00a to your computer and use it in GitHub Desktop.
Save fideloper/c4806c504e46e8cdb00a to your computer and use it in GitHub Desktop.
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)
@mcfoi
Copy link

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
Copy link

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
Copy link

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

@temkovs
Copy link

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

@pizdulicemica
Copy link

any update on the Crypt::encryptString hash?

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