keyring

package module
v0.2.4 Latest Latest
Warning

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

Go to latest
Published: Mar 28, 2026 License: BSD-3-Clause Imports: 7 Imported by: 0

README

keyring

GoDoc CI

The keyring package provides an interface to read and write encryption keys and other sensitive secrets in a persistent format protected by a secret key. The stored key material are symmetrically encrypted with chacha20poly1305.

Documentation

Overview

Package keyring implements self-contained encrypted storage for a versioned collection of versioned byte strings, typically cryptographic keys or access tokens.

Outline

To create a new keyring, call New. You must provide an access key and the first key version to initialize the ring:

r, err := keyring.New(keyring.Config{
   AccessKey:  accessKey,
   InitialKey: []byte("hunter2"},
})

The access key must be exactly AccessKeyLen bytes. The keys stored in the keyring may be of any length, but must not be empty.

Each key version in the keyring is identified by a unique ID. At any time, one version is identified as the "active" version. Call Ring.Active to obtain the active ID.

active := r.Active()

To add a new key version, use Ring.Add or Ring.AddRandom:

id := r.Add([]byte("hunter2 hunter2"))

Adding a new key does not change the active version. Use Ring.Activate to change the current active version:

r.Activate(id)

To retrieve a key version, use Ring.Get:

var buf []byte
buf = r.Get(id, buf)

This method will panic if the provided ID is unknown. Use Ring.Has to test whether a given version is present:

if r.Has(id) {
   log.Printf("Key id %v is present", id)
}

Use Ring.GetActive get the id and content of the active version:

id, buf := r.GetActive(buf)

Storage

To write a keyring to persistent storage, use Ring.WriteTo:

f, err := os.Create("key.ring")
// ...
nw, err := r.WriteTo(f)

To read a keyring from persistent storage, use Read:

f, err := os.Open("key.ring")
// ...
r, err := keyring.Read(f, accessKeyFunc)

The AccessKeyFunc returns the access key used to decrypt the stored ring.

The binary encoding format of a Ring is defined in the packet package.

Access Keys

An access key is a 32-byte symmetric encryption key. It must be provided to create a new keyring, or to read a saved keyring. In order to allow access keys generated by a key-derivation function, a Ring may optionally store an access-key generation salt provided by the user.

For example, if accessKey is generated by combining a root key with salt and other context, you may provide that context when constructing the ring:

r, err := keyring.New(keyring.Config{
   InitialKey:    []byte("hunter2"),
   AccessKey:     accessKey,  // derived from baseKey + keySalt
   AccessKeySalt: keySalt,
})

The keyring will persist keySalt in storage, and will pass it to the AccessKeyFunc provided when reading it:

r, err := keyring.Read(f, func(keySalt []byte) []byte {
    return someKDF(baseKey, keySalt, ...)
})

If you do not use a key-derivation function, use StaticKey. The PassphraseKey and AccessKeyFromPassphrase helpers may also be useful if you want to derive an access key from a user-provided low-entropy passphrase.

Read-only usage

To use a keyring with an API that does not need the ability to modify the contents of the keyring, call Ring.View to obtain a read-only view. The View type allows reading all the existing key versions at the time it is created, but stores no other cryptographic material and cannot be modified or written to storage.

Once a View is created, further changes to the Ring from which it was derived do not affect the view. While a Ring is not safe for concurrent access by multiple goroutines without separate synchronization, a View can be shared among multiple goroutines safely.

Deletion

A Ring intentionally does not support explicit deletion of keys. Once a key version has been used to encrypt data, deleting it would render those data unreadable. A newly-added and unused key could be safely removed, but the API omits this functionality to avoid accidental misuse. To remove a new and unused key, create a new keyring and copy over the key material.

Example
package main

import (
	"bytes"
	"fmt"
	"log"

	"github.com/creachadair/keyring"
)

func main() {
	key, salt := keyring.AccessKeyFromPassphrase("hunter2")

	r, err := keyring.New(keyring.Config{
		AccessKey:     key,
		AccessKeySalt: salt,
		InitialKey:    []byte("too many secrets"),
	})
	if err != nil {
		log.Fatalf("New failed: %v", err)
	}

	// Print the currently-active key.
	fmt.Printf("Key %d: %q\n", r.Active(), r.Get(r.Active(), nil))

	// Add another key and print that too.
	id := r.Add([]byte("no more secrets"))
	fmt.Printf("Key %d: %q\n", id, r.Get(id, nil))

	// Note that the active key ID doesn't change until we say so.
	fmt.Printf("Active ID before: %d\n", r.Active())
	r.Activate(id)
	fmt.Printf("Active ID after: %d\n", r.Active())

	var buf bytes.Buffer
	nw, err := r.WriteTo(&buf)
	if err != nil {
		log.Fatalf("Write failed: %v", err)
	}
	fmt.Printf("Encoded keyring is %d bytes\n\n", nw)

	// Read the keyring back in from "storage" (buf).
	r2, err := keyring.Read(&buf, keyring.PassphraseKey("hunter2"))
	if err != nil {
		log.Fatalf("Read failed: %v", err)
	}
	fmt.Println("(reloaded)")
	id, akey := r2.GetActive(nil)
	fmt.Printf("Key %d: %q\n", id, akey)

}
Output:
Key 1: "too many secrets"
Key 2: "no more secrets"
Active ID before: 1
Active ID after: 2
Encoded keyring is 199 bytes

(reloaded)
Key 2: "no more secrets"

Index

Examples

Constants

View Source
const AccessKeyLen = cipher.KeyLen // 32 bytes

AccessKeyLen is the length in bytes of an access key.

Variables

This section is empty.

Functions

func AccessKeyFromPassphrase

func AccessKeyFromPassphrase(passphrase string) (key, salt []byte)

AccessKeyFromPassphrase generates a key from the specified passphrase using argon2id and a random salt. It returns the key and the salt.

func RandomKey

func RandomKey(n int) []byte

RandomKey returns a randomly-generated key of the specified length. It will panic if n ≤ 0.

Types

type AccessKeyFunc

type AccessKeyFunc func([]byte) []byte

AccessKeyFunc is a function that generates an access key from a generation salt. The implementation is not required to use the salt. It must return a slice of exactly AccessKeyLen bytes.

func PassphraseKey

func PassphraseKey(passphrase string) AccessKeyFunc

PassphraseKey returns an access key generation function generates an access key using argon2id on the provided passphrase and the stored salt.

func StaticKey

func StaticKey(key []byte) AccessKeyFunc

StaticKey returns an access key generation function that ignores the key generation salt and returns the provided key.

type Config

type Config struct {
	// The initial active key for the ring. This field must be non-empty.
	InitialKey []byte

	// The secret key to decrypt the data encryption key.
	// This must be exactly [AccessKeyLen] bytes.
	AccessKey []byte

	// An optional key-generation salt for the access key. If provided, this
	// value will be passed to the accessKey callback of [Read] when reading the
	// keyring from storage. This may be empty or nil.
	AccessKeySalt []byte
}

Config carries the settings for a Ring.

type ID

type ID = int

An ID identifies a particular version of a key stored in a Ring. A valid ID value is positive. ID is defined is an alias for legibility, rather than a type, so that callers can use the methods of a Ring or View without a direct dependency on this package.

type Ring

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

A Ring is a versioned collection of byte strings, typically cryptographic keys or access tokens. The contents of a ring can be serialized in binary format for persistent storage, and are symmetrically encrypted under a randomly-generated data storage key.

In storage, the data storage key is itself symmetrically encrypted with a user-provided access key and stored alongside the encrypted keyring contents. When a keyring is read, the access key is used to recover the data storage key, which can then be used to decrypt and re-encrypt the contents of the keyring without further need of the access key.

func New

func New(c Config) (*Ring, error)

