Help us shape the future of Go! Take the 2026 Go Developer Survey

crypt

package
v0.30.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package crypt encrypts and authenticates values with AES-256-GCM and supports key rotation: one current key encrypts, previous keys only decrypt. Tokens are unpadded base64url strings, safe for cookies, URLs and database columns.

Token layout (before base64url):

version (1 byte) | key id (4 bytes) | nonce (12) | ciphertext | tag (16)

The key id lets Open pick the right key without trial decryption, and the header is authenticated together with the caller's additional data, so a token sealed for one purpose (a cookie name, a table column) cannot be replayed as another.

Never pass APP_KEY directly: derive a purpose key with app.Key.Derive("crypt", crypt.KeySize). Apps get a ready Encrypter in app.Crypt, built from APP_KEY and APP_PREVIOUS_KEYS.

See https://github.com/AlexGalhardo/vukem/blob/main/docs/subsystems.md#encryption.

Example
package main

import (
	"bytes"
	"fmt"

	"github.com/AlexGalhardo/vukem/crypt"
)

func main() {
	// In an app, use app.Crypt (keys derived from APP_KEY and
	// APP_PREVIOUS_KEYS) instead of literal keys.
	oldKey := bytes.Repeat([]byte{1}, crypt.KeySize)
	newKey := bytes.Repeat([]byte{2}, crypt.KeySize)

	before, _ := crypt.New(oldKey)
	token := before.EncryptString("4111 1111 1111 1111")

	// After rotating APP_KEY, the old key stays as a previous key.
	after, _ := crypt.New(newKey, oldKey)
	plain, err := after.DecryptString(token)
	fmt.Println(plain, err, after.NeedsRotation(token))

	renewed, _ := after.Rotate(token, nil)
	fmt.Println(after.NeedsRotation(renewed))
}
Output:
4111 1111 1111 1111 <nil> true
false

Index

Examples

Constants

View Source
const KeySize = 32

KeySize is the required key length in bytes (AES-256).

Variables

View Source
var ErrCookieTooLarge = errors.New("crypt: encrypted cookie value exceeds 4 KB")

ErrCookieTooLarge is returned by SetCookie when the encrypted value exceeds 4 KB; keep large data server-side.

View Source
var ErrInvalid = errors.New("crypt: invalid or tampered ciphertext")

ErrInvalid is returned for every token that cannot be opened: malformed, tampered, sealed with an unknown key or with different additional data. The causes are deliberately not distinguished.

View Source
var ErrNoKey = errors.New("crypt: no key: APP_KEY is not set (run `vukem key:generate`)")

ErrNoKey is returned when opening with a nil Encrypter: app.Crypt is nil when the app booted without APP_KEY (outside production).

Functions

This section is empty.

Types

type Encrypter

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

Encrypter seals and opens tokens. It is safe for concurrent use.

func New

func New(current []byte, previous ...[]byte) (*Encrypter, error)

New returns an Encrypter that seals with current and also opens tokens sealed with any of previous (key rotation). Every key must be exactly KeySize bytes.

func (*Encrypter) Cookie

func (e *Encrypter) Cookie(r *http.Request, name string) (string, error)

Cookie returns the decrypted value of the request cookie name, set by SetCookie. It returns http.ErrNoCookie when the cookie is absent and ErrInvalid when it was tampered with, renamed or sealed with an unknown key.

func (*Encrypter) Decrypt

func (e *Encrypter) Decrypt(token string) ([]byte, error)

Decrypt is Open without additional data.

func (*Encrypter) DecryptString

func (e *Encrypter) DecryptString(token string) (string, error)

DecryptString decrypts a token produced by EncryptString.

func (*Encrypter) Encrypt

func (e *Encrypter) Encrypt(plaintext []byte) string

Encrypt is Seal without additional data.

func (*Encrypter) EncryptString

func (e *Encrypter) EncryptString(s string) string

EncryptString encrypts s without additional data.

func (*Encrypter) NeedsRotation

func (e *Encrypter) NeedsRotation(token string) bool

NeedsRotation reports whether token is well formed but was sealed with a key other than the current one; re-seal it with Rotate. It does not authenticate the token.

func (*Encrypter) Open

func (e *Encrypter) Open(token string, ad []byte) ([]byte, error)

Open decrypts a token produced by Seal with the same ad, using the current or any previous key. Every failure returns ErrInvalid, or ErrNoKey on a nil Encrypter.

func (*Encrypter) Rotate

func (e *Encrypter) Rotate(token string, ad []byte) (string, error)

Rotate opens token with ad and seals the plaintext again with the current key.

func (*Encrypter) Seal

func (e *Encrypter) Seal(plaintext, ad []byte) string

Seal encrypts plaintext with the current key, authenticating ad (additional data, not stored in the token) alongside it. Open must be given the same ad. It panics on a nil Encrypter (no APP_KEY).

Example
package main

import (
	"bytes"
	"fmt"

	"github.com/AlexGalhardo/vukem/crypt"
)

func main() {
	enc, _ := crypt.New(bytes.Repeat([]byte{7}, crypt.KeySize))

	// The additional data binds the token to its purpose.
	token := enc.Seal([]byte("42"), []byte("invoice:total"))

	_, err := enc.Open(token, []byte("invoice:discount"))
	fmt.Println(err)

	plain, _ := enc.Open(token, []byte("invoice:total"))
	fmt.Println(string(plain))
}
Output:
crypt: invalid or tampered ciphertext
42

func (*Encrypter) SetCookie

func (e *Encrypter) SetCookie(w http.ResponseWriter, r *http.Request, c http.Cookie) error

SetCookie encrypts c.Value with the cookie name as additional data and adds the Set-Cookie header. Defaults are secure: HttpOnly is always set, SameSite defaults to Lax, and Secure is set when r arrived over TLS (set c.Secure yourself behind a TLS-terminating proxy). Nothing is written on error; a nil Encrypter (no APP_KEY) returns ErrNoKey.

Example
package main

import (
	"bytes"
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/AlexGalhardo/vukem/crypt"
)

func main() {
	enc, _ := crypt.New(bytes.Repeat([]byte{9}, crypt.KeySize))

	w := httptest.NewRecorder()
	r := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
	_ = enc.SetCookie(w, r, http.Cookie{Name: "cart", Value: "3 items", Path: "/"})

	next := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
	for _, c := range w.Result().Cookies() {
		next.AddCookie(c)
	}

	value, err := enc.Cookie(next, "cart")
	fmt.Println(value, err)
}
Output:
3 items <nil>

Jump to

Keyboard shortcuts

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