K9Crypt Go is a production-focused encryption module with a V5 AEAD payload format, authenticated metadata, cipher selection (AES-256-GCM / ChaCha20-Poly1305), master key derivation with HKDF expansion, bounded batch processing, and safe defaults for low-latency application encryption.
- V5 AEAD format - single-pass authenticated encryption with a 19-byte header and 12-byte IV
- Cipher selection - AES-256-GCM (default) or ChaCha20-Poly1305 via
CipherIdoption - Master key + HKDF - PBKDF2 master key derivation with HMAC-SHA512 per-message key expansion
- Backward compatible - transparently decrypts legacy V1-V4 payloads
- Authenticated metadata - magic, version, flags, cipher ID, time step, and issue time bound to ciphertext via AAD
- Freshness validation - optional max-age and clock-skew enforcement on decrypt
- Batch operations - sequential and parallel batch encrypt/decrypt with progress callbacks
- Compression support - optional zlib compression with configurable level
header(19) | salt(32) | iv(12) | ciphertext(N) | tag(16) | integritySalt(16) | dataHash(64)
header: magic(4) | version(1) | flags(1) | cipherId(1) | stepSeconds(4) | issuedAt(8)
The per-message key is derived via HKDF expansion from the master key, salt, and time metadata. The master key is derived once via PBKDF2-HMAC-SHA512 from the secret key and a fixed salt, then cached for subsequent operations.
| ID | Cipher |
|---|---|
| 0x00 | AES-256-GCM (default) |
| 0x01 | ChaCha20-Poly1305 |
The decrypt path automatically detects the payload version and dispatches accordingly:
- V5 - AEAD decrypt with HKDF-expanded key
- V1-V4 - legacy 5-cipher-chain decrypt with PBKDF2-derived key
Legacy payloads can be rejected by setting AllowLegacyPayloads: false in DecryptOptions.
go get github.com/K9Crypt/k9crypt-gopackage main
import (
"fmt"
"log"
k9crypt "github.com/K9Crypt/k9crypt-go/src"
)
func main() {
encryptor, err := k9crypt.New("VeryLongSecretKey!@#1234567890")
if err != nil {
log.Fatal(err)
}
encrypted, err := encryptor.Encrypt("Hello, World!")
if err != nil {
log.Fatal(err)
}
decrypted, err := encryptor.Decrypt(encrypted)
if err != nil {
log.Fatal(err)
}
fmt.Println(decrypted)
}encryptor, err := k9crypt.NewGenerated()
if err != nil {
log.Fatal(err)
}
generatedKey := encryptor.GetGenerated()data := []byte{0x00, 0xff, 0x10, 0x80}
encrypted, err := encryptor.EncryptBytes(data, nil)
if err != nil {
log.Fatal(err)
}
decrypted, err := encryptor.DecryptBytes(encrypted, nil)
if err != nil {
log.Fatal(err)
}issuedAt := int64(1700000000)
encrypted, err := encryptor.EncryptWithOptions("short-lived", &k9crypt.EncryptOptions{
IssuedAtUnix: &issuedAt,
TimeStepSeconds: 300,
})
if err != nil {
log.Fatal(err)
}
maxAge := int64(300)
now := int64(1700000100)
decrypted, err := encryptor.DecryptWithOptions(encrypted, &k9crypt.DecryptOptions{
MaxAgeSeconds: &maxAge,
AllowedClockSkewSeconds: 30,
NowUnixSeconds: &now,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(decrypted)level := 6
encrypted, err := encryptor.EncryptWithOptions("compressible payload", &k9crypt.EncryptOptions{
CompressionLevel: &level,
})chacha := byte(k9crypt.CipherChaCha20Poly1305)
encrypted, err := encryptor.EncryptWithOptions("ChaCha20 encrypted", &k9crypt.EncryptOptions{
CipherId: &chacha,
})
if err != nil {
log.Fatal(err)
}
// decrypt automatically detects the cipher from the payload header
decrypted, err := encryptor.Decrypt(encrypted)For file and batch operations, pass CipherId through EncryptFileOptions or EncryptManyOptions:
chacha := byte(k9crypt.CipherChaCha20Poly1305)
encrypted, err := encryptor.EncryptFile(data, &k9crypt.EncryptFileOptions{
CipherId: &chacha,
})data := make([]byte, 1024*1024)
encrypted, err := encryptor.EncryptFile(data, &k9crypt.EncryptFileOptions{
CompressionLevel: 0,
CipherId: nil, // nil = AES-256-GCM (default)
OnProgress: func(p k9crypt.ProgressInfo) {
fmt.Printf("%.0f%%\n", p.Percentage)
},
})
if err != nil {
log.Fatal(err)
}
decrypted, err := encryptor.DecryptFile(encrypted, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(decrypted))items := []string{"user1", "user2", "user3"}
encrypted, err := encryptor.EncryptMany(items, &k9crypt.EncryptManyOptions{
CipherId: nil, // nil = AES-256-GCM (default)
Parallel: true,
BatchSize: 2,
})
if err != nil {
log.Fatal(err)
}
decrypted, err := encryptor.DecryptMany(encrypted, &k9crypt.DecryptManyOptions{
Parallel: true,
BatchSize: 2,
SkipInvalid: false,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(decrypted)allowLegacy := false
plaintext, err := encryptor.DecryptWithOptions(ciphertext, &k9crypt.DecryptOptions{
AllowLegacyPayloads: &allowLegacy,
})| Function | Description |
|---|---|
New(secretKey string) (*K9Crypt, error) |
Creates an encryptor from a non-empty string secret. |
NewGenerated() (*K9Crypt, error) |
Creates an encryptor with a 50-byte random secret. |
NewWithKey(secretKey []byte) (*K9Crypt, error) |
Creates an encryptor from a cloned byte-slice secret; nil generates a key, empty slices are rejected. |
NewWithOptions(secretKey string, compressionLevel int) (*K9Crypt, error) |
Creates a string-key encryptor with a default compression level. |
NewWithKeyAndOptions(secretKey []byte, compressionLevel int) (*K9Crypt, error) |
Creates a byte-key encryptor with a default compression level. |
NewGeneratedWithOptions(compressionLevel int) (*K9Crypt, error) |
Creates a generated-key encryptor with a default compression level. |
| Method | Description |
|---|---|
Encrypt(plaintext string) |
Encrypts a string with default options. |
EncryptWithOptions(plaintext string, options *EncryptOptions) |
Encrypts a string with time/compression/cipher options. |
EncryptBytes(plaintext []byte, options *EncryptOptions) |
Encrypts binary data and marks the payload as binary. |
Decrypt(ciphertext string) |
Decrypts a string payload. |
DecryptWithOptions(ciphertext string, options *DecryptOptions) |
Decrypts with freshness and compatibility policy checks. |
DecryptBytes(ciphertext string, options *DecryptOptions) |
Decrypts and returns raw bytes. |
EncryptFile(data []byte, options *EncryptFileOptions) |
Encrypts buffered file data up to 16 MB. |
DecryptFile(ciphertext string, options *DecryptFileOptions) |
Decrypts buffered file data up to 16 MB. |
EncryptMany(dataArray []string, options *EncryptManyOptions) |
Batch encrypts strings. |
DecryptMany(ciphertextArray []string, options *DecryptManyOptions) |
Batch decrypts strings. |
SetCompressionLevel(level int) |
Sets default compression level 0-9. |
GetCompressionLevel() |
Returns current default compression level. |
GetGenerated() |
Returns a copy of the generated secret, or nil for caller-provided secrets. |
| Constant | Value | Description |
|---|---|---|
CipherAes256Gcm |
0x00 | AES-256-GCM cipher ID |
CipherChaCha20Poly1305 |
0x01 | ChaCha20-Poly1305 cipher ID |
PayloadVersionV5 |
5 | Current payload version |
PayloadHeaderSizeV5 |
19 | V5 header size in bytes |
IvSizeV5 |
12 | V5 IV/nonce size in bytes |
EncryptOptions
CompressionLevel *int- optional per-call compression level;0means raw mode.TimeStepSeconds uint32- optional time bucket size, default300, maximum86400.IssuedAtUnix *int64- optional authenticated issue time.CipherId *byte- optional cipher selection;nildefaults to AES-256-GCM (0x00), useCipherChaCha20Poly1305(0x01) for ChaCha20-Poly1305.
DecryptOptions
MaxAgeSeconds *int64- optional freshness limit.AllowedClockSkewSeconds int64- optional clock skew allowance.NowUnixSeconds *int64- optional deterministic validation time.AllowLegacyPayloads *bool- defaulttrue; set tofalseto reject pre-v5 payloads.
EncryptFileOptions / EncryptManyOptions also expose CipherId, compression, time metadata, progress, and parallel batch controls where applicable.
DecryptFileOptions / DecryptManyOptions also expose freshness, compatibility, progress, SkipInvalid, and parallel batch controls where applicable.
- Master key is derived once via PBKDF2-HMAC-SHA512 (600,000 iterations) from the secret key and a fixed salt, then cached
- Per-message: random 32-byte salt and 12-byte IV are generated
- Per-message key is derived via HMAC-SHA512 (HKDF expansion) from master key, salt, and time bucket metadata
- Plaintext is encrypted with the selected AEAD cipher (AES-256-GCM or ChaCha20-Poly1305) using the header as AAD
- Body is authenticated via HMAC-SHA512 with the per-message key
- Final payload is base64-encoded
- Payload version is detected from the header
- V5 payloads: freshness validation, master key derivation, HKDF key expansion, MAC verification, AEAD decrypt
- Legacy V1-V4 payloads: policy check, hash/MAC verification, key derivation, 5-cipher-chain decrypt
All intermediate key material (per-message keys, derived keys) is zeroed after use via zeroBytes() to minimize sensitive data lifetime in memory.
This project is licensed under the MIT License.
