Documentation
¶
Overview ¶
Package enchantrix provides the Sigil transformation framework for composable, reversible data transformations. See RFC-0003 for the formal specification.
Sigils are the core abstraction - each sigil implements a specific transformation (encoding, compression, hashing, encryption) with a uniform interface. Sigils can be chained together to create transformation pipelines.
Example usage:
hexSigil, _ := enchantrix.NewSigil("hex")
base64Sigil, _ := enchantrix.NewSigil("base64")
result, _ := enchantrix.Transmute(data, []enchantrix.Sigil{hexSigil, base64Sigil})
Index ¶
- Variables
- func GetNonceFromCiphertext(ciphertext []byte) ([]byte, error)
- func Transmute(data []byte, sigils []Sigil) ([]byte, error)
- type Base64Sigil
- type ChaChaPolySigil
- type Enchantrix
- type GzipSigil
- type HashSigil
- type HexSigil
- type JSONSigil
- type PreObfuscator
- type ReverseSigil
- type ShuffleMaskObfuscator
- type Sigil
- type XORObfuscator
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidKey is returned when the encryption key is invalid. ErrInvalidKey = errors.New("enchantrix: invalid key size, must be 32 bytes") // ErrCiphertextTooShort is returned when the ciphertext is too short to decrypt. ErrCiphertextTooShort = errors.New("enchantrix: ciphertext too short") // ErrDecryptionFailed is returned when decryption or authentication fails. ErrDecryptionFailed = errors.New("enchantrix: decryption failed") // ErrNoKeyConfigured is returned when no encryption key has been set. ErrNoKeyConfigured = errors.New("enchantrix: no encryption key configured") )
Functions ¶
func GetNonceFromCiphertext ¶
GetNonceFromCiphertext extracts the nonce from encrypted output. This is provided for debugging/logging purposes only. The nonce should NOT be stored separately in headers.
func Transmute ¶
Transmute applies a series of sigils to data in sequence.
Each sigil's In method is called in order, with the output of one sigil becoming the input of the next. If any sigil returns an error, Transmute stops immediately and returns nil with that error.
To reverse a transmutation, call each sigil's Out method in reverse order.
Example ¶
package main
import (
"fmt"
"log"
"forge.lthn.ai/Snider/Enchantrix/pkg/enchantrix"
)
func main() {
data := []byte("Hello, World!")
sigils := []enchantrix.Sigil{
&enchantrix.ReverseSigil{},
&enchantrix.HexSigil{},
}
transformed, err := enchantrix.Transmute(data, sigils)
if err != nil {
log.Fatalf("Transmute failed: %v", err)
}
fmt.Printf("Transformed data: %s\n", transformed)
}
Output: Transformed data: 21646c726f57202c6f6c6c6548
Types ¶
type Base64Sigil ¶
type Base64Sigil struct{}
Base64Sigil is a Sigil that encodes/decodes data to/from base64. The In method encodes the data, and the Out method decodes it.
type ChaChaPolySigil ¶
type ChaChaPolySigil struct {
Key []byte
Obfuscator PreObfuscator
// contains filtered or unexported fields
}
ChaChaPolySigil is a Sigil that encrypts/decrypts data using ChaCha20-Poly1305. It applies pre-obfuscation before encryption to ensure raw plaintext never goes directly to CPU encryption routines.
The output format is: [24-byte nonce][encrypted(obfuscated(plaintext))]
Unlike demo implementations, the nonce is ONLY embedded in the ciphertext, not exposed separately in headers.
func NewChaChaPolySigil ¶
func NewChaChaPolySigil(key []byte) (*ChaChaPolySigil, error)
NewChaChaPolySigil creates a new encryption sigil with the given key. The key must be exactly 32 bytes.
func NewChaChaPolySigilWithObfuscator ¶
func NewChaChaPolySigilWithObfuscator(key []byte, obfuscator PreObfuscator) (*ChaChaPolySigil, error)
NewChaChaPolySigilWithObfuscator creates a new encryption sigil with custom obfuscator.
type Enchantrix ¶
Enchantrix defines the interface for acceptance testing.
type GzipSigil ¶
type GzipSigil struct {
// contains filtered or unexported fields
}
GzipSigil is a Sigil that compresses/decompresses data using gzip. The In method compresses the data, and the Out method decompresses it.
type HashSigil ¶
HashSigil is a Sigil that hashes the data using a specified algorithm. The In method hashes the data, and the Out method is a no-op.
func NewHashSigil ¶
NewHashSigil creates a new HashSigil.
type HexSigil ¶
type HexSigil struct{}
HexSigil is a Sigil that encodes/decodes data to/from hexadecimal. The In method encodes the data, and the Out method decodes it.
type JSONSigil ¶
type JSONSigil struct{ Indent bool }
JSONSigil is a Sigil that compacts or indents JSON data. The Out method is a no-op.
type PreObfuscator ¶
type PreObfuscator interface {
// Obfuscate transforms plaintext before encryption using the provided entropy.
// The entropy is typically the encryption nonce, ensuring the transformation
// is unique per-encryption without additional random generation.
Obfuscate(data []byte, entropy []byte) []byte
// Deobfuscate reverses the transformation after decryption.
// Must be called with the same entropy used during Obfuscate.
Deobfuscate(data []byte, entropy []byte) []byte
}
PreObfuscator applies a reversible transformation to data before encryption. This ensures that raw plaintext patterns are never sent directly to CPU encryption routines, providing defense against side-channel attacks.
Implementations must be deterministic: given the same entropy, the transformation must be perfectly reversible: Deobfuscate(Obfuscate(x, e), e) == x
type ReverseSigil ¶
type ReverseSigil struct{}
ReverseSigil is a Sigil that reverses the bytes of the payload. It is a symmetrical Sigil, meaning that the In and Out methods perform the same operation.
type ShuffleMaskObfuscator ¶
type ShuffleMaskObfuscator struct{}
ShuffleMaskObfuscator provides stronger obfuscation through byte shuffling and masking.
The obfuscation process:
- Generate a mask from entropy using SHA-256 in counter mode
- XOR the data with the mask
- Generate a deterministic permutation using Fisher-Yates shuffle
- Reorder bytes according to the permutation
This provides both value transformation (XOR mask) and position transformation (shuffle), making pattern analysis more difficult than XOR alone.
func (*ShuffleMaskObfuscator) Deobfuscate ¶
func (s *ShuffleMaskObfuscator) Deobfuscate(data []byte, entropy []byte) []byte
Deobfuscate reverses the shuffle and mask operations.
type Sigil ¶
type Sigil interface {
// In applies the forward transformation to the data.
// For encoding sigils, this encodes the data.
// For compression sigils, this compresses the data.
// For hash sigils, this computes the digest.
In(data []byte) ([]byte, error)
// Out applies the reverse transformation to the data.
// For reversible sigils, this recovers the original data.
// For irreversible sigils (e.g., hashing), this returns the input unchanged.
Out(data []byte) ([]byte, error)
}
Sigil defines the interface for a data transformer.
A Sigil represents a single transformation unit that can be applied to byte data. Sigils may be reversible (encoding, compression, encryption) or irreversible (hashing).
For reversible sigils: Out(In(x)) == x for all valid x For irreversible sigils: Out returns the input unchanged For symmetric sigils: In(x) == Out(x)
Implementations must handle nil input by returning nil without error, and empty input by returning an empty slice without error.
func NewSigil ¶
NewSigil is a factory function that returns a Sigil based on a string name. It is the primary way to create Sigil instances.
Example ¶
package main
import (
"fmt"
"log"
"forge.lthn.ai/Snider/Enchantrix/pkg/enchantrix"
)
func main() {
sigil, err := enchantrix.NewSigil("base64")
if err != nil {
log.Fatalf("Failed to create sigil: %v", err)
}
data := []byte("Hello, World!")
encoded, err := sigil.In(data)
if err != nil {
log.Fatalf("Sigil In failed: %v", err)
}
fmt.Printf("Encoded data: %s\n", encoded)
decoded, err := sigil.Out(encoded)
if err != nil {
log.Fatalf("Sigil Out failed: %v", err)
}
fmt.Printf("Decoded data: %s\n", decoded)
}
Output: Encoded data: SGVsbG8sIFdvcmxkIQ== Decoded data: Hello, World!
type XORObfuscator ¶
type XORObfuscator struct{}
XORObfuscator performs XOR-based obfuscation using an entropy-derived key stream.
The key stream is generated using SHA-256 in counter mode:
keyStream[i*32:(i+1)*32] = SHA256(entropy || BigEndian64(i))
This provides a cryptographically uniform key stream that decorrelates plaintext patterns from the data seen by the encryption routine. XOR is symmetric, so obfuscation and deobfuscation use the same operation.
func (*XORObfuscator) Deobfuscate ¶
func (x *XORObfuscator) Deobfuscate(data []byte, entropy []byte) []byte
Deobfuscate reverses the XOR transformation (XOR is symmetric).