NAME
Crypt::Age::Primitives - Low-level cryptographic primitives for age encryption
VERSION
version 0.001
SYNOPSIS
use Crypt::Age::Primitives;
# Generate random file key
my $file_key = Crypt::Age::Primitives->generate_file_key();
# X25519 key exchange
my ($pub, $priv) = Crypt::Age::Primitives->x25519_generate_keypair();
my $secret = Crypt::Age::Primitives->x25519_shared_secret($our_priv, $their_pub);
# Key derivation and wrapping
my $wrap_key = Crypt::Age::Primitives->derive_wrap_key($secret, $eph_pub, $rec_pub);
my $wrapped = Crypt::Age::Primitives->wrap_file_key($wrap_key, $file_key);
my $unwrapped = Crypt::Age::Primitives->unwrap_file_key($wrap_key, $wrapped);
# Payload encryption
my $payload_key = Crypt::Age::Primitives->derive_payload_key($file_key);
my $encrypted = Crypt::Age::Primitives->encrypt_payload($payload_key, $plaintext);
my $decrypted = Crypt::Age::Primitives->decrypt_payload($payload_key, $encrypted);
# Header MAC
my $mac = Crypt::Age::Primitives->compute_header_mac($file_key, $header_bytes);
DESCRIPTION
This module provides low-level cryptographic primitives for age encryption. It wraps functions from CryptX and implements the age-specific key derivation and payload encryption schemes.
This is an internal module used by Crypt::Age. Most users should use the high-level interface provided by Crypt::Age instead.
Cryptographic Primitives Used
X25519 - Key exchange (Curve25519 Diffie-Hellman)
ChaCha20-Poly1305 - AEAD encryption
HKDF-SHA256 - Key derivation
HMAC-SHA256 - Header MAC
generate_file_key
my $file_key = Crypt::Age::Primitives->generate_file_key();
Generates a random 16-byte file key using a cryptographically secure PRNG.
The file key is used to encrypt the payload and is itself encrypted for each recipient.
x25519_generate_keypair
my ($public_bytes, $private_bytes) = Crypt::Age::Primitives->x25519_generate_keypair();
Generates a new X25519 keypair. Returns raw 32-byte public and private keys.
Note: For generating age-encoded keypairs, use "generate_keypair" in Crypt::Age::Keys instead.
x25519_shared_secret
my $shared_secret = Crypt::Age::Primitives->x25519_shared_secret($our_private, $their_public);
Performs X25519 key exchange to compute a shared secret.
Parameters are raw 32-byte keys. Returns a 32-byte shared secret.
derive_wrap_key
my $wrap_key = Crypt::Age::Primitives->derive_wrap_key(
$shared_secret,
$ephemeral_public,
$recipient_public
);
Derives a wrapping key from an X25519 shared secret using HKDF-SHA256.
The salt is ephemeral_public || recipient_public (concatenated). The info string is "age-encryption.org/v1/X25519".
Returns a 32-byte key suitable for wrapping the file key.
wrap_file_key
my $wrapped_key = Crypt::Age::Primitives->wrap_file_key($wrap_key, $file_key);
Wraps a 16-byte file key using ChaCha20-Poly1305 with a zero nonce.
Returns a 32-byte value: 16 bytes ciphertext + 16 bytes authentication tag.
unwrap_file_key
my $file_key = Crypt::Age::Primitives->unwrap_file_key($wrap_key, $wrapped_key);
Unwraps a wrapped file key using ChaCha20-Poly1305.
Dies if authentication fails. Returns the 16-byte file key on success.
derive_payload_key
my $payload_key = Crypt::Age::Primitives->derive_payload_key($file_key, $nonce);
Derives a 32-byte payload encryption key from the file key and nonce using HKDF-SHA256.
The nonce (16 bytes) is used as the salt, and "payload" is the info string.
generate_payload_nonce
my $nonce = Crypt::Age::Primitives->generate_payload_nonce();
Generates a random 16-byte nonce for payload encryption.
compute_header_mac
my $mac = Crypt::Age::Primitives->compute_header_mac($file_key, $header_bytes);
Computes HMAC-SHA256 MAC over the header bytes.
First derives a MAC key from the file key using HKDF with info string "header", then computes HMAC-SHA256 of the header. Returns 32 bytes.
encrypt_payload
my $ciphertext = Crypt::Age::Primitives->encrypt_payload($payload_key, $plaintext);
Encrypts the payload using ChaCha20-Poly1305 in chunked mode.
The plaintext is split into 64 KiB chunks. Each chunk is encrypted with a unique nonce derived from a counter and a final-chunk flag. Returns the concatenated encrypted chunks.
decrypt_payload
my $plaintext = Crypt::Age::Primitives->decrypt_payload($payload_key, $ciphertext);
Decrypts a chunked payload encrypted with encrypt_payload.
Dies if any chunk fails authentication. Returns the decrypted plaintext.
SEE ALSO
Crypt::Age - Main age encryption module
CryptX - Provides all cryptographic primitives
Crypt::PK::X25519 - X25519 key exchange
Crypt::AuthEnc::ChaCha20Poly1305 - AEAD encryption
Crypt::KeyDerivation - HKDF implementation
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-crypt-age/issues.
IRC
You can reach Getty on irc.perl.org for questions and support.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <torsten@raudssus.de>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.