Skip to content
Support this libraryStar on GitHub

crypt logo

Symmetric encryption for Go - AES-128/256 CBC with HMAC, key rotation, and portable payloads.

Go ReferenceLicense: MITGo TestGo versionLatest tagGo Report Card

crypt provides symmetric encryption for Go services with authenticated payloads (AES-CBC + HMAC) and key rotation via APP_PREVIOUS_KEYS. It also supports Laravel/PHP-compatible payloads for interoperability.

Install

bash
go get github.com/goforj/crypt

Features

  • AES-128 / AES-256 encryption (Laravel/PHP-compatible payload format)
  • Authenticated encryption (AES-CBC + HMAC)
  • Transparent key rotation via APP_PREVIOUS_KEYS
  • Zero dependencies (stdlib only)
  • Deterministic, testable API
  • Instanced and global usage styles
  • Safe defaults with explicit failure modes

Why crypt?

crypt exists to solve one problem well: encrypting small application payloads with safe defaults and painless key rotation.

It is not a general-purpose cryptography library.

It is a focused, application-layer utility designed to be boring, predictable, and interoperable.

Quickstart

Instanced (recommended)

go
package main

import (
	"fmt"

	"github.com/goforj/crypt"
)

func main() {
	appKey := "base64:..." // 16-byte (AES-128) or 32-byte (AES-256) key after decoding.
	key, err := crypt.ReadAppKey(appKey)
	if err != nil {
		panic(err)
	}

	c, err := crypt.New(key)
	if err != nil {
		panic(err)
	}

	ciphertext, err := c.Encrypt("secret")
	if err != nil {
		panic(err)
	}

	plaintext, err := c.Decrypt(ciphertext)
	if err != nil {
		panic(err)
	}

	fmt.Println(plaintext) // "secret"
}

Global (env-based convenience)

go
package main

import (
	"fmt"
	"os"

	"github.com/goforj/crypt"
)

func main() {
	_ = os.Setenv("APP_KEY", "base64:...")

	ciphertext, _ := crypt.Encrypt("secret")
	plaintext, _ := crypt.Decrypt(ciphertext)

	fmt.Println(plaintext) // "secret"
}

Key format & rotation

crypt uses a base64-prefixed key format and supports key rotation. This matches Laravel/PHP conventions when interoperability is needed.

  • APP_KEY must be prefixed with base64: and decode to either 16 bytes (AES-128) or 32 bytes (AES-256).
  • APP_PREVIOUS_KEYS is optional and may contain a comma-separated list of older keys in the same format.
  • During decryption, the current key is tried first, followed by any previous keys.
  • Encryption always uses the current APP_KEY; previous keys are never used for encryption.

Example

bash
export APP_KEY="base64:J63qRTDLub5NuZvP+kb8YIorGS6qFYHKVo6u7179stY="
export APP_PREVIOUS_KEYS="base64:2nLsGFGzyoae2ax3EF2Lyq/hH6QghBGLIq5uL+Gp8/w="

Runnable examples

Every function has a corresponding runnable example under ./examples.

Examples are generated directly from function doc comments, and the same snippets power the README and GoDoc examples.

An automated test builds every example so the docs stay valid as the API evolves.

API Index

Global = package-level functions (env-based convenience). Instanced = methods on *crypt.Cipher with injected keys.

GroupNamespaceFunctions
EncryptionGlobalDecrypt Encrypt
EncryptionInstancedCipher.Decrypt Cipher.Encrypt
Key managementGlobalGenerateAppKey GenerateKeyToEnv GetAppKey GetPreviousAppKeys New NewFromEnv ReadAppKey RotateKeyInEnv

Encryption

Global

Decrypt

Decrypt decrypts an encrypted payload using the APP_KEY from environment. Falls back to APP_PREVIOUS_KEYS when the current key cannot decrypt.

Example: decrypt using current key

go
appKey, _ := crypt.GenerateAppKey()
_ = os.Setenv("APP_KEY", appKey)
ciphertext, _ := crypt.Encrypt("secret")
plaintext, _ := crypt.Decrypt(ciphertext)
godump.Dump(plaintext)
// #string "secret"

Example: decrypt ciphertext encrypted with a previous key

go
oldAppKey, _ := crypt.GenerateAppKey()
newAppKey, _ := crypt.GenerateAppKey()

