cipherlock

package
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: MIT Imports: 29 Imported by: 0

Documentation

Overview

WARNING: V1 compat reads the entire file into memory. Large legacy files (>= 1 GiB) are rejected with ErrCorrupted to prevent OOM crashes.

WARNING: KDF CANNOT BE CANCELLED.

All *Context functions spawn a goroutine that runs the full Argon2id key derivation to completion, even when ctx is cancelled. The goroutine is "leaked" until KDF finishes — it cannot be interrupted.

This means:

  • A cancelled context does NOT speed up a running KDF.
  • Memory and CPU for Argon2id are fully consumed regardless of ctx.
  • Only the subsequent encrypt/decrypt phases (AES-GCM) can be cancelled.

If you need to bound KDF runtime, adjust Config.Time, Config.Memory, or set a deadline on ctx *before* calling KDF.

Package cipherlock implements AES-256-GCM encryption with Argon2id key derivation.

FORMAT

All versions share a 4-byte magic prefix "CV2\0" followed by a version byte. Unless otherwise noted, multi-byte integers are little-endian.

V2/V3 (single-recipient, non-streaming)

4 bytes   Magic: "CV2\0"
1 byte    Version: 0x02 or 0x03
1 byte    Flags (v0x03 only): bit 0 = checksum present
2 bytes   Salt length
N bytes   Argon2id salt
4 bytes   Argon2 time
4 bytes   Argon2 memory
1 byte    Argon2 threads
4 bytes   Key length
12 bytes  AES-GCM nonce
[32 bytes SHA-256 checksum (v0x03, when flags bit 0 set)]
Variable  Ciphertext + 16-byte GCM tag

V4 (multi-recipient, non-streaming)

4 bytes   Magic: "CV2\0"
1 byte    Version: 0x04
1 byte    Flags: bit 0 = checksum present
4 bytes   Number of recipients
For each recipient:
  2 bytes  Salt length
  N bytes  Argon2id salt
  4 bytes  Time
  4 bytes  Memory
  1 byte   Threads
  4 bytes  Key length
  12 bytes Nonce for key encryption
  2 bytes  Sealed key length
  M bytes  Encrypted file key + GCM tag
12 bytes   File nonce
[32 bytes SHA-256 checksum (when flags bit 0 set)]
Variable  Ciphertext + GCM tag

The plaintext is buffered in memory — use V7 for large files.

V5 (streaming, cleartext metadata)

4 bytes   Magic: "CV2\0"
1 byte    Version: 0x05
1 byte    Flags: bit 0 = checksum present
2 bytes   Salt length
N bytes   Argon2id salt
4 bytes   Time
4 bytes   Memory
1 byte    Threads
4 bytes   Key length
4 bytes   Chunk size
1 byte    Has metadata (0/1)
[If metadata:]
  2 bytes  Filename length
  M bytes  Filename (UTF-8)
  8 bytes  File size
  8 bytes  Modification time (Unix nanosecond)
[32 bytes SHA-256 checksum (trailer, when flags bit 0 set)]

Zero or more data chunks:
  12 bytes  Nonce
  4 bytes   Ciphertext + GCM tag length (0 = end of stream)
  N bytes   Ciphertext + 16-byte GCM tag

4 zero bytes (end marker)

V6 (streaming, encrypted metadata)

4 bytes   Magic: "CV2\0"
1 byte    Version: 0x06
1 byte    Flags: bit 0 = checksum present, bit 1 = has metadata
2 bytes   Salt length
N bytes   Argon2id salt
4 bytes   Time
4 bytes   Memory
1 byte    Threads
4 bytes   Key length
4 bytes   Chunk size

[Optional encrypted metadata chunk, when flags bit 1 set:]
  12 bytes  Nonce
  4 bytes   Ciphertext length
  M bytes   Ciphertext + GCM tag (decrypts to: nameLen(2)+name+size(8)+mtime(8))

Zero or more data chunks — same as V5.
4 zero bytes (end marker)
[32 bytes SHA-256 checksum trailer, when flags bit 0 set]

Metadata is encrypted under the same key as the data so filename and size are not visible without the password. EncryptStreamV2 selects this format automatically when FileMeta is provided.

V7 (streaming, multi-recipient)

4 bytes   Magic: "CV2\0"
1 byte    Version: 0x07
1 byte    Flags: bit 0 = checksum present, bit 1 = has metadata
4 bytes   Number of recipients
For each recipient:
  2 bytes  Salt length
  N bytes  Argon2id salt
  4 bytes  Time
  4 bytes  Memory
  1 byte   Threads
  4 bytes  Key length
  12 bytes Nonce for key encryption
  2 bytes  Sealed key length
  M bytes  Encrypted file key + GCM tag

[Optional encrypted metadata chunk — same layout as V6]
Zero or more data chunks — same as V5.
4 zero bytes (end marker)
[32 bytes SHA-256 checksum trailer, when flags bit 0 set]

A fresh random file key encrypts the data and metadata chunk. The file key is sealed once per recipient with their derived key. This is the streaming replacement for V4 — use EncryptStreamMulti / DecryptStreamMulti.

V8 (asymmetric, X25519)

4 bytes   Magic: "CV2\0"
1 byte    Version: 0x08
1 byte    Flags: bit 0 = checksum present, bit 1 = has metadata
4 bytes   Number of X25519 recipients
For each recipient:
  1 byte   Identity type (0x01 = X25519)
  32 bytes Ephemeral X25519 public key
  12 bytes Nonce for key sealing
  48 bytes Sealed file key (AES-256-GCM, ciphertext + 16-byte tag)

[Optional encrypted metadata chunk — same layout as V6]
Zero or more data chunks — same as V5.
4 zero bytes (end marker)
[32 bytes SHA-256 checksum trailer, when flags bit 0 set]

Uses ECDH + HKDF-SHA256 for key agreement. Use --recipient-pubkey (CLI) or EncryptAsymmetric / DecryptAsymmetric (library).

ASCII-armor

When --armor is used, the binary format is base64-encoded with PEM-style delimiters:

-----BEGIN CIPHERLOCK-----
<base64, wrapped at 64 columns>
-----END CIPHERLOCK-----

Decrypt detects armor automatically.

Example
dir, err := os.MkdirTemp("", "cipherlock")
if err != nil {
	return
}
defer os.RemoveAll(dir)

src := filepath.Join(dir, "secret.txt")
os.WriteFile(src, []byte("my secret data"), 0644)

dst := src + ".encrypted"
password := []byte("my-strong-password")

if err := EncryptFile(src, dst, password, nil); err != nil {
	return
}

restored := filepath.Join(dir, "restored.txt")
if err := DecryptFile(dst, restored, password); err != nil {
	return
}

Index

Examples

Constants

View Source
const DefaultChunkSize = 64 * 1024

DefaultChunkSize is the default chunk size (64 KB) used for stream encryption.

Variables

View Source
var DefaultConfig = &Config{
	SaltLen:   16,
	Time:      3,
	Memory:    64 * 1024,
	Threads:   4,
	KeyLen:    32,
	ChunkSize: DefaultChunkSize,
}

DefaultConfig is the default configuration used when a nil config is passed. It uses Argon2id with time=3, memory=64MB, threads=4, 16-byte salt, 32-byte key, and 64KB chunk size with no checksum.

View Source
var ErrAtLeastOnePassword = errors.New("cipherlock: at least one password required")

ErrAtLeastOnePassword is returned when no passwords are provided for multi-key encryption.

View Source
var ErrAuthFailed = errors.New("cipherlock: authentication failed")

ErrAuthFailed is returned when decryption authentication fails. This typically indicates an incorrect password or corrupted data.

View Source
var ErrChecksumMismatch = errors.New("cipherlock: checksum mismatch")

ErrChecksumMismatch is returned when the decrypted data's checksum does not match the stored checksum.

View Source
var ErrConfigInvalid = errors.New("cipherlock: invalid configuration")

ErrConfigInvalid is returned when Config.Validate() detects invalid parameters.

View Source
var ErrCorrupted = errors.New("cipherlock: corrupted data")

ErrCorrupted is returned when the encrypted data is malformed or incomplete.

View Source
var ErrEncryptedMeta = errors.New("cipherlock: file metadata is encrypted; password required")

ErrEncryptedMeta is returned by ReadStreamMeta when the file uses an encrypted-metadata format version (v0x06 or v0x07) and the caller must supply a password to ReadStreamMetaWithPassword.

View Source
var ErrIdentityNeedsPassphrase = errors.New("cipherlock: identity is encrypted, provide a passphrase")

ErrIdentityNeedsPassphrase is returned when attempting to deserialize an encrypted identity without providing a passphrase.

View Source
var ErrInvalidFormat = errors.New("cipherlock: invalid file format")

ErrInvalidFormat is returned when the input does not contain a valid cipherlock header.

View Source
var (
	ErrNotArmored = errors.New("cipherlock: not an armored file")
)
View Source
var ErrUnsupportedIdentity = errors.New("cipherlock: unsupported identity type")

ErrUnsupportedIdentity is returned when an asymmetric identity type is not recognized.

View Source
var ErrVersionMismatch = errors.New("cipherlock: unsupported version")

ErrVersionMismatch is returned when the cipherlock format version is not supported.

View Source
var MagicArmorHeader = []byte(armorHeader)

MagicArmorHeader is the ASCII armor header line as a byte slice. It can be used to detect whether a byte stream uses the cipherlock armored format before calling Unarmor or IsArmored.

Functions

func Armor added in v1.1.0

func Armor(w io.Writer, data []byte) error

Armor writes data to w in ASCII-armor format (base64 encoded with header/footer lines). It returns any write error from the underlying io.Writer.

Example
var encrypted bytes.Buffer
Encrypt(&encrypted, bytes.NewReader([]byte("armored data")), []byte("password"), nil)

var armored bytes.Buffer
if err := Armor(&armored, encrypted.Bytes()); err != nil {
	return
}

func Decrypt

func Decrypt(dst io.Writer, src io.Reader, password []byte) error

Decrypt decrypts data read from src using password and writes plaintext to dst. It supports all format versions (v2, v3, v0x04 multi-key, v0x05 stream, v0x06 stream with encrypted metadata, v0x07 streaming multi-recipient). Returns ErrInvalidFormat, ErrVersionMismatch, ErrAuthFailed, or ErrChecksumMismatch on failure. To recover FileMeta attached to a v0x06/v0x07 container use DecryptWithMeta.

Example
var encrypted bytes.Buffer
Encrypt(&encrypted, bytes.NewReader([]byte("hello")), []byte("password"), nil)

var plaintext bytes.Buffer
err := Decrypt(&plaintext, &encrypted, []byte("password"))
if err != nil {
	return
}

func DecryptAsymmetric added in v1.1.1

func DecryptAsymmetric(dst io.Writer, src io.Reader, identity *X25519Identity) error

DecryptAsymmetric decrypts a v0x08 asymmetric cipherlock file from src and writes the plaintext to dst. The identity is used to unseal the file key.

It returns ErrInvalidFormat, ErrVersionMismatch, ErrAuthFailed, or ErrChecksumMismatch on failure.

func DecryptContext added in v1.1.1

func DecryptContext(ctx context.Context, dst io.Writer, src io.Reader, password []byte) error

DecryptContext is a context-aware wrapper around Decrypt. It cancels decryption if the context is done before completion.

NOTE: Context cancellation does not interrupt an in-progress Argon2id key derivation. The spawned goroutine runs the KDF to completion even after ctx is cancelled.

func DecryptDir

func DecryptDir(source, dest string, password []byte) error

DecryptDir decrypts a cipherlock file containing a tar.gz archive and extracts it. The output directory defaults to source without the .cipherlock or .encrypted suffix.

It returns errors from os.Open, Decrypt, or gzip/tar extraction, including ErrInvalidFormat, ErrVersionMismatch, or ErrAuthFailed from the decrypt step.

func DecryptDirContext added in v1.1.1

func DecryptDirContext(ctx context.Context, source, dest string, password []byte) error

DecryptDirContext is a context-aware wrapper around DecryptDir. It cancels directory decryption if the context is done before completion.

NOTE: Context cancellation does not interrupt an in-progress Argon2id key derivation. The spawned goroutine runs the KDF to completion even after ctx is cancelled.

func DecryptFile

func DecryptFile(source, dest string, password []byte) error

DecryptFile decrypts a source file and writes the plaintext to a destination file. If dest is empty, the .encrypted or .cipherlock suffix is stripped, or .decrypted is appended.

It returns errors from os.Open/os.Create, or ErrInvalidFormat, ErrVersionMismatch, ErrAuthFailed, or ErrChecksumMismatch.

func DecryptFileV1

func DecryptFileV1(source, dest string, password []byte) error

DecryptFileV1 decrypts a v1-format encrypted file using PBKDF2 key derivation. This is provided for backward compatibility with legacy cipherlock files.

WARNING: The entire file is loaded into memory. Large legacy files may cause out-of-memory crashes. Files >= 1 GiB are rejected upfront.

It returns ErrCorrupted if the ciphertext is too large (>= 1 GiB) and ErrInvalidFormat if the ciphertext is too short.

func DecryptStream added in v1.1.1

func DecryptStream(dst io.Writer, src io.Reader, password []byte) error

DecryptStream decrypts a stream-format cipherlock file from src and writes plaintext to dst. It is a convenience wrapper around DecryptStreamMeta that discards the FileMeta.

It returns ErrInvalidFormat if src does not start with the cipherlock magic, ErrVersionMismatch for an unrecognized version, ErrAuthFailed on wrong password or tampered ciphertext, or ErrChecksumMismatch if the embedded checksum does not match.

func DecryptStreamContext added in v1.1.1

func DecryptStreamContext(ctx context.Context, dst io.Writer, src io.Reader, password []byte) error

DecryptStreamContext is a context-aware wrapper around DecryptStream. It cancels stream decryption if the context is done before completion.

NOTE: Context cancellation does not interrupt an in-progress Argon2id key derivation. The spawned goroutine runs the KDF to completion even after ctx is cancelled.

func DecryptStreamMultiContext added in v1.1.1

func DecryptStreamMultiContext(ctx context.Context, dst io.Writer, src io.Reader, password []byte) error

DecryptStreamMultiContext is a context-aware wrapper around the v0x07 streaming multi-recipient decrypt path. Metadata is always returned via ReadStreamMetaWithPassword when needed.

NOTE: Context cancellation does not interrupt an in-progress Argon2id key derivation. The spawned goroutine runs the KDF to completion even after ctx is cancelled.

func DecryptStreamV2Context added in v1.1.1

func DecryptStreamV2Context(ctx context.Context, dst io.Writer, src io.Reader, password []byte) error

DecryptStreamV2Context is a context-aware wrapper around DecryptStreamV2. It cancels v0x06 streaming decryption if the context is done before completion.

NOTE: Context cancellation does not interrupt an in-progress Argon2id key derivation. The spawned goroutine runs the KDF to completion even after ctx is cancelled.

func Encrypt deprecated

func Encrypt(dst io.Writer, src io.Reader, password []byte, config *Config) error

Encrypt encrypts data read from src using password and writes ciphertext to dst. It is a convenience wrapper around EncryptStream and always produces the streaming format (v0x05). The config parameter controls Argon2 parameters and whether to include a checksum. Returns ErrAuthFailed if authentication fails.

Deprecated: Encrypt is identical to EncryptStream. Prefer EncryptStream for clarity.

Example
var buf bytes.Buffer
err := Encrypt(&buf, bytes.NewReader([]byte("hello")), []byte("password"), nil)
if err != nil {
	return
}

func EncryptAsymmetric added in v1.1.1

func EncryptAsymmetric(dst io.Writer, src io.Reader, recipients []*X25519Recipient, config *Config) error

EncryptAsymmetric encrypts src to dst using the v0x08 asymmetric streaming format. The data is encrypted with a random file key using AES-256-GCM, and the file key is sealed once per recipient. Each recipient can independently decrypt the file.

It returns an error if no recipients are provided.

func EncryptContext added in v1.1.1

func EncryptContext(ctx context.Context, dst io.Writer, src io.Reader, password []byte, config *Config) error

EncryptContext is a context-aware wrapper around Encrypt. It cancels encryption if the context is done before completion.

NOTE: Context cancellation does not interrupt an in-progress Argon2id key derivation. The spawned goroutine runs the KDF to completion even after ctx is cancelled. Only the AES-GCM encrypt phase can be cancelled early.

Example
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

var buf bytes.Buffer
err := EncryptContext(ctx, &buf, bytes.NewReader([]byte("context data")), []byte("password"), nil)
if err != nil {
	return
}

func EncryptDir

func EncryptDir(source, dest string, password []byte, config *Config) error

EncryptDir encrypts a directory by tar+gzipping it and encrypting the archive. The result is written to dest (or source+.cipherlock if dest is empty).

It returns errors from os.Create, tar/gzip writing, or Encrypt.

func EncryptDirContext added in v1.1.1

func EncryptDirContext(ctx context.Context, source, dest string, password []byte, config *Config) error

EncryptDirContext is a context-aware wrapper around EncryptDir. It cancels directory encryption if the context is done before completion.

NOTE: Context cancellation does not interrupt an in-progress Argon2id key derivation. The spawned goroutine runs the KDF to completion even after ctx is cancelled.

func EncryptFile

func EncryptFile(source, dest string, password []byte, config *Config) error

EncryptFile encrypts a source file and writes the ciphertext to a destination file. If dest is empty, the source path with a .encrypted suffix is used.

The output format is v0x05 (streaming) by default. When config.FileMeta is set the output is v0x06 so the metadata chunk is encrypted (v0x05 would leak the original filename and mtime in the cleartext header). EncryptFile explicitly selects EncryptStreamV2 in that case.

It returns errors from os.Open/os.Create, or any error returned by Encrypt or EncryptStreamV2 (see those functions for details).

func EncryptMulti deprecated added in v1.1.1

func EncryptMulti(dst io.Writer, src io.Reader, passwords [][]byte, config *Config) error

EncryptMulti encrypts data using multiple passwords, each of which can decrypt independently. It generates a random file key, seals it under each password, and stores all recipient entries in the header. Returns ErrAtLeastOnePassword if no passwords are provided.

Deprecated: EncryptMulti produces a non-streaming v0x04 file, which buffers the full plaintext in memory. New code should use EncryptStreamMulti (v0x07) for streaming, or EncryptStreamV2 (v0x06) for single-recipient. The v0x04 format is still readable and remains for backward compatibility.

Example
var buf bytes.Buffer
passwords := [][]byte{[]byte("alice"), []byte("bob"), []byte("charlie")}
src := bytes.NewReader([]byte("shared secret"))
err := EncryptMulti(&buf, src, passwords, nil)
if err != nil {
	return
}

func EncryptMultiContext deprecated added in v1.1.1

func EncryptMultiContext(ctx context.Context, dst io.Writer, src io.Reader, passwords [][]byte, config *Config) error

EncryptMultiContext is a context-aware wrapper around EncryptMulti. It cancels multi-key encryption if the context is done before completion.

Deprecated: EncryptMulti (and this wrapper) is retained only for v0x04 backward compatibility. New code should use EncryptStreamMulti (v0x07).

NOTE: Context cancellation does not interrupt an in-progress Argon2id key derivation. The spawned goroutine runs the KDF to completion even after ctx is cancelled.

func EncryptStream added in v1.1.1

func EncryptStream(dst io.Writer, src io.Reader, password []byte, config *Config) error

EncryptStream encrypts src using password with a streaming (chunked) format. It supports large data sizes by processing data in chunks. The config controls Argon2 parameters, chunk size, checksumming, and optional FileMeta.

When config.FileMeta is set, EncryptStream automatically produces the v0x06 format (encrypted metadata chunk) instead of v0x05 (cleartext metadata). This avoids leaking the original filename, size, or modification time in the header.

It returns a ChunkSize bound error when config.ChunkSize exceeds maxChunkSize.

Example
var buf bytes.Buffer
err := EncryptStream(&buf, bytes.NewReader([]byte("stream data")), []byte("password"), nil)
if err != nil {
	return
}

func EncryptStreamContext added in v1.1.1

func EncryptStreamContext(ctx context.Context, dst io.Writer, src io.Reader, password []byte, config *Config) error

EncryptStreamContext is a context-aware wrapper around EncryptStream. It cancels stream encryption if the context is done before completion.

NOTE: Context cancellation does not interrupt an in-progress Argon2id key derivation. The spawned goroutine runs the KDF to completion even after ctx is cancelled.

func EncryptStreamMulti added in v1.1.1

func EncryptStreamMulti(dst io.Writer, src io.Reader, passwords [][]byte, config *Config) error

EncryptStreamMulti encrypts src to dst using the v0x07 streaming multi-recipient format. A fresh random file key encrypts the data (and optional metadata chunk), and the file key is sealed once per password. Each password can independently decrypt the file. The config controls Argon2id parameters, chunk size, optional SHA-256 checksum, and optional FileMeta. Returns ErrAtLeastOnePassword if no passwords are provided.

Unlike the legacy v0x04 EncryptMulti this routine streams the plaintext and never loads the entire input into memory, so it is safe for arbitrarily large files.

func EncryptStreamMultiContext added in v1.1.1

func EncryptStreamMultiContext(ctx context.Context, dst io.Writer, src io.Reader, passwords [][]byte, config *Config) error

EncryptStreamMultiContext is a context-aware wrapper around EncryptStreamMulti. It cancels v0x07 streaming multi-recipient encryption if the context is done before completion.

NOTE: Context cancellation does not interrupt an in-progress Argon2id key derivation. The spawned goroutine runs the KDF to completion even after ctx is cancelled.

func EncryptStreamV2 added in v1.1.1

func EncryptStreamV2(dst io.Writer, src io.Reader, password []byte, config *Config) error

EncryptStreamV2 encrypts src using password with the v0x06 streaming format. Unlike v0x05, the optional FileMeta is stored as an encrypted chunk so it is not visible without the password. Use this when you need streaming encryption and want to keep the original filename and size confidential.

It returns a ChunkSize bound error when config.ChunkSize exceeds maxChunkSize.

func EncryptStreamV2Context added in v1.1.1

func EncryptStreamV2Context(ctx context.Context, dst io.Writer, src io.Reader, password []byte, config *Config) error

EncryptStreamV2Context is a context-aware wrapper around EncryptStreamV2. It cancels v0x06 streaming encryption if the context is done before completion.

NOTE: Context cancellation does not interrupt an in-progress Argon2id key derivation. The spawned goroutine runs the KDF to completion even after ctx is cancelled.

func IsArmored added in v1.1.0

func IsArmored(data []byte) bool

IsArmored reports whether data begins with a cipherlock armor header.

func IsArmoredReader added in v1.1.0

func IsArmoredReader(r io.Reader) (bool, io.Reader, error)

IsArmoredReader peeks at the reader to determine if the stream starts with an armor header. It returns the result along with a new reader that includes the peeked bytes.

func IsEncrypted

func IsEncrypted(path string) (bool, error)

IsEncrypted reports whether the file at path is a cipherlock file. It detects both the binary format (CV2\0 magic bytes) and the ASCII-armored format (-----BEGIN CIPHERLOCK----- header). It returns false without error if the file cannot be read or is too short.

Example
dir, err := os.MkdirTemp("", "cipherlock")
if err != nil {
	return
}
defer os.RemoveAll(dir)

plainFile := filepath.Join(dir, "plain.txt")
os.WriteFile(plainFile, []byte("public data"), 0644)

encFile := plainFile + ".encrypted"
EncryptFile(plainFile, encFile, []byte("password"), nil)

ok, _ := IsEncrypted(encFile)
_ = ok

ok, _ = IsEncrypted(plainFile)
_ = ok

func NewArmorWriter added in v1.1.1

func NewArmorWriter(w io.Writer) io.WriteCloser

NewArmorWriter returns a writer that base64-encodes anything written to it and emits the result on w as ASCII-armored cipherlock data (header, wrapped at armorLineLen-character lines, footer). Close must be called to flush the base64 encoder, emit the final partial line, and write the footer; callers should defer Close.

The armor header is written on the first Write call (not on Close), so a partial output that never receives Close will have a header but no footer. Callers MUST call Close after checking the encrypt error to ensure the armor footer is present.

The returned writer is not safe for concurrent use.

func NewUnarmorReader added in v1.1.1

func NewUnarmorReader(r io.Reader) (io.Reader, error)

NewUnarmorReader returns an io.Reader that decodes ASCII-armored cipherlock data on the fly. It scans r for the armorHeader, then base64-decodes each subsequent line, and stops at armorFooter.

Use NewUnarmorReader to pipe an armored source directly into a streaming Decrypt path without buffering the full plaintext in memory. If the input does not begin with armorHeader, NewUnarmorReader returns the original reader unchanged so callers can transparently handle both armored and raw inputs.

The returned reader is not safe for concurrent reads.

func ReKey added in v1.1.1

func ReKey(dst io.Writer, src io.Reader, oldPassword, newPassword []byte, config *Config) error

ReKey decrypts data from src with oldPassword and re-encrypts it with newPassword. The output format mirrors the input format:

  • v0x05 in -> v0x05 out
  • v0x06 in -> v0x06 out (preserves the attached FileMeta)
  • v0x07 in -> v0x07 out (collapses the recipient list to newPassword)

For all streaming inputs the operation is performed without buffering the entire plaintext. For legacy v0x02/v0x03/v0x04 inputs the plaintext is held in memory during re-encryption.

It returns ErrInvalidFormat, ErrVersionMismatch, ErrAuthFailed, ErrChecksumMismatch, or ErrCorrupted from the decrypt step, or any encrypt error from the re-encrypt step.

func ReKeyContext added in v1.1.1

func ReKeyContext(ctx context.Context, dst io.Writer, src io.Reader, oldPassword, newPassword []byte, config *Config) error

ReKeyContext is a context-aware wrapper around ReKey. It cancels streaming re-key if the context is done before completion.

NOTE: Context cancellation does not interrupt an in-progress Argon2id key derivation. The spawned goroutine runs the KDF to completion even after ctx is cancelled.

func ReKeyFile added in v1.1.1

func ReKeyFile(source, dest string, oldPassword, newPassword []byte, config *Config) error

ReKeyFile decrypts a file with oldPassword and re-encrypts it with newPassword. If dest is empty, the source file is overwritten in place.

The in-place case (source == dest) is implemented as a write to a sibling tempfile followed by Shred(source) + atomic rename. This guarantees the source is preserved if the rekey fails partway: the old behavior was to os.Create(dest) which truncates the file to zero bytes before any decryption runs, leaving the user with an empty file and a "wrong password" error.

It returns errors from os.Open/os.Create, or any error from ReKey (ErrInvalidFormat, ErrAuthFailed, etc.).

Example
dir, err := os.MkdirTemp("", "cipherlock")
if err != nil {
	return
}
defer os.RemoveAll(dir)

src := filepath.Join(dir, "secret.txt")
os.WriteFile(src, []byte("my secret data"), 0644)

encrypted := src + ".encrypted"
oldPass := []byte("old-password")
EncryptFile(src, encrypted, oldPass, nil)

newPass := []byte("new-password")
if err := ReKeyFile(encrypted, "", oldPass, newPass, nil); err != nil {
	return
}

func SerializeX25519Identity added in v1.1.1

func SerializeX25519Identity(identity *X25519Identity, passphrase []byte) ([]byte, error)

SerializeX25519Identity serializes an identity's private key to a PEM-like armored format. If passphrase is non-nil, the key is encrypted with Argon2id + AES-256-GCM.

func Shred added in v1.1.0

func Shred(path string) error

Shred securely overwrites a file with random data followed by zeros, then removes it.

Example
dir, err := os.MkdirTemp("", "cipherlock")
if err != nil {
	return
}
defer os.RemoveAll(dir)

path := filepath.Join(dir, "secret.txt")
os.WriteFile(path, []byte("sensitive data"), 0644)

if err := Shred(path); err != nil {
	return
}

func ShredWith added in v1.2.0

func ShredWith(path string, fn ShredProgressFn) error

ShredWith is like Shred but calls fn after each write to report progress. fn may be nil.

func Unarmor added in v1.1.0

func Unarmor(r io.Reader) ([]byte, error)

