crypt

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 26, 2026 License: MIT Imports: 13 Imported by: 0

README

crypt logo

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

Go Reference Go Test Go version Latest tag Go 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

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

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)

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

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.

Group Namespace Functions
Encryption Global Decrypt Encrypt
Encryption Instanced Cipher.Decrypt Cipher.Encrypt
Key management Global GenerateAppKey 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

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

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.

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:".

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.

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.

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:".

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.

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

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

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Decrypt

func Decrypt(encodedPayload string) (string, error)

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

Example: decrypt using current key

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

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>

func Encrypt

func Encrypt(plaintext string) (string, error)

Encrypt encrypts a plaintext using the APP_KEY from environment. @group Encryption @behavior readonly

Example: encrypt with current APP_KEY

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

func GenerateAppKey

func GenerateAppKey() (string, error)

GenerateAppKey generates a random base64 app key prefixed with "base64:". @group Key management @behavior readonly

Example: generate an AES-256 key

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

func GenerateKeyToEnv

func GenerateKeyToEnv(envPath string) (string, error)

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. @group Key management @behavior mutates-filesystem

Example: generate and write APP_KEY to a temp .env

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

func GetAppKey

func GetAppKey() ([]byte, error)

GetAppKey retrieves the APP_KEY from the environment and parses it. @group Key management @behavior readonly

Example: read APP_KEY and ensure the correct size

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

func GetPreviousAppKeys

func GetPreviousAppKeys() ([][]byte, error)

GetPreviousAppKeys retrieves and parses APP_PREVIOUS_KEYS from the environment. Keys are expected to be comma-delimited and prefixed with "base64:". @group Key management @behavior readonly

Example: parse two previous keys (mixed AES-128/256)

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>

func ReadAppKey

func ReadAppKey(key string) ([]byte, error)

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

Example: parse AES-128 and AES-256 keys

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

func RotateKeyInEnv

func RotateKeyInEnv(envPath string) (string, error)

RotateKeyInEnv mimics Laravel's key:rotate. It moves the current APP_KEY into APP_PREVIOUS_KEYS (prepended) and writes a new APP_KEY. @group Key management @behavior mutates-filesystem

Example: rotate APP_KEY and prepend old key to APP_PREVIOUS_KEYS

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

Types

type Cipher added in v1.1.0

type Cipher struct {
	// contains filtered or unexported fields
}

Cipher provides instance-based encryption/decryption with injected keys. It is safe to construct in DI containers and avoids relying on process-global env state. @group Encryption @behavior readonly

func New added in v1.1.0

func New(key []byte, previousKeys ...[]byte) (*Cipher, error)

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. @group Key management @behavior readonly

func NewFromEnv added in v1.1.0

func NewFromEnv() (*Cipher, error)

NewFromEnv constructs a Cipher from APP_KEY and APP_PREVIOUS_KEYS. @group Key management @behavior readonly

func (*Cipher) Decrypt added in v1.1.0

func (c *Cipher) Decrypt(encodedPayload string) (string, error)

Decrypt decrypts ciphertext with the current key, then any configured previous keys. @group Encryption @behavior readonly

func (*Cipher) Encrypt added in v1.1.0

func (c *Cipher) Encrypt(plaintext string) (string, error)

Encrypt encrypts plaintext with the Cipher's injected current key. @group Encryption @behavior readonly

type EncryptedPayload

type EncryptedPayload struct {
	IV    string `json:"iv"`
	Value string `json:"value"`
	MAC   string `json:"mac"`
}

EncryptedPayload is the JSON structure wrapped in base64 used for ciphertext.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL