tinycrypto

package module
v0.0.0-...-df5bcd1 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2023 License: MIT Imports: 10 Imported by: 1

README

tinycrypto (The Tiny Crypto Toolbox)

Overview

tinycrypto provides a set of tools for encrypting and decrypting data. It is intentionally kept very minimal, to make it as simple as possible for developers, even without deep knowledge of cryptography.

import (
    // …
    "github.com/bitdabbler/tinycrypto"
) 

Basic Usage

The basic API is simple.

First, we need a 256-bit (32-byte) encryption key. tinycrypto provides two easy ways to make cryptographically secure encryption keys.

We can generate one using any string:

encryptionKey := tinycrypto.HashForString("super secret encryption key string")

Or, we generate a completely random one:

encryptionKey, err := tinycrypto.GenerateRandomBytes(32)
if err != nil {
    fmt.Println(err)
}

Now we can use our encryption key to encrypt and decrypt any slice of bytes:

secretToProtect := []byte("crown jewels")
encrypted, err := tinycrypto.Encrypt(secretToProtect, encryptionKey)
if err != nil {
    fmt.Println(err)
}

// here we encode this base64 before printing
fmt.Println(base64.RawStdEncoding.EncodeToString(encrypted))

To get the original value back, we decrypt it using the same encryption key:

recoveredSecret, err := tinycrypto.Decrypt(encrypted, encryptionKey)
if err != nil {
    fmt.Println(err)
}
fmt.Println(string(recoveredSecret)) // crown jewels

Keysets

A second API, also intentionally minimalist, is based on using a Keyset. This is a container wrapping multiple Keys. This enables the simple, transparent rotation of encryption keys.

A Key wraps one of those raw 32-byte encryption keys we made earlier. It augments the raw key with creation and expiration timestamps (unix epoch).

plaintext := []byte("this is my secret value that I must protect")

// we’ll let it generate a complete random Key for us
key, err := tinycrypto.NewRandomKey()
if err != nil {
    fmt.Println(err)
}

// and add that to a new Keyset
keyset := &Keyset{Keys: []*Key{key}}

// now we can use the Keyset methods to encrypt and decrypt values
cipherText, err := keyset.Encrypt(plaintext)
if err != nil {
    fmt.Println(err)
}

Now, imagine that we want to start using a new Key, but for defined transition period, still be able to access the values encrypted with the old key:

newKey, err := NewRandomKey()
if err != nil {
    fmt.Println(err)
}

// the current key we’re replacing will expireAfter 30 days
keyset.RotateIn(newKey, time.Hour * 24 * 30)

From now on, anything that we encrypt with our Keyset will be encrypted using the newest Key. But we can also still decrypt secrets that were encrypted with any older key that hasn’t expired yet.

// cipherText was encrypted using the previous key
decrypted, err := keyset.Decrypt(cipherText)
if err != nil {
    fmt.Println(err)
}
fmt.Println(string(decrypted)) // this is my secret value that I must protect

One Approach

When our service starts, we inject a "secret", and then immediately hash that secret to turn it into a valid encryption key, which we’ll call the prime key. We do not store the secret or the prime key in the code, or in the backend. And, we do not use the prime key to encrypt business values. Instead, we generate a random key, our working key, that we use to encrypt and decrypt business values.

Now, we need to store the working key in a configuration service or a data store, to make it available across sessions and across different instances of our service. We use the prime key to encrypt the working key before persisting it, and to decrypt it when an instance starts up. This which ensures that our prime key was never stored anywhere, and minimizes its presence in memory.

Documentation

Overview

Package tinycrypto provides some very simple helpers for encrypting and decrypting data with minimal fuss, either directly, or through a `Keyset`, which allows working with multiple encryption keys easily when you want to be able to smoothly rotate new keys in over time.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Decrypt

func Decrypt(val []byte, key []byte) ([]byte, error)

Decrypt decrypts an AES-GCM encrypted value.

func Encrypt

func Encrypt(val, key []byte) ([]byte, error)

Encrypt leverages AES-GCM authenticated encryption (encrypts and signs). https://en.wikipedia.org/wiki/Galois/Counter_Mode NOTE: This is for safely storing secret keys. If you need to hash a password, use the acrypt lib.

func GenerateRandomBytes

func GenerateRandomBytes(n uint32) ([]byte, error)

GenerateRandomBytes generates cryptographically secure pseudo-random numbers.

func HashForString

func HashForString(s string) []byte

HashForString converts a string into a 256-bit hash, usable as a secret key for symmetric crypto. NOTE: This is for safely stored secret keys. Do NOT use this for passwords.

func RandUInt32

func RandUInt32() (uint32, error)

RandUInt32 returns a randomly-generated BigEndian 32-bit unsigned integer. It uses the crypto package, and these values are frequently used as nonces.

Types

type CryptoKeyStore

type CryptoKeyStore interface {
	GetCryptoKeyset(name string) (keyset *Keyset, err error)
	PutCryptoKeyset(name string, keyset *Keyset) (err error)
}

CryptoKeyStore provides a generic interface for storing and retrieving cryptographic keys (that themselves should be encrypted at rest).

type Key

type Key struct {
	Value       []byte
	CreatedUnix int64
	ExpiresUnix int64
}

Key wraps an encryption key value to be used with `Keyset`s.

func NewKey

func NewKey(key256 []byte) *Key

NewKey creates a `Key`, for use with `Keyset`'s, with the given 256-bit value, and sets the creation date.

func NewRandomKey

func NewRandomKey() (*Key, error)

NewRandomKey creates a `Key`, for use with `Keyset`s, with a random 32-byte key value, and sets the creation date.

type Keyset

type Keyset struct {
	TypeID int
	sync.RWMutex
	// contains filtered or unexported fields
}

A Keyset stores multiple keys, allowing clients to rotate keys if required. The Keysets get persisted in a name-value store, so the type of Key in a given Keyset is generally fixed/known based on the name used to fetch it. If clients need to support Keysets of various types on a given API (which get persisted using the same name), they can optionally provide a TypeID.

func NewKeyset

func NewKeyset() *Keyset

NewKeyset constructs a new, empty, Keyset.

func NewKeysetWithKey

func NewKeysetWithKey(k *Key) *Keyset

NewKeysetWithKey constructs a new Keyset with provided Key installed.

func (*Keyset) Decrypt

func (ks *Keyset) Decrypt(val []byte) (res []byte, err error)

Decrypt attempts to decrypt an AES-GCM encrypted value using each unexpired key in the given keyset until decryption is successful.

func (*Keyset) Encrypt

func (ks *Keyset) Encrypt(val []byte) ([]byte, error)

Encrypt leverages AES-GCM authenticated encryption using the first encryption key in they Keyset.

func (*Keyset) Purge

func (ks *Keyset) Purge()

Purge removes all any expired keys.

func (*Keyset) RotateIn

func (ks *Keyset) RotateIn(key *Key, expireAfter time.Duration)

RotateIn adds the new Key to the first slot in the Keyset, and pushes the previous Keys back, maintaining the order. It also sets the expiration on the more recent previous key.

Jump to

Keyboard shortcuts

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