Skip to content

Repository files navigation

laravel-go-encryption

CI Go Reference Go Report Card

A Go port of Laravel's Illuminate\Encryption\Encrypter, wire-compatible with Laravel 10 through 13. Decrypt in Go what Laravel encrypted with Crypt / Encrypter, and produce payloads Laravel can decrypt — sharing encrypted data seamlessly across platforms using the same key.

Features

  • AES-256-CBC (Laravel's default) and AES-256-GCM (AEAD).
  • Both Laravel paths: EncryptString/DecryptString (raw strings) and Encrypt/Decrypt (the PHP-serialize path — strings, numbers, bools, null, arrays and nested structures).
  • Accepts the Laravel APP_KEY base64:... form, a bare base64 key, or raw key bytes.
  • Constant-time MAC verification (CBC) / AEAD tag verification (GCM).
  • Tampered, wrong-key and malformed payloads are always rejected with typed errors.
  • Zero dependencies (standard library only).
  • Verified both directions against the real Laravel encrypter in CI (Laravel 11/12/13).

Install

go get github.com/Rene-Roscher/laravel-go-encryption

Usage

import laravelcrypt "github.com/Rene-Roscher/laravel-go-encryption"

enc, err := laravelcrypt.New(os.Getenv("APP_KEY"), "aes-256-cbc") // "base64:...." form
if err != nil {
    log.Fatal(err)
}

// Decrypt something Laravel encrypted (Crypt::encryptString(...) in PHP):
plain, err := enc.DecryptString(payloadFromLaravel)

// Encrypt something Laravel can decrypt (Crypt::decryptString($payload) in PHP):
payload, err := enc.EncryptString("hello")

On the Laravel side (no PHP serialization, so the Go side stays simple — JSON-encode structured data yourself):

use Illuminate\Support\Facades\Crypt;

$payload = Crypt::encryptString('hello');     // -> DecryptString in Go
$plain   = Crypt::decryptString($payloadFromGo);

Structured values (the PHP-serialize path)

Encrypt / Decrypt mirror Laravel's default Crypt::encrypt / Crypt::decrypt, which PHP-serialize the value:

payload, _ := enc.Encrypt(map[string]any{"user": "alice", "id": 7, "tags": []any{"x", "y"}})
// PHP: Crypt::decrypt($payload) === ['user' => 'alice', 'id' => 7, 'tags' => ['x', 'y']]

v, _ := enc.Decrypt(payloadFromLaravelCryptEncrypt) // -> any (map[string]any / []any / scalar)

Supported: nil, bool, ints, floats, string, []any, map[string]any (and other slice/map kinds via reflection). PHP objects decode to a map[string]any (class under "__class__") and are never instantiated — so decoding untrusted payloads can't trigger unserialize-gadget chains. Numbers decoded from JSON in Go are float64; pass explicit int/int64 for PHP integers.

For cross-language interop, a plain string carrying your own JSON (EncryptString + encoding/json) is the simplest, least surprising option.

Type mapping & type-safety

PHP (Laravel) Go
string string
int int64
float float64
bool bool
null nil
array (list) []any
array (assoc) map[string]any
object map[string]any ("__class__"; decode-only, never instantiated)

Decrypt returns exactly one of the Go types above — assert with the comma-ok form. The decoder never panics on any input (verified by FuzzDecryptString); unsupported Encrypt inputs (channels, funcs, structs, …) return a typed error rather than panicking. Note that PHP int ↔ Go int64 and PHP floatfloat64; numbers decoded from JSON in Go are float64, so pass explicit int/int64 when you need a PHP integer.

For full compile-time type safety, prefer the string path and let the Go compiler check your own types end-to-end:

b, _ := json.Marshal(cfg)               // cfg is your typed struct
payload, _ := enc.EncryptString(string(b))

plain, _ := enc.DecryptString(payload)
var got Config                          // typed
_ = json.Unmarshal([]byte(plain), &got)

Wire format (identical to Laravel)

base64( json{ "iv": b64, "value": b64, "mac": hex, "tag": b64 } )
  • AES-256-CBC: random 16-byte IV, PKCS#7 padding, mac = HMAC-SHA256(base64(iv)+base64(value), key) (lower-case hex), tag = "".
  • AES-256-GCM: random 12-byte IV, 16-byte GCM tag in tag, mac = "".

The key is the raw key bytes — a Laravel APP_KEY base64:XXXX decodes to 32 bytes.

Security

  • CBC payloads are authenticated with HMAC-SHA256 and compared in constant time before decryption; GCM is authenticated by the AEAD tag.
  • Any modification of the payload (iv/value/mac/tag), a wrong key, an unsupported cipher or key length, or a malformed payload returns a typed error — never a silent/partial result.
  • DecryptString never panics on arbitrary input (covered by a fuzz test).

Testing & cross-language proof

go test ./...                       # unit + Laravel-generated vectors + round-trip + tamper
make race                           # with the race detector
make fuzz                           # fuzz the decoder
make interop                        # both directions vs the REAL Laravel encrypter (needs PHP)

CI runs the unit tests on a Go/OS matrix and, using the real illuminate/encryption, proves both directions:

  • Laravel → Go: scripts/generate_vectors.php (real Encrypter) → TestDecryptLaravelVectors.
  • Go → Laravel: go run ./cmd/genvectorsscripts/verify_go_vectors.php (real Encrypter).

License

MIT © René Roscher

About

Share encrypted data seamlessly between your Laravel backend and go services. Encrypt in Laravel, decrypt in go - or vice versa!

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages