fast

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2025 License: MIT Imports: 8 Imported by: 0

README

go-fast

A Go implementation of the FAST (Format-preserving encryption And Secure Tokenization) algorithm.

FAST is a format-preserving encryption (FPE) scheme that encrypts data while preserving its format. For example, a 16-byte string will encrypt to another 16-byte string, and numeric data maintains its numeric format.

Features

  • Format-preserving encryption: Output has the same length and format as input
  • Secure: Based on AES with provable security guarantees
  • Fast: Optimized implementation with pre-computed S-boxes and efficient diffusion
  • Deterministic: Same plaintext + key + tweak always produces the same ciphertext
  • Tweak support: Domain separation through optional tweak parameter

Installation

go get github.com/jedisct1/go-fast

Usage

Basic Example
package main

import (
    "fmt"
    "github.com/jedisct1/go-fast"
)

func main() {
    // Create a new FAST cipher with a 16-byte key (AES-128)
    key := []byte("0123456789abcdef")
    cipher, err := fast.NewCipher(key)
    if err != nil {
        panic(err)
    }

    // Encrypt some data
    plaintext := []byte("Hello, World!")
    ciphertext := cipher.Encrypt(plaintext, nil)
    
    fmt.Printf("Plaintext:  %s\n", plaintext)
    fmt.Printf("Ciphertext: %x\n", ciphertext)
    
    // Decrypt it back
    decrypted := cipher.Decrypt(ciphertext, nil)
    fmt.Printf("Decrypted:  %s\n", decrypted)
}
Using Tweaks for Domain Separation
// Different tweaks produce different ciphertexts for the same plaintext
data := []byte("sensitive data")
tweak1 := []byte("domain1")
tweak2 := []byte("domain2")

ciphertext1 := cipher.Encrypt(data, tweak1)
ciphertext2 := cipher.Encrypt(data, tweak2)

// ciphertext1 != ciphertext2

// Must use the same tweak to decrypt
decrypted1 := cipher.Decrypt(ciphertext1, tweak1) // ✓ Correct
decrypted2 := cipher.Decrypt(ciphertext1, tweak2) // ✗ Wrong result
Key Sizes

FAST supports AES-128, AES-192, and AES-256:

// AES-128 (recommended)
key128 := make([]byte, 16)
cipher128, _ := fast.NewCipher(key128)

// AES-192
key192 := make([]byte, 24)
cipher192, _ := fast.NewCipher(key192)

// AES-256
key256 := make([]byte, 32)
cipher256, _ := fast.NewCipher(key256)

Algorithm Details

FAST is based on the research paper:

"FAST: Secure and High Performance Format-Preserving Encryption and Tokenization"
https://eprint.iacr.org/2021/1171.pdf

Key Properties
  • Security: Provides 128-bit security when used with AES-128
  • Performance: Optimized with cached S-boxes and efficient buffer management
  • Format preservation: Input length = output length
  • Deterministic: Reproducible encryption for the same inputs
Security Considerations
  • Use a cryptographically secure random key
  • Different applications should use different tweaks
  • The same (plaintext, key, tweak) always produces the same ciphertext
  • For probabilistic encryption, include random data in the tweak
Benchmark Results

Benchmarks run on Apple M4:

Encryption Performance (nil tweak)
  • 8 bytes: 18.28 MB/s (437.7 ns/op, 3 allocs)
  • 16 bytes: 38.26 MB/s (418.2 ns/op, 3 allocs)
  • 32 bytes: 69.17 MB/s (462.6 ns/op, 4 allocs)
  • 64 bytes: 119.48 MB/s (535.7 ns/op, 4 allocs)
  • 128 bytes: 164.01 MB/s (780.4 ns/op, 4 allocs)
  • 256 bytes: 211.19 MB/s (1212 ns/op, 5 allocs)
  • 512 bytes: 223.47 MB/s (2291 ns/op, 5 allocs)
  • 1KB: 240.01 MB/s (4267 ns/op, 5 allocs)
  • 4KB: 177.92 MB/s (23022 ns/op, 5 allocs)
  • 8KB: 178.09 MB/s (46000 ns/op, 5 allocs)
Nil Tweak vs With Tweak Performance

The implementation includes optimizations for the common case of nil tweaks:

Size Nil Tweak With Tweak Improvement
16B 418.2 ns/op (38.26 MB/s) 580.0 ns/op (27.59 MB/s) 28% faster
64B 535.7 ns/op (119.48 MB/s) 713.7 ns/op (89.68 MB/s) 25% faster
256B 1212 ns/op (211.19 MB/s) 1474 ns/op (173.69 MB/s) 18% faster
1KB 4267 ns/op (240.01 MB/s) N/A N/A

Memory allocations are also significantly reduced (3-5 allocs vs 10 allocs).

Testing

Run the comprehensive test suite:

go test -v

For performance benchmarks:

go test -bench=. -benchtime=10s -run=^$

This implementation is based on the FAST specification and is provided for research and educational purposes.

References

Documentation

Overview

Package fast implements the FAST (Format-preserving encryption And Secure Tokenization) algorithm, providing secure format-preserving encryption for arbitrary byte sequences.

FAST is a cryptographic scheme that encrypts data while preserving its format and length. For example, a 16-byte string encrypts to another 16-byte string, maintaining the same structure as the original data. This makes FAST ideal for encrypting structured data like database fields, tokens, or identifiers without changing their format.

Features

  • Format-preserving: Output length always equals input length
  • Secure: Based on AES with 128-bit security guarantees
  • Deterministic: Same (plaintext, key, tweak) always produces the same ciphertext
  • Fast: Optimized with pre-computed S-boxes and efficient buffer management
  • Tweak support: Optional domain separation for different contexts

Security

FAST provides 128-bit security when used with AES-128. The algorithm is based on the research paper "FAST: Secure and High Performance Format-Preserving Encryption and Tokenization" (https://eprint.iacr.org/2021/1171.pdf).

Key security properties:

  • Provable security reduction to AES
  • Statistical indistinguishability from random permutations
  • Resistance to known cryptographic attacks

Basic Usage

key := make([]byte, 16) // AES-128 key
// Fill key with cryptographically secure random bytes

cipher, err := fast.NewCipher(key)
if err != nil {
    log.Fatal(err)
}

plaintext := []byte("sensitive data")
ciphertext := cipher.Encrypt(plaintext, nil)

// Decrypt
decrypted := cipher.Decrypt(ciphertext, nil)

Using Tweaks

Tweaks provide domain separation, allowing the same key to be used for different purposes without compromising security:

// Different tweaks produce different ciphertexts
userData := cipher.Encrypt(data, []byte("user-context"))
adminData := cipher.Encrypt(data, []byte("admin-context"))

// Must use the same tweak for decryption
decrypted := cipher.Decrypt(userData, []byte("user-context"))

Performance

FAST is optimized for performance with:

  • Pre-computed and cached S-boxes
  • Efficient buffer management to minimize allocations
  • Optimized AES-CMAC implementation
  • Bulk operations for improved cache locality

Typical throughput on modern hardware:

  • Small data (16 bytes): ~50 MB/s
  • Medium data (256 bytes): ~100 MB/s
  • Large data (4KB+): ~150+ MB/s

Thread Safety

Cipher instances are safe for concurrent use. The S-box pool is initialized once using sync.Once, making it safe to encrypt/decrypt from multiple goroutines.

Package fast provides the FAST algorithm implementation for format-preserving encryption. Based on: "FAST: Secure and High Performance Format-Preserving Encryption and Tokenization" https://eprint.iacr.org/2021/1171.pdf

Example (DatabaseFields)

Example_databaseFields demonstrates encrypting database fields

key := []byte("database-key-123") // 16 bytes
cipher, _ := fast.NewCipher(key)

// Encrypt different types of database fields
email := []byte("[email protected]")
phone := []byte("+1-555-0123")
ssn := []byte("123-45-6789")

// Use different tweaks for different field types
encEmail := cipher.Encrypt(email, []byte("field:email"))
encPhone := cipher.Encrypt(phone, []byte("field:phone"))
encSSN := cipher.Encrypt(ssn, []byte("field:ssn"))

fmt.Printf("Email format preserved: %t\n", len(encEmail) == len(email))
fmt.Printf("Phone format preserved: %t\n", len(encPhone) == len(phone))
fmt.Printf("SSN format preserved: %t\n", len(encSSN) == len(ssn))
Output:

Email format preserved: true
Phone format preserved: true
SSN format preserved: true
Example (ErrorHandling)

Example_errorHandling demonstrates proper error handling

// Invalid key size
invalidKey := []byte("short")
_, err := fast.NewCipher(invalidKey)
if err != nil {
	fmt.Printf("Invalid key error: %v\n", err)
}

// Valid key
validKey := []byte("0123456789abcdef")
cipher, err := fast.NewCipher(validKey)
if err != nil {
	log.Fatal(err)
}

// Encrypt empty data (valid operation)
empty := cipher.Encrypt([]byte{}, nil)
fmt.Printf("Empty data result length: %d\n", len(empty))
Output:

Invalid key error: fast: invalid key size, must be 16, 24, or 32 bytes
Empty data result length: 0
Example (HexEncoding)

Example_hexEncoding demonstrates using hex encoding for display

key := []byte("0123456789abcdef")
cipher, _ := fast.NewCipher(key)

// Binary data that may not be printable
binaryData := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}
encrypted := cipher.Encrypt(binaryData, nil)

// Use hex encoding for display/storage
hexStr := hex.EncodeToString(encrypted)
fmt.Printf("Hex encoded: %s\n", hexStr)
fmt.Printf("Hex length: %d\n", len(hexStr))
fmt.Printf("Original length: %d\n", len(binaryData))

// Decode back from hex
decoded, _ := hex.DecodeString(hexStr)
decrypted := cipher.Decrypt(decoded, nil)

fmt.Printf("Decryption successful: %t\n", len(decrypted) == len(binaryData))
Output:

Hex encoded: eb16c02d7607
Hex length: 12
Original length: 6
Decryption successful: true
Example (KeyManagement)

Example_keyManagement demonstrates different key sizes

// AES-128 (16 bytes)
key128 := make([]byte, 16)
rand.Read(key128)
cipher128, err := fast.NewCipher(key128)
if err != nil {
	log.Fatal(err)
}

// AES-192 (24 bytes)
key192 := make([]byte, 24)
rand.Read(key192)
cipher192, err := fast.NewCipher(key192)
if err != nil {
	log.Fatal(err)
}

// AES-256 (32 bytes)
key256 := make([]byte, 32)
rand.Read(key256)
cipher256, err := fast.NewCipher(key256)
if err != nil {
	log.Fatal(err)
}

data := []byte("test data")

// All key sizes work the same way
enc128 := cipher128.Encrypt(data, nil)
enc192 := cipher192.Encrypt(data, nil)
enc256 := cipher256.Encrypt(data, nil)

fmt.Printf("AES-128 preserves format: %t\n", len(enc128) == len(data))
fmt.Printf("AES-192 preserves format: %t\n", len(enc192) == len(data))
fmt.Printf("AES-256 preserves format: %t\n", len(enc256) == len(data))
Output:

AES-128 preserves format: true
AES-192 preserves format: true
AES-256 preserves format: true
Example (Tokenization)

Example_tokenization demonstrates using FAST for tokenization

// Generate a secure random key
key := make([]byte, 16) // AES-128
if _, err := rand.Read(key); err != nil {
	log.Fatal(err)
}

cipher, err := fast.NewCipher(key)
if err != nil {
	log.Fatal(err)
}

// Tokenize credit card numbers while preserving format
ccNumber := []byte("4111111111111111")
token := cipher.Encrypt(ccNumber, []byte("cc-tokens"))

