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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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