// Encrypt with the old key first.
_ = os.Setenv("APP_KEY", oldAppKey)
rotatedCiphertext, _ := crypt.Encrypt("rotated")

// Rotate to a new current key, but keep the old key in APP_PREVIOUS_KEYS.
_ = os.Setenv("APP_KEY", newAppKey)
_ = os.Setenv("APP_PREVIOUS_KEYS", oldAppKey)
plaintext, err := crypt.Decrypt(rotatedCiphertext)
godump.Dump(plaintext, err)
// #string "rotated"
// #error <nil>

Encrypt

Encrypt encrypts a plaintext using the APP_KEY from environment.

go
appKey, _ := crypt.GenerateAppKey()
_ = os.Setenv("APP_KEY", appKey)
ciphertext, err := crypt.Encrypt("secret")
godump.Dump(err == nil, ciphertext != "")
// #bool true
// #bool true

Instanced

Cipher.Decrypt

Decrypt decrypts ciphertext with the current key, then any configured previous keys.

Cipher.Encrypt

Encrypt encrypts plaintext with the Cipher's injected current key.

Key management

Global

GenerateAppKey

GenerateAppKey generates a random base64 app key prefixed with "base64:".

go
key, _ := crypt.GenerateAppKey()
godump.Dump(key)
// #string "base64:..."

GenerateKeyToEnv

GenerateKeyToEnv mimics Laravel's key:generate. It generates a new APP_KEY and writes it to the provided .env path. Other keys are preserved; APP_KEY is replaced/added.

go
envPath := filepath.Join(os.TempDir(), ".env")
key, err := crypt.GenerateKeyToEnv(envPath)
godump.Dump(err, key)
// #error <nil>
// #string "base64:..."

GetAppKey

GetAppKey retrieves the APP_KEY from the environment and parses it.

go
appKey, _ := crypt.GenerateAppKey()
_ = os.Setenv("APP_KEY", appKey)
key, err := crypt.GetAppKey()
godump.Dump(len(key), err)
// #int 32
// #error <nil>

GetPreviousAppKeys

GetPreviousAppKeys retrieves and parses APP_PREVIOUS_KEYS from the environment. Keys are expected to be comma-delimited and prefixed with "base64:".

go
oldKeyA, _ := crypt.GenerateAppKey()
oldKeyB, _ := crypt.GenerateAppKey()
// APP_PREVIOUS_KEYS is a comma-separated list.
_ = os.Setenv("APP_PREVIOUS_KEYS", oldKeyA+", "+oldKeyB)
keys, err := crypt.GetPreviousAppKeys()
godump.Dump(len(keys), err)
// #int 2
// #error <nil>

New

New constructs a Cipher with an injected current key and optional previous keys. Keys must be 16 bytes (AES-128) or 32 bytes (AES-256). Inputs are copied.

NewFromEnv

NewFromEnv constructs a Cipher from APP_KEY and APP_PREVIOUS_KEYS.

ReadAppKey

ReadAppKey parses a base64 encoded app key with "base64:" prefix. Accepts 16-byte keys (AES-128) or 32-byte keys (AES-256) after decoding.

go
// Build a 16-byte (AES-128) key string manually.
raw16 := make([]byte, 16)
_, _ = rand.Read(raw16)
key16 := "base64:" + base64.StdEncoding.EncodeToString(raw16)

// Generate a 32-byte (AES-256) key string with the helper.
key32, _ := crypt.GenerateAppKey()

parsed16, _ := crypt.ReadAppKey(key16)
parsed32, _ := crypt.ReadAppKey(key32)
godump.Dump(len(parsed16), len(parsed32))
// #int 16
// #int 32

RotateKeyInEnv

RotateKeyInEnv mimics Laravel's key:rotate. It moves the current APP_KEY into APP_PREVIOUS_KEYS (prepended) and writes a new APP_KEY.

go
envPath := filepath.Join(os.TempDir(), ".env")
currentKey, _ := crypt.GenerateAppKey()
// Seed a minimal .env with an existing APP_KEY.
_ = os.WriteFile(envPath, []byte("APP_KEY="+currentKey+"\n"), 0o644)
newKey, err := crypt.RotateKeyInEnv(envPath)
godump.Dump(err == nil, newKey != "")
// #bool true
// #bool true