tinycrypto

package module
v0.0.0-...-4a49283 Latest Latest
Warning

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

Go to latest
Published: Apr 2, 2021 License: MIT Imports: 10 Imported by: 0

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.

Usage

The basic API is simple.

First, we need a 256-bit (32-byte) encryption key. This is our master secret that never appears anywhere in repo. It can be any 256-bit byte slice, but we provide an easy way to make one using a secret string.

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

Alternatively, we could use a completely random 256-bit key.

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

Once we have an encryption key, we can then use that value to encrypt and decrypt any slice of bytes.

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

To get the original value back, we need to decrypt it using the same encryption key.

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

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

A Key wraps a 32-byte value used as an encryption key (as we saw above). Along with the value, it stores that Key's creation and expiration timestamps (unix epoch).

plaintext := []byte("this is my secret value that I must protect")
key, err := NewRandomKey()
if err != nil {
    fmt.Println(err)
}
keyset := &Keyset{Keys: []*Key{key}}
cipherText, err := keyset.Encrypt(plaintext)
if err != nil {
    fmt.Println(err)
}

Now let's say the want to start using a new key, but we still want to be able to access the values encrypted with the old key, up until a certain moment in time.

newKey, err := NewRandomKey()
if err != nil {
    fmt.Println(err)
}
days90 := time.Hour * 24 * 90
keyset.RotateIn(newKey,  days90)

From now on, anything that we encrypt with our keyset will be encrypted using the newKey. But we can also still decrypt our secret value, which was encrypted with the old 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

Simple tip

When your application starts, pass in a "master secret". Hash the master secret to make it the a valid (master) encryption key. The master secret and master key are NOT stored in the application or backend, and are NOT used to encrypt secret values in your business domain.

But then what do we use to encrypt values in the business domain?

Generate a new random encryption key. Let's call that the working encryption key. Encrypt secret values in your business domain with that working encryption key. Now, you'll need to persist that working encryption key so that it can be used across multiple instances or multiple sessions. This is where you use the master encryption key. Encrypt the working encryption key with the master encryption key, and only then share the (encrypted) working encryption key among instances or save it to a persistent store.

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

func NewKeysetWithKey

func NewKeysetWithKey(k *Key) *Keyset

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