Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

K9Crypt Go Banner

K9Crypt Go

K9Crypt Go is a production-focused encryption module with a V5 AEAD payload format, authenticated metadata, cipher selection (AES-256-GCM / ChaCha20-Poly1305), PBKDF2 master key derivation with HMAC-SHA512 message key expansion, bounded batch processing, and safe defaults for low-latency application encryption.

Key Features

  • 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 CipherId option
  • Master key + message keys - PBKDF2 master key derivation with a single HMAC-SHA512 expansion per message
  • 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; BatchSize is clamped to 2
  • Compression support - optional zlib compression with configurable level

Payload Format (V5)

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 from the master key, salt, and time metadata with a single HMAC-SHA512 pass over that input. This is not RFC 5869 HKDF (there is no extract/expand split); the exact byte layout is wire format and must not change. The master key is derived once via PBKDF2-HMAC-SHA512 from the secret key and a fixed salt, then cached for subsequent operations.

Cipher IDs

ID Cipher
0x00 AES-256-GCM (default)
0x01 ChaCha20-Poly1305

Backward Compatibility

The decrypt path automatically detects the payload version and dispatches accordingly:

  • V5 - AEAD decrypt with an HMAC-SHA512-expanded message key
  • V1-V4 - legacy 5-cipher-chain decrypt with PBKDF2-derived key

Legacy payloads can be rejected by setting AllowLegacyPayloads: false in DecryptOptions. They are accepted by default for compatibility, but their key derivation (PBKDF2/Argon2) runs before anything is authenticated, so decrypting untrusted input is CPU- and memory-heavy; at most 4 such derivations run at once and the rest wait. Disable legacy payloads when the input is not trusted.

Installation

go get github.com/K9Crypt/k9crypt-go

Usage

Basic Encryption

package 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)
}

Generated Key

encryptor, err := k9crypt.NewGenerated()
if err != nil {
    log.Fatal(err)
}

generatedKey := encryptor.GetGenerated()

Binary Payloads

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)
}

Time-Scoped Payloads And Freshness

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)

Compression Override

level := 6
encrypted, err := encryptor.EncryptWithOptions("compressible payload", &k9crypt.EncryptOptions{
    CompressionLevel: &level,
})

Levels are 0-9; 0 (the default) stores the payload raw, any higher level compresses it with zlib before encryption. The deflate stream is embedded in the payload and the level is not recorded, so any level can decrypt any other.

Cipher Selection (V5)

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,
})

Buffered File Encryption

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

Batch Encryption

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)

Strict Compatibility Policy

allowLegacy := false
plaintext, err := encryptor.DecryptWithOptions(ciphertext, &k9crypt.DecryptOptions{
    AllowLegacyPayloads: &allowLegacy,
})

API Reference

Constructors

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.

Methods

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.

Constants

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

Options

EncryptOptions

  • CompressionLevel *int - optional per-call compression level; 0 means raw mode.
  • TimeStepSeconds uint32 - optional time bucket size, default 300, maximum 86400.
  • IssuedAtUnix *int64 - optional authenticated issue time.
  • CipherId *byte - optional cipher selection; nil defaults to AES-256-GCM (0x00), use CipherChaCha20Poly1305 (0x01) for ChaCha20-Poly1305.

DecryptOptions

  • MaxAgeSeconds *int64 - optional freshness limit.
  • AllowedClockSkewSeconds int64 - optional clock skew allowance.
  • NowUnixSeconds *int64 - optional deterministic validation time.
  • AllowLegacyPayloads *bool - default true; set to false to 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.

Security Design

V5 Encryption Flow

  1. Master key is derived once via PBKDF2-HMAC-SHA512 (600,000 iterations) from the secret key and a fixed salt, then cached
  2. Per-message: random 32-byte salt and 12-byte IV are generated
  3. Per-message key is derived with a single HMAC-SHA512 pass over the master key, salt, and time bucket metadata (not RFC 5869 HKDF)
  4. Plaintext is encrypted with the selected AEAD cipher (AES-256-GCM or ChaCha20-Poly1305) using the header as AAD
  5. Body is authenticated via HMAC-SHA512 with the per-message key
  6. Final payload is base64-encoded

V5 Decryption Flow

  1. Payload version is detected from the header
  2. V5 payloads: freshness validation, master key derivation, message key expansion, MAC verification, AEAD decrypt
  3. Legacy V1-V4 payloads: policy check, hash/MAC verification, key derivation, 5-cipher-chain decrypt

Key Zeroization

All intermediate key material (per-message keys, derived keys) is zeroed after use via zeroBytes() to minimize sensitive data lifetime in memory.

License

This project is licensed under the MIT License.

About

A high-performance and secure data encryption library built to military standards.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages