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 with
age).
* secret: the secret X25519 key, in age format (i.e. starting with
AGE-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
COPYRIGHT AND LICENSE
Copyright 2026 by Flavio Poletti
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.