luks — LUKS1 / LUKS2
github.com/go-fde/luks is pure-Go read/write support for LUKS1 and LUKS2
full-disk-encryption containers. The on-disk header is big-endian; containers
created or opened by this library are interoperable with cryptsetup for the
supported feature set.
Supported features
LUKS version 1
| Feature | Status |
|---|---|
| Header parsing (592-byte phdr, 8 key slots) | ✅ |
| Key derivation: PBKDF2 | ✅ |
Hash specs: sha1, sha256, sha512, ripemd160 |
✅ |
| Anti-forensic split/merge (AF) | ✅ |
Cipher: aes-xts-plain64 |
✅ |
Cipher: aes-cbc-essiv:sha256 |
✅ |
Cipher: twofish-*, serpent-* |
❌ (extremely rare; out of scope) |
Hash specs: sha3-*, blake2b |
❌ (very rare; out of scope) |
LUKS version 2
| Feature | Status |
|---|---|
| Binary superblock + JSON metadata parsing | ✅ |
| Key derivation: PBKDF2 | ✅ |
| Key derivation: Argon2i | ✅ |
| Key derivation: Argon2id | ✅ |
Cipher: aes-xts-plain64 |
✅ |
Cipher: aes-cbc-essiv:sha256 |
✅ |
Segment iv_tweak (non-zero starting sector number) |
✅ |
Integrity (AEAD per-sector tags, --integrity) |
❌ (complex; out of scope) |
| Hardware tokens (FIDO2, TPM, smart cards) | ❌ (out of scope for a passphrase library) |
| Detached headers | ❌ (out of scope) |
Backup header fallback (secondary copy at hdrSize/2) |
❌ (valid devices always have a readable primary header) |
Usage
import "github.com/go-fde/luks"
// Open and unlock a LUKS container.
dev, err := luks.Open("/dev/sdb", []byte("my passphrase"))
if err != nil {
log.Fatal(err)
}
defer dev.Close()
// Read decrypted payload (offset is relative to the payload start).
buf := make([]byte, 4096)
n, err := dev.ReadAt(buf, 0)
// Write to the payload (encrypt-on-write).
_, err = dev.WriteAt([]byte("hello"), 0)
// Check whether a file looks like a LUKS container.
ok, err := luks.Detect("/path/to/disk.img")
Create a new LUKS1 container
Format and FormatOn initialise a fresh LUKS1 header on an existing file or
block device, derive an encryption key from the passphrase, and return an open
*Device ready for payload I/O. Container parameters:
| Parameter | Value |
|---|---|
| LUKS version | 1 |
| Master key size | 32 bytes (AES-256) |
| Cipher | aes-xts-plain64 |
| Key derivation | PBKDF2-SHA256, 1000 iterations |
| AF stripes | 4000 |
| Key material offset | sector 8 |
| Payload offset | sector 258 (132 096 bytes from start) |
// The file must exist before calling Format.
f, _ := os.Create("disk.luks")
f.Close()
dev, err := luks.Format("disk.luks", []byte("passphrase"))
if err != nil { log.Fatal(err) }
defer dev.Close()
// WriteAt offset is relative to payload start (sector 0 of plaintext).
dev.WriteAt(myData, 0)
FormatOn accepts the same blockRW interface as OpenFrom, allowing LUKS to
be initialised directly inside a QCOW2 virtual disk or any other RW backend.
IV sector number semantics
Sector numbers used for IV computation start at 0 (or the LUKS2 iv_tweak
value) for the first payload sector and increment by one per sector,
independently of where the payload sits in the file. This matches cryptsetup,
making containers created by this library interoperable with the standard Linux
tooling.
Compatibility
Containers created or opened by this library are fully interoperable with
cryptsetup for the supported feature set, which covers the vast majority of
LUKS containers found in practice:
- LUKS1 with
aes-xts-plain64andsha256(thecryptsetupdefault) - LUKS1 with
aes-cbc-essiv:sha256andsha1/ripemd160(legacy containers) - LUKS2 with PBKDF2 or Argon2id (the
cryptsetupLUKS2 default)
Layering on top of other block devices (raw, QCOW2, …)
OpenFrom layers LUKS on top of any read-write-closable block device instead of
a plain file — useful when the container lives inside a virtual disk image such
as QCOW2. The caller passes any value satisfying:
interface {
io.ReaderAt
WriteAt([]byte, int64) (int, error)
io.Closer
}
When the returned *Device is closed it also closes the underlying block
device.
Example: LUKS over QCOW2
import (
luks "github.com/go-fde/luks"
image_qcow2 "github.com/go-diskimages/qcow2"
)
// 1. Open the QCOW2 container.
qdev, err := image_qcow2.OpenDevice("disk.qcow2")
if err != nil { log.Fatal(err) }
// 2. Unlock the LUKS container embedded in the QCOW2 virtual disk.
// ldev.Close() will also close qdev.
ldev, err := luks.OpenFrom(qdev, []byte("passphrase"))
if err != nil {
qdev.Close()
log.Fatal(err)
}
defer ldev.Close()
// 3. Read/write the plaintext payload.
buf := make([]byte, 512)
_, err = ldev.ReadAt(buf, 0)