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 Key
s, 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 Encrypt ¶
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 ¶
GenerateRandomBytes generates cryptographically secure pseudo-random numbers.
func HashForString ¶
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 ¶
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 ¶
Key wraps an encryption key value to be used with `Keyset`s.
func NewKey ¶
NewKey creates a `Key`, for use with `Keyset`'s, with the given 256-bit value, and sets the creation date.
func NewRandomKey ¶
NewRandomKey creates a `Key`, for use with `Keyset`s, with a random 32-byte key value, and sets the creation date.
type Keyset ¶
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 NewKeysetWithKey ¶
func (*Keyset) Decrypt ¶
Decrypt attempts to decrypt an AES-GCM encrypted value using each unexpired key in the given keyset until decryption is successful.