Unarmor reads ASCII-armor encoded data from r and returns the decoded bytes. It buffers the entire stream into memory; for large armored inputs use NewUnarmorReader to stream the decoded bytes into a downstream reader.

It returns ErrNotArmored if the input does not contain a valid armor header, or a base64 decode error if the encapsulated data is malformed.

func UnarmorBytes added in v1.1.0

func UnarmorBytes(data []byte) ([]byte, error)

UnarmorBytes decodes ASCII-armor encoded data and returns the original bytes.

It returns ErrNotArmored if the data does not contain a valid armor header, or a base64 decode error if the encapsulated portion is malformed.

Example
var encrypted bytes.Buffer
Encrypt(&encrypted, bytes.NewReader([]byte("secret")), []byte("mypass"), nil)

var armored bytes.Buffer
Armor(&armored, encrypted.Bytes())

raw, err := UnarmorBytes(armored.Bytes())
if err != nil {
	return
}

var plaintext bytes.Buffer
if err := Decrypt(&plaintext, bytes.NewReader(raw), []byte("mypass")); err != nil {
	return
}

Types

type Config

type Config struct {
	SaltLen     int
	Time        uint32
	Memory      uint32
	Threads     uint8
	KeyLen      uint32
	Checksum    bool
	ChunkSize   int
	FileMeta    *FileMeta
	Compression bool
}

Config holds encryption parameters including Argon2id key derivation settings, checksum behavior, chunk size, optional file metadata, and compression.

func (*Config) ApplyProfile added in v1.1.1

func (c *Config) ApplyProfile(p *Profile)

ApplyProfile applies non-zero Profile fields to the Config. Only Time, Memory, Threads, and Checksum are applied; zero values are skipped.

func (*Config) Validate added in v1.1.1

func (c *Config) Validate() error

Validate checks that all Config fields are within valid bounds. Returns nil if the configuration is valid, or ErrConfigInvalid wrapping a descriptive message if not.

type FileMeta added in v1.1.1

type FileMeta struct {
	Name      string
	Size      int64
	ModTime   time.Time
	ExpiresAt time.Time
}

FileMeta contains metadata about an encrypted file including its name, size, and modification time.

func DecryptAsymmetricWithMeta added in v1.2.0

func DecryptAsymmetricWithMeta(dst io.Writer, src io.Reader, identity *X25519Identity) (*FileMeta, error)

DecryptAsymmetricWithMeta is the metadata-aware form of DecryptAsymmetric. The returned *FileMeta is non-nil when the encrypted file was created with FileMeta attached (original filename, size, modification time).

func DecryptFileWithMeta added in v1.1.1

func DecryptFileWithMeta(source, dest string, password []byte) (*FileMeta, error)

DecryptFileWithMeta is the metadata-aware form of DecryptFile. The returned *FileMeta is non-nil only when the source was a v0x06 or v0x07 container with a FileMeta attached.

It returns errors from os.Open/os.Create, or any error returned by DecryptWithMeta (see that function for details).

func DecryptStreamMeta deprecated added in v1.1.1

func DecryptStreamMeta(dst io.Writer, src io.Reader, password []byte) (*FileMeta, error)

DecryptStreamMeta decrypts a stream-format cipherlock file (v0x05, v0x06, or v0x07) and returns the FileMeta attached at encrypt time. The plaintext is streamed to dst. The returned *FileMeta is nil if the source had no metadata.

Deprecated: DecryptStreamMeta is identical to DecryptWithMeta. Prefer DecryptWithMeta.

It returns ErrInvalidFormat, ErrVersionMismatch, ErrAuthFailed, or ErrChecksumMismatch on failure.

func DecryptStreamMulti added in v1.1.1

func DecryptStreamMulti(dst io.Writer, src io.Reader, password []byte) (*FileMeta, error)

DecryptStreamMulti handles the v0x07 streaming multi-recipient body. The magic header and version byte have already been consumed; r contains the recipient list followed by the chunked body. Returns the FileMeta when present.

It returns ErrAuthFailed on wrong password or tampered ciphertext, or ErrCorrupted for malformed input.

func DecryptStreamMultiFromReader added in v1.1.1

func DecryptStreamMultiFromReader(dst io.Writer, src io.Reader, password []byte) (*FileMeta, error)

DecryptStreamMultiFromReader is a convenience wrapper for DecryptStreamMulti that reads and validates the magic + version prefix.

It returns ErrInvalidFormat if src does not start with the cipherlock magic, or ErrVersionMismatch for an unrecognized version.

func DecryptStreamV2 added in v1.1.1

func DecryptStreamV2(dst io.Writer, src io.Reader, password []byte) (*FileMeta, error)

DecryptStreamV2 decrypts a v0x06 stream-format cipherlock file. The returned FileMeta is non-nil only if the source file was encrypted with a FileMeta attached.