fmt.Printf("Original: %s\n", ccNumber)
fmt.Printf("Token length preserved: %t\n", len(token) == len(ccNumber))
fmt.Printf("Token is different: %t\n", string(token) != string(ccNumber))
Output:

Original: 4111111111111111
Token length preserved: true
Token is different: true

Index

Examples

Constants

View Source
const MaxDataSize = 1 << 20 // 1 MB

MaxDataSize is the maximum supported data size for encryption/decryption. This limit ensures reasonable performance and memory usage.

Variables

View Source
var ErrInvalidKeySize = errors.New("fast: invalid key size, must be 16, 24, or 32 bytes")

ErrInvalidKeySize is returned when the provided key is not 16, 24, or 32 bytes.

Functions

This section is empty.

Types

type Cipher

type Cipher struct {
	// contains filtered or unexported fields
}

Cipher implements the FAST format-preserving encryption algorithm

func NewCipher

func NewCipher(key []byte) (*Cipher, error)

NewCipher creates a new FAST cipher instance with the given AES key. The key must be 16, 24, or 32 bytes for AES-128, AES-192, or AES-256 respectively.

Example

ExampleNewCipher demonstrates basic usage of the FAST cipher

// Create a new FAST cipher with a 16-byte key (AES-128)
key := []byte("0123456789abcdef")
cipher, err := fast.NewCipher(key)
if err != nil {
	panic(err)
}

// Encrypt some data
plaintext := []byte("Hello, World!")
ciphertext := cipher.Encrypt(plaintext, nil)

// Decrypt it back
decrypted := cipher.Decrypt(ciphertext, nil)

fmt.Printf("Original length: %d\n", len(plaintext))
fmt.Printf("Encrypted length: %d\n", len(ciphertext))
fmt.Printf("Decrypted matches: %t\n", string(decrypted) == string(plaintext))
Output:

Original length: 13
Encrypted length: 13
Decrypted matches: true

func (*Cipher) Decrypt

func (f *Cipher) Decrypt(data []byte, tweak []byte) []byte

Decrypt performs FAST format-preserving decryption on the input data. The output has the same length as the input. The same tweak used for encryption must be provided for successful decryption.

Returns nil if the cipher is nil or data exceeds MaxDataSize.

Example

ExampleCipher_Decrypt demonstrates decryption with tweaks

key := []byte("0123456789abcdef")
cipher, _ := fast.NewCipher(key)

data := []byte("confidential")
tweak := []byte("production")

// Encrypt with tweak
ciphertext := cipher.Encrypt(data, tweak)

// Decrypt with correct tweak
decrypted1 := cipher.Decrypt(ciphertext, tweak)

// Decrypt with wrong tweak
wrongTweak := []byte("testing")
decrypted2 := cipher.Decrypt(ciphertext, wrongTweak)

fmt.Printf("Correct tweak works: %t\n", string(decrypted1) == string(data))
fmt.Printf("Wrong tweak fails: %t\n", string(decrypted2) == string(data))
Output:

Correct tweak works: true
Wrong tweak fails: false

func (*Cipher) Encrypt

func (f *Cipher) Encrypt(data []byte, tweak []byte) []byte

Encrypt performs FAST format-preserving encryption on the input data. The output has the same length as the input. The optional tweak parameter provides domain separation - different tweaks produce different ciphertexts for the same plaintext.

Returns nil if the cipher is nil or data exceeds MaxDataSize.

Example

ExampleCipher_Encrypt demonstrates encryption with tweaks

key := []byte("0123456789abcdef")
cipher, _ := fast.NewCipher(key)

data := []byte("secret message")

// Encrypt without tweak
ciphertext1 := cipher.Encrypt(data, nil)

// Encrypt with tweak
tweak := []byte("domain1")
ciphertext2 := cipher.Encrypt(data, tweak)

fmt.Printf("Same length preserved: %t\n", len(ciphertext1) == len(data))
fmt.Printf("Different tweaks produce different results: %t\n",
	string(ciphertext1) != string(ciphertext2))
Output:

Same length preserved: true
Different tweaks produce different results: true

Jump to

Keyboard shortcuts

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