Decrypting Laravel Encrypted Data

Written by Luke Arntz on July 24, 2023
[ code ] [ laravel ] [ php ] [ openssl ] [ encryption ] [ bash ]

Contents

I recently came across a base64 encoded json string that contained data that was encrypted by php’s Laravel framework. The json contained three keys, iv, value, and mac. I hadn’t seen this before and wanted to learn more about what the data represented and how to decrypt it without using the Laravel framework.

Based on the documentation I read, this functionality uses openssl and I worked backward from there.

This post is an overview of what I learned.

Problem

I want to decrypt some values that were encrypted using Laravel’s Encrypter. The docs say this uses “OpenSSL and the AES-256-CBC cipher.”

The encrypted secret becomes a base64 encoded json document.

Example base64 encoded secret:

eyJpdiI6IlJOK2gyYzlIR2UwbXVjeHRsRndQN0E9PSIsInZhbHVlIjoiVk5XTlhSMlZDbWY1YTk5Z1BWMnhUUT09IiwibWFjIjoiZmNlNGI2MzI3YTIyMzdjMDBlYWVjODk2YTk2N2U5MGMzOWExNWJlYjgxMzY5MjZlYmJkZGVkNTk2ZDkxMGJkMCJ9

Decoded this looks like:

{"iv":"RN+h2c9HGe0mucxtlFwP7A==","value":"VNWNXR2VCmf5a99gPV2xTQ==","mac":"fce4b6327a2237c00eaec896a967e90c39a15beb8136926ebbdded596d910bd0"}

Solution

There is an encryption key used to encrypt our secret. When using Laravel the key is set in the file app.php. The key must be a 32 character string.

The JSON Keys Explained

These json keys are from the base64 encoded Laravel encrypter output (not the same as the encryption key).

iv key:

This should be 16 random bytes in length.

From https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation:

An initialization vector (IV) or starting variable (SV)[5] is a block of bits that is used by several modes to randomize the encryption and hence to produce distinct ciphertexts even if the same plaintext is encrypted multiple times, without the need for a slower re-keying process.

An initialization vector has different security requirements than a key, so the IV usually does not need to be secret. For most block cipher modes it is important that an initialization vector is never reused under the same key, i.e. it must be a cryptographic nonce. Many block cipher modes have stronger requirements, such as the IV must be random or pseudorandom. Some block ciphers have particular problems with certain initialization vectors, such as all zero IV generating no encryption (for some keys).

For CBC and CFB, reusing an IV leaks some information about the first block of plaintext, and about any common prefix shared by the two messages.

In CBC mode, the IV must be unpredictable (random or pseudorandom) at encryption time; in particular, the (previously) common practice of re-using the last ciphertext block of a message as the IV for the next message is insecure (for example, this method was used by SSL 2.0). If an attacker knows the IV (or the previous block of ciphertext) before the next plaintext is specified, they can check their guess about plaintext of some block that was encrypted with the same key before (this is known as the TLS CBC IV attack).[9]

value key:

This contains the encrypted data we want to access.

mac key:

In this case, the mac is the SHA256 hash of the concatenation iv and the encrypted value.

From https://cheatsheetseries.owasp.org/cheatsheets/Key_Management_Cheat_Sheet.html#message-authentication-codes-macs:

Message Authentication Codes (MACs) provide data authentication and integrity. A MAC is a cryptographic checksum on the data that is used in order to provide assurance that the data has not changed and that the MAC was computed by the expected entity.

Steps to Decrypt

Encryption

First, we must encrypt something using Laravel.

php -r 'require("init_laravel_app.php"); echo Crypt::encrypt("test") . "\n";'
eyJpdiI6IjdSbERMakUyOG9uSytEdGg2SUMyYlE9PSIsInZhbHVlIjoiNlB5TUF1MXBBU0JaWE5ybXZLSXBnZz09IiwibWFjIjoiMWU3MmY1NTMzNmNkYTdiZTU1ZTU3NWJmNjNhMzgxZDFjNzIwMTlmMjhhNmI4NmI3ZGNhOWYzMDY5NTMzYmZjYyJ9

Now to see base64 decoded we see the underlying json:

echo eyJpdiI6IjdSbERMakUyOG9uSytEdGg2SUMyYlE9PSIsInZhbHVlIjoiNlB5TUF1MXBBU0JaWE5ybXZLSXBnZz09IiwibWFjIjoiMWU3MmY1NTMzNmNkYTdiZTU1ZTU3NWJmNjNhMzgxZDFjNzIwMTlmMjhhNmI4NmI3ZGNhOWYzMDY5NTMzYmZjYyJ9 | base64 -d
{"iv":"7RlDLjE28onK+Dth6IC2bQ==","value":"6PyMAu1pASBZXNrmvKIpgg==","mac":"1e72f55336cda7be55e575bf63a381d1c72019f28a6b86b7dca9f3069533bfcc"}

Decryption

Below is a script that takes the secret key and base64 encoded output from the php encrypter and decrypts the secret with openssl.

#!/bin/bash
set -euo pipefail
IFS=$'\t\n'

###
#
# decrypter.sh
#
# This script is an example of how to decrypt data that as encrypted using the php Laravel
# Encrypter (https://laravel.com/docs/10.x/encryption#using-the-encrypter).
#
# USAGE
# ./decrypter.sh $SECRET_KEY $BASE64_ENCRYPTER_OUTPUT
#
# When running this a new random IV will be generated, the input data will be encrypted,
# and a MAC will be created.
#
# OUTPUT
# 1. The decrypted data.
# 2. Whether MAC verfication passed or failed.
#
###

###
# functions
###
decrypt() {
	echo -n "${3}" | base64 -d | openssl enc -aes-256-cbc -nosalt -d -K "$(echo -n "${1}" | hexdump -e '16/1 "%02x"')" -iv "$(echo -n "${2}" | hexdump -e '16/1 "%02x"')"
}

verify() {
	CALC_MAC="$(echo -n "${2}${3}" | openssl sha256 -hmac "${1}" -r | cut -d' ' -f1)"

	if [[ "$4" == "$CALC_MAC" ]]; then
		echo pass
	else
		echo fail
	fi
}

###
# do stuff
###
KEY="$1"
SECRET="$2"

M=$(echo -n "$SECRET" | base64 -d)
VALUE=$(echo "$M" | jq -r .value)
MAC=$(echo "$M" | jq -r .mac)
IVB64=$(echo "$M" | jq -r .iv)
IV=$(echo "$IVB64" | base64 -d)

echo "decrypted data : $(decrypt "$KEY" "$IV" "$VALUE")"
echo "mac verification: $(verify "$KEY" "$IVB64" "$VALUE" "$MAC")"

Related Articles

Top