It returns ErrInvalidFormat, ErrAuthFailed, ErrCorrupted, or ErrChecksumMismatch.

func DecryptWithMeta added in v1.1.1

func DecryptWithMeta(dst io.Writer, src io.Reader, password []byte) (*FileMeta, error)

DecryptWithMeta is the metadata-aware form of Decrypt. The returned *FileMeta is non-nil only when the source was encrypted with v0x06 or v0x07 and had a FileMeta attached; v0x02 through v0x05 return nil. It exists so that downstream code can recover the original filename and modification time without an extra ReadStreamMetaWithPassword call.

It returns ErrInvalidFormat if src does not start with the cipherlock magic, ErrVersionMismatch for an unrecognized format version, ErrAuthFailed on wrong password or tampered ciphertext, or ErrChecksumMismatch if the embedded SHA-256 checksum does not match the decrypted plaintext.

func DecryptWithMetaContext added in v1.1.1

func DecryptWithMetaContext(ctx context.Context, dst io.Writer, src io.Reader, password []byte) (*FileMeta, error)

DecryptWithMetaContext is a context-aware wrapper around DecryptWithMeta. It cancels decryption if the context is done before completion and returns the FileMeta (if present, v0x06/v0x07 only) alongside the decrypt error.

NOTE: Context cancellation does not interrupt an in-progress Argon2id key derivation. The spawned goroutine runs the KDF to completion even after ctx is cancelled.

func ReadStreamMeta added in v1.1.1

func ReadStreamMeta(src io.Reader) (*FileMeta, error)

ReadStreamMeta reads the FileMeta from a stream-format cipherlock header without decrypting the data. Returns nil if the file is not in stream format or has no metadata. For files that use the v0x06 or v0x07 format (which store metadata as an encrypted chunk) it returns ErrEncryptedMeta; callers should use ReadStreamMetaWithPassword in that case.

func ReadStreamMetaWithPassword added in v1.1.1

func ReadStreamMetaWithPassword(src io.Reader, password []byte) (*FileMeta, error)

ReadStreamMetaWithPassword reads the FileMeta from any stream-format cipherlock header (v0x05, v0x06, v0x07) by supplying a password. For v0x05 files the password is unused and the metadata is read in cleartext. For v0x06 / v0x07 files the password and KDF are required to unseal the metadata chunk. Returns ErrAuthFailed if the password does not unlock a v0x06 or v0x07 file. Returns nil (no error) if the file has no metadata attached.

type Profile added in v1.1.1

type Profile struct {
	Time        uint32 `json:"time"`
	Memory      uint32 `json:"memory"`
	Threads     uint8  `json:"threads"`
	Checksum    bool   `json:"checksum"`
	Compression bool   `json:"compression"`
}

Profile defines Argon2id parameters that can be applied to a Config. Fields are JSON-tagged for serialization.

type ShredProgressFn added in v1.2.0

type ShredProgressFn func(pass, totalPasses int, bytesWritten, fileSize int64)

type X25519Identity added in v1.1.1

type X25519Identity struct {
	PrivateKey []byte // 32 bytes seed/scalar
	PublicKey  []byte // 32 bytes point
}

X25519Identity is a private key that can decrypt data encrypted to its public key.

func DeserializeX25519Identity added in v1.1.1

func DeserializeX25519Identity(data, passphrase []byte) (*X25519Identity, error)

DeserializeX25519Identity deserializes an identity from the PEM-like armored format. If the identity was encrypted, passphrase must be provided.

func GenerateX25519Keypair added in v1.1.1

func GenerateX25519Keypair() (*X25519Identity, error)

GenerateX25519Keypair generates a new X25519 key pair from crypto/rand.

func IdentityFromSSHPrivateKey added in v1.2.0

func IdentityFromSSHPrivateKey(pemData []byte) (*X25519Identity, error)

IdentityFromSSHPrivateKey parses a PEM-encoded SSH private key (Ed25519) and returns an X25519Identity derived from it. Ed25519 and X25519 share the same curve (Curve25519); the seed is converted via SHA-512 + standard X25519 clamping. The resulting identity can be used to decrypt files that were encrypted to the derived X25519 public key.

For RSA and ECDSA keys, it returns ErrUnsupportedKeyType.

func X25519IdentityFromPrivateKey added in v1.1.1

func X25519IdentityFromPrivateKey(privKey []byte) (*X25519Identity, error)

X25519IdentityFromPrivateKey derives the public key from a private key seed.

type X25519Recipient added in v1.1.1

type X25519Recipient struct {
	PublicKey []byte // 32 bytes
}

X25519Recipient is a public key that can encrypt data for its corresponding identity.

func NewX25519Recipient added in v1.1.1

func NewX25519Recipient(pubKey []byte) (*X25519Recipient, error)

NewX25519Recipient creates a recipient from a raw 32-byte public key.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL