Chestnut
π° Chestnut is a powerful encrypted storage library for Go, featuring Sparse Encryption, a novel technique for selectively encrypting struct fields. It supports Chained Encryption, custom encryption (AES256-CTR), multiple storage backends (BBolt, NutsDB), and built-in compression (Zstandard), offering unmatched flexibility for secure data storage.
Install / Use
/learn @jrapoport/ChestnutREADME
π° Β Chestnut
Chestnut is encrypted storage for Go. The goal was an easy to use encrypted store with helpful features that was quick to set up, but highly flexible.
Chestnut is written in pure go and designed not to have strong opinions about things like storage, compression, hashing, secrets, or encryption. Chestnut is a storage chest, and not a datastore itself. As such, Chestnut must be backed by a storage solution.
Currently, Chestnut supports BBolt and NutsDB as backing storage.
Table of Contents
- Getting Started
- Storage
- Encryption
- Secrets
- Compression
- Operations
- Struct Field Tags
- Disable Overwrites
- Keystore
- Logging
- Examples
- Known Issues
- Misc
Getting Started
Installing
To start using Chestnut, install Go (version 1.11+) and run go get:
$ go get -u github.com/jrapoport/chestnut
Importing Chestnut
To use Chestnut as an encrypted store, import as:
import (
"github.com/jrapoport/chestnut"
"github.com/jrapoport/chestnut/encryptor/aes"
"github.com/jrapoport/chestnut/encryptor/crypto"
"github.com/jrapoport/chestnut/storage/nuts"
)
// use nutsdb for storage
store := nuts.NewStore(path)
// use AES256-CFB for encryption
opt := chestnut.WithAES(crypto.Key256, aes.CFB, mySecret)
cn := chestnut.NewChestnut(store, opt)
if err := cn.Open(); err != nil {
return err
}
defer cn.Close()
Requirements
Chestnut has two requirements:
- Storage that supports the
storage.Storageinterface (with a lightweight adapter). - Encryption which supports the
crypto.Encryptorinterface.
Storage
Chestnut will work seamlessly with any storage solution (or adapter) that
supports thestorage.Storage interface.
Built-in
Currently, Chestnut has built-in support for BBolt and NutsDB.
BBolt
https://github.com/etcd-io/bbolt Chestnut has built-in support for using BBolt as a backing store.
To use bbolt for a backing store you can import Chestnut's bolt package
and call bolt.NewStore():
import "github.com/jrapoport/chestnut/storage/bolt"
//use or create a bbolt backing store at path
store := bolt.NewStore(path)
// use bbolt for the storage chest
cn := chestnut.NewChestnut(store, ...)
NutsDB
https://github.com/nutsdb/nutsdb
Chestnut has built-in support for using
NutsDB as a backing store.
To use nutsDB for a backing store you can import Chestnut's nuts package
and call nuts.NewStore():
import "github.com/jrapoport/chestnut/storage/nuts"
//use or create a nutsdb backing store at path
store := nuts.NewStore(path)
// use nutsdb for the storage chest
cn := chestnut.NewChestnut(store, ...)
Planned
Other K/V stores like LevelDB.
GORM (probably not) Gorm is an ORM, so while it's not a datastore per se, it could be adapted to support sparse encryption and would mean automatic support for databases like mysql, sqlite, etc. However, most (if not all) of those DBs already support built-in encryption, so w/o some compelling use-case that's not already covered I don't see a lot of value-add.
Encryption
Chestnut supports several flavors of AES out of the box:
- AES128-CFB, AES192-CFB, and AES256-CFB
- AES128-CTR, AES192-CTR, and AES256-CTR
- AES128-GCM, AES192-GCM, and AES256-GCM
You can add AES encryption to Chestnut by passing the chestnut.WithAES() option:
opt := chestnut.WithAES(crypto.Key256, aes.CFB, mySecret)
AES256-CTR
For encryption we recommend using AES256-CTR. We chose AES256-CTR based in part on this helpful analysis from Shawn Wang, PostgreSQL Database Core.
Custom Encryption
Chestnut supports drop-in custom encryption. A struct that supports the
crypto.Encryptor interface can be used with the chestnut.WithEncryptor()
option.
Supporting crypto.Encryptor interface is straightforward and mainly consists
of vending the following two methods:
// Encrypt returns data encrypted with the secret.
Encrypt(plaintext []byte) (ciphertext []byte, err error)
// Decrypt returns data decrypted with the secret.
Decrypt(ciphertext []byte) (plaintext []byte, err error)
Chained Encryption
Chestnut also supports chained encryption which allows data to be arbitrarily transformed by a chain of Encryptors in a FIFO order.
A chain of crypto.Encryptors can be passed to Chestnut with the
chestnut.WithEncryptorChain option:
opt := chestnut.WithEncryptorChain(
encryptor.NewAESEncryptor(crypto.Key128, aes.CFB, secret1),
encryptor.NewAESEncryptor(crypto.Key192, aes.CTR, secret2),
encryptor.NewAESEncryptor(crypto.Key256, aes.GCM, secret3),
)
or by using a crypto.ChainEncryptor with the chestnut.WithEncryptor option:
encryptors := []crypto.Encryptor{
encryptor.NewAESEncryptor(crypto.Key128, aes.CFB, secret1),
encryptor.NewAESEncryptor(crypto.Key192, aes.CTR, secret2),
encryptor.NewAESEncryptor(crypto.Key256, aes.GCM, secret3),
}
chain := crypto.NewChainEncryptor(encryptors...)
opt := chestnut.WithEncryptor(chain)
If you use both the chestnut.WithEncryptor and the
chestnut.WithEncryptorChain options, the crypto.Encryptor from
chestnut.WithEncryptor will be prepended* to the chain.
Sparse Encryption
Chestnut supports the sparse encryption of structs.
Sparse encryption is a transparent feature of saving structs with
Chestnut.Save(), Chestnut.Load(), and Chestnut.Sparse(); or structs that
support the value.Keyed interface with Chestnut.SaveKeyed(),
Chestnut.LoadKeyed(), and Chestnut.SparseKeyed().
What is "sparse" encryption?
With sparse encryption, only struct fields marked as secure will be encrypted.
The remaining "plaintext" fields are encoded and stored separately.
This allows you to load a "sparse" copy of the struct by calling
Chestnut.Sparse() or Chestnut.SparseKeyed() (if you have a value.Keyed
value) and examine the plaintext fields without the overhead of decryption.
When a sparse struct is loaded, the contents of struct fields marked as
secure are replaced by empty values.
Enabling Sparse Encryption
Chestnut uses struct tags to indicate which specific struct fields should be
encrypted. To enable sparse encryption for a struct, add the secure tag option
to the JSON tag of at least one struct field:
SecretKey string `json:",secure"` // 'secure' option (bare minimum)
like so:
type MySparseStruct struct {
SecretValue string `json:"secret_value,secure"` // <-- add 'secure' here
PublicValue string `json:"public_value"`
}
Using Sparse Encryption
Structs can be sparsely encrypted by calling `Chestnut.
Related Skills
openhue
341.8kControl Philips Hue lights and scenes via the OpenHue CLI.
sag
341.8kElevenLabs text-to-speech with mac-style say UX.
weather
341.8kGet current weather and forecasts via wttr.in or Open-Meteo
tweakcc
1.5kCustomize Claude Code's system prompts, create custom toolsets, input pattern highlighters, themes/thinking verbs/spinners, customize input box & user message styling, support AGENTS.md, unlock private/unreleased features, and much more. Supports both native/npm installs on all platforms.
