NAME
Crypt::Age::Simple - DWIM interface to age files from Perl
VERSION
This document describes Crypt::Age::Simple version 0.001.
SYNOPSIS
use Crypt::Age::Simple qw< :scrypt :x25519 :kdf >;
############ X25519 interface ######################################
($pub, $sec) = age_x25519_generate_keypair();
# you can pass one single recipient or many in an array ref
$encrypted = age_x25519_encrypt($pub, 'something to encrypt!');
$encrypted = age_x25519_encrypt([$pub], 'something to encrypt!');
# ditto when decrypting
$cleartext = age_x25519_decrypt($sec, $encrypted);
$cleartext = age_x25519_decrypt([$sec], $encrypted);
# there are variants for file that play nice with memory. Works also
# when passing scalar references as $input or $output
age_x25519_encrypt_file($pub, $inpath, $outpath);
age_x25519_decrypt_file($pub, $inpath, $outpath);
# the swiss-army knife is actually the filehandle-based interface, it
# might even work on sockets - provided that end-of-file means the
# end of the data in either direction!
age_x25519_encrypt_fh($pub, \*STDIN, \*STDOUT);
age_x25519_decrypt_fh($sec, \*STDIN, \*STDOUT);
# if you happen to have a "regular" key file generated by age-keygen
# these functions help you extract the public or the private key
my $keydata = slurp($keyfile_path); # slurp is left as an exercise
my $public = age_x25519_extract_public_key($keydata);
my $secret = age_x25519_extract_secret_key($keydata);
############ scrypt interface ######################################
$password = rand();
$encrypted = age_scrypt_encrypt($password, 'my little secret!');
$decrypted = age_scrypt_decrypt($password, $encrypted);
# file-based and filehandle-based variants here too
age_scrypt_encrypt_file($password, $inpath, $outpath);
age_scrypt_decrypt_file($password, $inpath, $outpath);
age_scrypt_encrypt_fh($password, \*STDIN, \*STDOUT);
age_scrypt_decrypt_fh($password, \*STDIN, \*STDOUT);
############ key-derivation function interface #####################
# if you feel lucky, the password_to_key function will allow using
# passwords/passphrases as random inputs for generating asymmetric
# keys in a deterministic and reproducible way. No guarantees that
# this is a good idea for your use case though.
$password = rand();
# basic usage: generate a brand new key pair using Argon2id
$key = password_to_key($password);
# $key points to an anonymous hash now
# $key->{pch} contains public data that can be later used to
# re-derive the whole key from the same password
print {*STDOUT} "save this: $key->{phc}\n";
# use it
($pub, $sec) = $key->@{qw< public secret >};
# ... some time later...
# when you need to retrieve the key, make sure to have the PCH at
# hand - it holds all parameters needed to re-generate the key in a
# deterministic way and land on the same exact keypair
$phc = ...;
$key = password_to_key($password, $phc);
($pub, $sec) = $key->@{qw< public secret >};
# you can use a partial PHC to drive the key generation process, e.g.
# to select a specific KDF or its parameters as time moves on
$key = password_to_key($password, '$agex-scrypt$ln=10');
DESCRIPTION
This module provides a few functions to ease working with encryption according to age.
INTERFACE
The interface is composed of functions that can be imported or called with their fully qualified name. Nothing is exported by default, although everything can be imported at once with target :all.
scrypt Functions
The functions in this group can all be imported at once with import target :scrypt, like this:
use Crypt::Age::Simple qw< :scrypt >;
These functions aim at supporting working with symmetric encryption using the native scrypt approach in age. The first argument is always the $password, other following arguments depend on the specific function.
age_scrypt_decrypt
my $decrypted = age_scrypt_decrypt($password, $encrypted);
Decrypt the input $encrypted string using $password. String $encrypted must contain data that was properly encrypted with age in its entirety, i.e. including the header, stanzas and so on.
Returns the decrypted data octets if successful; throws an Ouch exception otherwise.
age_scrypt_decrypt_file
my $outcome = age_scrypt_decrypt_file($password, $in, $out);
Decrypts the input file $in to the output file $out using $password. The input file must be compliant to the age specification.
Both $in and $out can be whatever Perl's open supports, e.g. references to scalars that are opened for in-memory operations.
Returns 1 when successful; throws an Ouch exception otherwise.
age_scrypt_decrypt_fh
my $outcome = age_scrypt_decrypt_fh($password, $ifh, $ofh);
Decrypts the input filehandle $ifh to the output filehandle $ofh using $password. The input filehandle $ifh is supposed to hit end-of-file at the end of the input, i.e. there can not be other data beyond the end of the encrypted section (this is in line with age's way of working).
Returns 1 when successful; throws an Ouch exception otherwise.
age_scrypt_encrypt
my $encrypted = age_scrypt_encrypt($password, $clear);
Encrypt the input $clear string using $password.
Returns the encrypted data octets (including header stanzas and the rest) if successful; throws an Ouch exception otherwise.
age_scrypt_encrypt_file
my $outcome = age_scrypt_encrypt_file($password, $in, $out);
Encrypts the input file $in to the output file $out using $password. The output file will be compatible for use with age.
Both $in and $out can be whatever Perl's open supports, e.g. references to scalars that are opened for in-memory operations.
Returns 1 when successful; throws an Ouch exception otherwise.
age_scrypt_encrypt_fh
my $outcome = age_scrypt_encrypt_fh($password, $ifh, $ofh);
Encrypts the input filehandle $ifh to the output filehandle $ofh using $password. The input filehandle $ifh will be exhausted. The data written in the output filehandle will contain all that is needed to be compatible with age.
Returns 1 when successful; throws an Ouch exception otherwise.
X25519 Functions
The functions in this group can all be imported at once with import target :x25519, like this:
use Crypt::Age::Simple qw< :x25519 >;
These functions aim at supporting working with asymmetric encryption using the native X25519 approach in age.
age_x25519_decrypt
my $decrypted = age_x25519_decrypt($identities, $encrypted);
Decrypt the input $encrypted trying the provided $identities.
$identities can be a single identity (secret key) as a string, or an array reference of multiple identities to use for attempting the decryption.
String $encrypted must contain data that was properly encrypted with age in its entirety, i.e. including the header, stanzas and so on.
Returns the decrypted data octets if successful; throws an Ouch exception otherwise.
age_x25519_decrypt_file
my $outcome = age_x25519_decrypt_file($identities, $in, $out);
Decrypts the input file $in to the output file $out. The input file must be compliant to the age specification.
$identities can be a single identity (secret key) as a string, or an array reference of multiple identities to use for attempting the decryption.
Both $in and $out can be whatever Perl's open supports, e.g. references to scalars that are opened for in-memory operations.
Returns 1 when successful; throws an Ouch exception otherwise.
age_x25519_decrypt_fh
my $outcome = age_x25519_decrypt_fh($password, $ifh, $ofh);
Decrypts the input filehandle $ifh to the output filehandle $ofh. The input filehandle $ifh is supposed to hit end-of-file at the end of the input, i.e. there can not be other data beyond the end of the encrypted section (this is in line with age's way of working).
$identities can be a single identity (secret key) as a string, or an array reference of multiple identities to use for attempting the decryption.
Returns 1 when successful; throws an Ouch exception otherwise.
age_x25519_encrypt
my $encrypted = age_x25519_encrypt($recipients, $clear);
Encrypt the input $clear for $recipients.
$recipients can be a single recipient (public key) as a string, or an array reference of multiple recipients to use for encryption.
Returns the encrypted data octets (including header stanzas and the rest) if successful; throws an Ouch exception otherwise.
age_x25519_encrypt_file
my $outcome = age_x25519_encrypt_file($recipients, $in, $out);
Encrypts the input file $in to the output file $out for $recipients. The output file will be compatible for use with age.
$recipients can be a single recipient (public key) as a string, or an array reference of multiple recipients to use for encryption.
Both $in and $out can be whatever Perl's open supports, e.g. references to scalars that are opened for in-memory operations.
Returns 1 when successful; throws an Ouch exception otherwise.
age_x25519_encrypt_fh
my $outcome = age_x25519_encrypt_fh($recipients, $ifh, $ofh);
Encrypts the input filehandle $ifh to the output filehandle $ofh for $recipients. The input filehandle $ifh will be exhausted. The data written in the output filehandle will contain all that is needed to be compatible with age.
$recipients can be a single recipient (public key) as a string, or an array reference of multiple recipients to use for encryption.
Returns 1 when successful; throws an Ouch exception otherwise.
age_x25519_extract_public_key
my $public = age_x25519_extract_public_key($text);
Assuming to have read a file generated by age-keygen or anything compatible, you have a string that has more or less the following shape:
# created: 2026-08-11T19:51:24+02:00
# public key: age1wnwl400t8tlx6vlfzgpzsgy4hf3hhk...
AGE-SECRET-KEY-15KTVYU2FGPXCNLJ3E3T9A9XXQJKULE8W...
This function extracts the public key, so that it can be e.g. used with the encryption subs in this module.
age_x25519_extract_secret_key
my $secret = age_x25519_extract_secret_key($text);
Assuming to have read a file generated by age-keygen or anything compatible, you have a string that has more or less the following shape:
# created: 2026-08-11T19:51:24+02:00
# public key: age1wnwl400t8tlx6vlfzgpzsgy4hf3hhk...
AGE-SECRET-KEY-15KTVYU2FGPXCNLJ3E3T9A9XXQJKULE8W...
This function extracts the secret key, so that it can be e.g. used with the decryption subs in this module.
age_x25519_generate_keypair
my ($public, $secret) = age_x25519_generate_keypair();
Generate a new keypair. This is just a thin wrapper around Age::Crypt::generate_keypair.
KDF Functions
This group contains functions for handling key derivation. They can be imported via group :kdf.
password_to_key
$key_hashref = password_to_key($password);
$key_hashref = password_to_key($password, $partial_PHC);
$key_hashref = password_to_key($password, $complete_PHC);
Generate a X25519 keypair suitable for using the asymmetric encryption/decryption functions starting from a $password.
CAVEAT: using this method requires that you the user do your own assessment that it's the right tool for your situation.
The return value is a hash reference that contains at least the following keys:
phc: a string compatible with the PHC de-facto standard, that can be later used to re-derive the same public/secret keypair at a later time from the same$password.public: the public X25519 key, in age format (i.e. starting withage).secret: the secret X25519 key, in age format (i.e. starting withAGE-SECRET-KEY-).
To thwart brute force attempts, a costly key-derivation function is used to generate the secret key. The implementation supports Argon2 in all three variants and scrypt, defaulting to Argon2id.
Parameter $partial_PHC can be used to set the key derivation function and its parameters, if needed:
$partial_PCH = '$agex-argon2i';
$partial_PCH = '$agex-argon2d';
$partial_PCH = '$agex-argon2id$m=131072';
$partial_PCH = '$agex-scrypt';
$partial_PCH = '$agex-scrypt$ln=10';
Supported PHC identifiers are agex-argon2i, agex-argon2d, agex-argon2id, and agex-scrypt. Parameters are according to the corresponding PHCs for password hashing.
Parameter $complete_PHC can be provided to set all parameters for generating the keypair, including the salt. This is needed when you need to re-generate the same keypair from the same password; the right candidate for this parameter is the phc value in the hash reference returned by this function.
Typical usage:
# initial generation
my $password = get_user_password(...);
my $initial_key = password_to_key($password); # no PHC, use defaults
publish_public_key($initial_key->{public}, ...);
save_phc_for_later($initial_key->{phc}, ...);
# ... some time later, in another process...
# after receiving something encrypted with $inital_key->{public}...
my $password = get_user_password(...);
my $phc = retrieve_saved_phc(...);
my $retrieved_key = password_to_key($password, $phc);
# now $retrieved_key and $initial_key are the same and
# $retrieved_key->{secret} can be used to decrypt the received stuff
BUGS AND LIMITATIONS
Minimum perl version 5.24.
SEE ALSO
This module relies upon Crypt::Age. That module is still under active development, this one tries to pinpoint some of the do-what-I-mean things I find useful, and it's not excluded that it might eventually be merged into the other distribution. In particular, some parts are re-implemented here to support the streaming interface where data might arrive in chunks (e.g. when using a user agent in a non-blocking setup like in Mojolicious) and you want to avoid stuffing everything in memory or on disk.
CryptX is a wonderful distribution and provides all the moving parts that allow this module to actually be.
AUTHOR
Flavio Poletti <flavio@polettix.it>
COPYRIGHT AND LICENSE
Copyright 2026 by Flavio Poletti <flavio@polettix.it>
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.