New constructs a new Ring from c. At minimum, a non-empty initial key and an access key must be provided. It reports an error if any required options are unset or invalid, or if a data encryption key could not be generated.

func Read

func Read(r io.Reader, accessKey AccessKeyFunc) (*Ring, error)

Read parses, and decrypts the binary representation of a Ring from r. It fully consumes the contents of r.

The accessKey function is called to obtain the encryption key for the ring itself. If the ring has a key generation salt, it is passed to the accessKey function; otherwise the salt argument is nil.

func (*Ring) Activate

func (r *Ring) Activate(id ID)

Activate activates the specified key ID in r. It has no effect if the given key ID is already active. It panics if id does not exist in r.

func (*Ring) Active

func (r *Ring) Active() ID

Active reports the current active key ID in r.

func (*Ring) Add

func (r *Ring) Add(key []byte) ID

Add adds the specified non-empty key to r and returns its new ID. If r is empty, the The added key is not marked active; use Ring.Activate to make it active. It panics if len(key) == 0.

func (*Ring) AddRandom

func (r *Ring) AddRandom(n int) ID

AddRandom adds a new randomly-generated n-byte key to r, and returns its ID. It is shorthand for calling Ring.Add with a randomly-generated key. It will panic if n ≤ 0.

func (*Ring) Get added in v0.1.0

func (r *Ring) Get(id ID, buf []byte) []byte

Get appends the contents of the specified key to buf, and returns the resulting slice. It panics if id does not exist in r.

func (*Ring) GetActive added in v0.1.0

func (r *Ring) GetActive(buf []byte) (ID, []byte)

GetActive appends the contents of the active key to buf, and returns active ID and the updated slice.

func (*Ring) Has

func (r *Ring) Has(id ID) bool

Has reports whether v contains a key with the given ID.

func (*Ring) Len

func (r *Ring) Len() int

Len reports the number of keys in r.

func (*Ring) Rekey

func (r *Ring) Rekey(accessKey, accessKeySalt []byte) error

Rekey generates a new data storage key for r, and changes the access key to the provided value. If an error occurs, the current state of r is unchanged. The accessKey must be exactly AccessKeyLen bytes; the salt may be empty or nil.

func (*Ring) View

func (r *Ring) View() *View

View returns a read-only view of r. Subsequent changes to r do not affect the view after it has been initialized.

func (*Ring) WriteTo

func (r *Ring) WriteTo(w io.Writer) (int64, error)

WriteTo encrypts and encodes r in binary format and writes the result to w. It satisfies the io.WriterTo interface.

type View

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

A View is a read-only view of a Ring. A View contains no cryptographic material, Keys cannot be added to it, the active key ID cannot be changed, and it cannot be written to storage.

func SingleKeyView

func SingleKeyView(singleKey []byte) *View

SingleKeyView constructs a View that exports the single provided key as its only version with ID 1. It will panic if singleKey is empty.

func (*View) Active

func (v *View) Active() ID

Active reports the current active key ID in v.

func (*View) Get added in v0.1.0

func (v *View) Get(id ID, buf []byte) []byte

Get appends the contents of the specified key to buf, and returns the resulting slice. It panics if id does not exist in r.

func (*View) GetActive added in v0.1.0

func (v *View) GetActive(buf []byte) (ID, []byte)

GetActive appends the contents of the active key to buf, and returns active ID and the updated slice.

func (*View) Has

func (v *View) Has(id ID) bool

Has reports whether v contains a key with the given ID.

func (*View) Len

func (v *View) Len() int

Len reports the number of keys in v.

Directories

Path Synopsis
cmd
keyring command
Program keyring is a command-line tool to manipulate keyring files.
Program keyring is a command-line tool to manipulate keyring files.
internal
cipher
Package cipher implements symmetric encryption helpers for keyrings.
Package cipher implements symmetric encryption helpers for keyrings.
packet
Packet packet defines the binary storage representation of a keyring as defined by the parent package.
Packet packet defines the binary storage representation of a keyring as defined by the parent package.

Jump to

Keyboard shortcuts

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