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)
@fideloper
Copy link
Author

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

wbond commented Jun 4, 2015

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

@shamimulalam
Copy link

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

fideloper commented Apr 13, 2020 via email

@shamimulalam
Copy link

Thanks for your reply. Let me check it

@mcfoi
Copy link

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