keygrip

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package keygrip provides signing and verification of data using a rotating list of secret keys, a stdlib-only Go port of the npm "keygrip" library used by frameworks such as Express and Koa to protect signed cookies and other tamper-evident tokens. Rather than encrypting data, keygrip attaches a keyed message-authentication code (a digest) that a later request can present alongside the data so the server can confirm the data was produced by a holder of one of its secret keys and has not been altered in transit.

The motivating problem is secret rotation. A long-lived service wants to replace its signing secret periodically without invalidating every cookie or token that is still in the wild. Keygrip solves this by holding an ordered list of keys: the newest key sits at the front and signs all new data, while the older keys are retained only so that previously issued digests still verify. When it is time to rotate, the operator prepends a fresh key and eventually drops the oldest one from the tail, and outstanding tokens signed under the retiring key keep working until they expire.

A digest is computed by sign, which runs HMAC-SHA256 over the data using a key as the MAC secret and encodes the result with unpadded base64url (base64.RawURLEncoding). The URL-safe, padding-free encoding means a digest is safe to place directly in a cookie value or URL: it never contains '=', '+', or '/'. Sign always uses the first (current) key, so every freshly produced digest reflects the newest secret.

Verification is where rotation shows through. Index walks the key list from front to back, i.e. newest to oldest, recomputing the digest under each key and comparing it to the supplied digest with crypto/hmac.Equal, a constant-time comparison that does not leak how many leading bytes matched. It returns the index of the first key that reproduces the digest, or -1 when none do; that index tells the caller how far down the rotation the matching key sits, so an application can, for example, re-sign a token whenever it verifies under a non-zero index to migrate it onto the current key. Verify is a thin boolean wrapper that reports whether Index found any match.

The construction and semantics track the Node original closely. New defends against caller-side mutation by copying the supplied slice, so later changes to the caller's array do not affect signing, and it panics when given an empty key list because a keygrip with no keys can neither sign nor verify. The chief intentional divergence from Node is that this port fixes the digest algorithm to HMAC-SHA256 with base64url output, whereas the JavaScript library lets the caller configure the hash and encoding; callers needing a different scheme should adapt sign accordingly.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Keygrip

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

Keygrip holds an ordered list of secret keys. The first key is used for signing, while all keys are tried during verification to support key rotation.

Example

ExampleKeygrip demonstrates the basic sign-then-verify round trip. A Keygrip is created from a list of secret keys; Sign computes an HMAC-SHA256 digest of the data using the first (current) key and encodes it as unpadded base64url so it is safe to place directly in a cookie or URL. Verify recomputes the digest and compares it in constant time, reporting whether the data and digest belong together. Index goes a step further and reports which key in the rotation produced the digest, which is 0 here because the current key signed it. The digest itself is not printed because its exact bytes are an implementation detail of the keys chosen.

package main

import (
	"fmt"

	"github.com/malcolmston/express/keygrip"
)

func main() {
	kg := keygrip.New([]string{"current-secret", "previous-secret"})

	digest := kg.Sign("user=42")
	fmt.Println(kg.Verify("user=42", digest))
	fmt.Println(kg.Index("user=42", digest))
}
Output:
true
0
Example (Rotation)

ExampleKeygrip_rotation shows why Keygrip keeps a list of keys rather than a single secret. A digest is first produced by a keygrip whose only key is the now-retired "previous-secret", simulating a token that was signed before a rotation. A second keygrip is then configured with a freshly prepended "current-secret" ahead of the old one. Verify still accepts the old digest because every key is tried during verification, and Index returns 1 to reveal that the match came from the second (older) key. An application can use that non-zero index as a signal to re-sign the token under the current key.

package main

import (
	"fmt"

	"github.com/malcolmston/express/keygrip"
)

func main() {
	oldDigest := keygrip.New([]string{"previous-secret"}).Sign("user=42")

	rotated := keygrip.New([]string{"current-secret", "previous-secret"})
	fmt.Println(rotated.Verify("user=42", oldDigest))
	fmt.Println(rotated.Index("user=42", oldDigest))
}
Output:
true
1

func New

func New(keys []string) *Keygrip

New creates a Keygrip from the given keys. The first key is used to sign new data; older keys remain valid for verification. New panics if keys is empty.

func (*Keygrip) Index

func (k *Keygrip) Index(data, digest string) int

Index returns the index of the first key that produces digest for data, using a constant-time comparison, or -1 if no key matches.

func (*Keygrip) Sign

func (k *Keygrip) Sign(data string) string

Sign returns the digest of data computed with the current (first) key.

func (*Keygrip) Verify

func (k *Keygrip) Verify(data, digest string) bool

Verify reports whether digest is a valid signature of data for any key.

Jump to

Keyboard shortcuts

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