Implement robust data security without sacrificing performance or usability
Warning
This is a work in progress. The package is not yet available on pkg.go.dev.
Protect.go is a Go module for encrypting and decrypting data. Encryption operations happen directly in your app, and the ciphertext is stored in your database. Every value you encrypt with Protect.go has a unique key, made possible by CipherStash ZeroKMS's blazing fast bulk key operations, and backed by a root key in AWS KMS. The encrypted data is structured as an EQL JSON payload, and can be stored in any database that supports JSONB.
Important
Searching, sorting, and filtering on encrypted data is currently only supported when storing encrypted data in PostgreSQL.
- Features
- Installing Protect.go
- Getting started
- Basic usage
- Configuration
- Identity-aware encryption
- Supported data types
- Searchable encryption
- API Reference
- Building from source
- Example applications
- Contributing
- License
For more specific documentation, refer to the docs.
Protect.go protects data using industry-standard AES encryption. Protect.go uses ZeroKMS for bulk encryption and decryption operations. This enables every encrypted value, in every column, in every row in your database to have a unique key — without sacrificing performance.
Features:
- Bulk encryption and decryption: Protect.go uses ZeroKMS for encrypting and decrypting thousands of records at once, while using a unique key for every value.
- Single item encryption and decryption: Just looking for a way to encrypt and decrypt single values? Protect.go has you covered.
- Really fast: ZeroKMS's performance makes using millions of unique keys feasible and performant for real-world applications built with Protect.go.
- Identity-aware encryption: Lock down access to sensitive data by requiring a valid JWT to perform a decryption.
- Audit trail: Every decryption event will be logged in ZeroKMS to help you prove compliance.
- Searchable encryption: Protect.go supports searching encrypted data in PostgreSQL.
- Type safety: Strong typing with Go structs and interfaces.
Use cases:
- Trusted data access: make sure only your end-users can access their sensitive data stored in your product.
- Meet compliance requirements faster: meet and exceed the data encryption requirements of SOC2 and ISO27001.
- Reduce the blast radius of data breaches: limit the impact of exploited vulnerabilities to only the data your end-users can decrypt.
- Go 1.21 or later
- CipherStash CLI (for configuration)
go get github.com/cipherstash/protectgo/pkg/protect
-
On macOS:
brew install cipherstash/tap/stash
-
On Linux, download the binary for your platform, and put it on your
PATH
:
Important
Make sure you have installed the CipherStash CLI before following these steps.
To set up all the configuration and credentials required for Protect.go:
stash setup
If you haven't already signed up for a CipherStash account, this will prompt you to do so along the way.
At the end of stash setup
, you will have two files in your project:
cipherstash.toml
which contains the configuration for Protect.gocipherstash.secret.toml
: which contains the credentials for Protect.go
Warning
Don't commit cipherstash.secret.toml
to git; it contains sensitive credentials.
The stash setup
command will attempt to append to your .gitignore
file with the cipherstash.secret.toml
file.
The library respects the following environment variables:
CIPHERSTASH_WORKSPACE_CRN
- Workspace CRNCIPHERSTASH_ACCESS_KEY
- Access keyCIPHERSTASH_CLIENT_ID
- Client IDCIPHERSTASH_CLIENT_KEY
- Client key
package main
import (
"context"
"log"
"github.com/cipherstash/protectgo/pkg/protect"
)
func main() {
// Configure encryption settings
config := protect.EncryptConfig{
Version: 1,
Tables: protect.Tables{
"users": protect.Table{
"email": protect.Column{
CastAs: &protect.CastAsText,
Indexes: &protect.Indexes{
UniqueIndex: &protect.UniqueIndexOpts{
TokenFilters: []protect.TokenFilter{
{Kind: "downcase"},
},
},
},
},
},
},
}
// Create client
client, err := protect.NewClient(protect.NewClientOptions{
EncryptConfig: config,
})
if err != nil {
log.Fatal(err)
}
defer client.Free()
// Encrypt data
encrypted, err := client.Encrypt(protect.EncryptOptions{
Plaintext: "[email protected]",
Table: "users",
Column: "email",
})
if err != nil {
log.Fatal(err)
}
log.Printf("Encrypted: %s", *encrypted.Ciphertext)
// Decrypt data
plaintext, err := client.Decrypt(protect.DecryptOptions{
Ciphertext: *encrypted.Ciphertext,
})
if err != nil {
log.Fatal(err)
}
log.Printf("Decrypted: %s", plaintext)
}
// Bulk encryption
bulkEncrypted, err := client.EncryptBulk(protect.EncryptBulkOptions{
Plaintexts: []protect.PlaintextPayload{
{
Plaintext: "[email protected]",
Table: "users",
Column: "email",
},
{
Plaintext: "[email protected]",
Table: "users",
Column: "email",
},
},
})
if err != nil {
log.Fatal(err)
}
// Bulk decryption
ciphertexts := make([]protect.BulkDecryptPayload, len(bulkEncrypted))
for i, enc := range bulkEncrypted {
ciphertexts[i] = protect.BulkDecryptPayload{
Ciphertext: *enc.Ciphertext,
}
}
plaintexts, err := client.DecryptBulk(protect.DecryptBulkOptions{
Ciphertexts: ciphertexts,
})
if err != nil {
log.Fatal(err)
}
log.Printf("Decrypted values: %v", plaintexts)
type EncryptConfig struct {
Version uint32 `json:"v"`
Tables Tables `json:"tables"`
}
type Tables map[string]Table
type Table map[string]Column
type Column struct {
CastAs *CastAs `json:"cast_as,omitempty"`
Indexes *Indexes `json:"indexes,omitempty"`
}
type ClientOpts struct {
WorkspaceCrn *string `json:"workspaceCrn,omitempty"`
AccessKey *string `json:"accessKey,omitempty"`
ClientID *string `json:"clientId,omitempty"`
ClientKey *string `json:"clientKey,omitempty"`
}
The library supports all the same index types as other Protect libraries:
- ORE Index: For range queries and ordering
- Match Index: For full-text search
- Unique Index: For exact matching and uniqueness constraints
- SteVec Index: For vector similarity searches
Supported cast types:
CastAsText
- UTF-8 stringsCastAsInt
- 32-bit integersCastAsBigInt
- 64-bit integersCastAsBoolean
- Boolean valuesCastAsDate
- Date valuesCastAsReal/CastAsDouble
- Floating point numbersCastAsJsonB
- JSON data
Important
Identity-aware encryption requires implementing JWT validation in your application.
Protect.go can add an additional layer of protection to your data by requiring a valid JWT to perform a decryption. This ensures that only the user who encrypted data is able to decrypt it.
// Create lock context from JWT claims
lockContext := protect.LockContext{
IdentityClaim: []string{"user:12345"}, // Extract from JWT
}
// Encrypt with lock context
encrypted, err := client.Encrypt(protect.EncryptOptions{
Plaintext: "sensitive-data",
Table: "users",
Column: "email",
LockContext: &lockContext,
})
// Decrypt with the same lock context
plaintext, err := client.Decrypt(protect.DecryptOptions{
Ciphertext: *encrypted.Ciphertext,
LockContext: &lockContext,
})
Caution
You must use the same lock context to encrypt and decrypt data.
If you use different lock contexts, you will be unable to decrypt the data.
Protect.go currently supports encrypting and decrypting text. Other data types like booleans, dates, ints, floats, and JSON are well-supported in other CipherStash products, and will be coming to Protect.go soon.
Important
Searchable encryption requires PostgreSQL with EQL extensions installed.
To enable searchable encryption, you need to install EQL in your PostgreSQL database:
-
Download the latest EQL install script:
curl -sLo cipherstash-encrypt.sql \ https://github.com/cipherstash/encrypt-query-language/releases/latest/download/cipherstash-encrypt.sql
-
Run this command to install the custom types and functions:
psql -f cipherstash-encrypt.sql
-
Create tables with the
eql_v2_encrypted
type:CREATE TABLE users ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, email eql_v2_encrypted );
Read more about searching encrypted data in the docs.
NewClient(options NewClientOptions) (*Client, error)
- Create a new clientclient.Free()
- Release client resourcesclient.Encrypt(options EncryptOptions) (*Encrypted, error)
- Encrypt single valueclient.EncryptBulk(options EncryptBulkOptions) ([]Encrypted, error)
- Encrypt multiple valuesclient.Decrypt(options DecryptOptions) (string, error)
- Decrypt single valueclient.DecryptBulk(options DecryptBulkOptions) ([]string, error)
- Decrypt multiple valuesclient.DecryptBulkFallible(options DecryptBulkOptions) ([]DecryptResult, error)
- Decrypt with error handling per item
All operations return Go-style errors. Use standard Go error handling patterns:
if err != nil {
// Handle error
log.Printf("Encryption failed: %v", err)
return err
}