Age encryption cookbook

Many locks for one secret

One of the niftiest tools that I have been using a lot nowadays is the deceptively simple encryption/decryption command line tool called age by Filippo Valsorda.

Encrypting secrets using keypairs instead of a single passphrase is obviously a more secure option, as it separates the concerns for encryption and decryption. Therefore you can share around the public key without fear for others to encrypt data for you that only you can decrypt.

Traditionally, we have been using tools like gpg e.g. while using eyaml in Puppet, etc. However, gpg comes with its own overhead of key management - multiple keyrings, tool configuration, etc. which makes the use case of simple encryption to be fairly complicated.

age packs a lot of functionality into its dead simple CLI interface - a single file to “manage” the keypair. It requires zero configuration. More importantly, it supports not just its own X25519-based key pair format, but it can even use SSH key pairs for encryption. For simple use cases, it even supports symmetric passphrases.

I have been keeping this document in my PKM for a while, and I thought it best to share in public as a cookbook as well.

Installation

I mostly work on Mac nowadays, and prefer to use homebrew for installing age. Instructions for other platforms are in the official docs.

$ brew install age
...

Usage

Key generation

Before you can accept encrypted content, you need to generate your keypair. Key generation gives us a public key and a private key. The public key is also referred to as a recipient in the tool documentation.

$ age-keygen
# created: 2023-07-06T22:35:40-04:00
# public key: age1lm45u4v4nka4s3uv68dpfv2c33fdgak6mjvdn5sfsv8rlvss9vyss927mg
AGE-SECRET-KEY-167YP7YLNUE74Y7PA3X9SKXCS92JJ2DQCVN584WV20Y9WW5VL67ZSFFLDZ4

Specifying a file to save, using -o, will store the previously shown output to the file. But it hides the secret key from being displayed to the console. It still displays the public part.

$ age-keygen -o key.txt
Public key: age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t

$ cat key.txt
# created: 2023-07-06T22:39:59-04:00
# public key: age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t
AGE-SECRET-KEY-1FP43QXCQ3VM4EJANLG3DJ0VRHZD9FY0HW4RM5WVP9JNPZPVARD4SGAXD94

You can extract the public key from a key file using age-keygen -y, similar to how you might have used ssh-keygen -y for ssh keys.

$ age-keygen -y key.txt
age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t

However, unlike ssh-keygen, you can extract public keys from a bunch of age identity files in one shot.

$ cat key*.txt | age-keygen -y
age1sa3srvuxwx4g4sshgxvgc0cx8c5kwukv5kmvsmnp3d43levctehsla3m2f
age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t

Encryption

You encrypt using a recipient name. The output is by default in an efficient binary form. You can use the -a option to armor the output into a printable version.

$ cat /etc/hosts | age -r age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t -o hosts-1.age

$ file hosts-1.age
hosts-1.age: data

$ cat /etc/hosts | age -r age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t -a
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKdXpDQURkeVUzNmxKdk9X
ckgvS2FYZ2laRjlCZzUvV3IwYzMwcGNpakY0Cm1WeC9DakFFMUZjWGdqalYweHB3
MkppYlV3YXhNenpxSlg4MEhsV1R2ckEKLS0tIFQvcjMvVFZLM1UrRWlRYTVNenFl
WU93bFcwT011dFpLektWTnJzZkFoNVUKNd5jWDu9jzly/x9NPRFwTiTSPDrSn5A3
8UJ6sPtY4s2z6rlH6v2di3/PYympUNsJFlYIVchOor8YhivI80JWOZyMdge2h6Qu
49C+pEftVeyqYcaAhusgcQREWgXLp53GbpLbsWst4tiXFE5Hku4M5nLGIMrPjDw7
bPR+/9GFLVyBEAF27iwFGpFsR4ywKzgwgbLWJBvzZfv13/g6GDu8XAmMOUc3aGr9
7oUq6SNNmHWhpVImZc/h2a73LVPZqmXj515jx0vIYaBsaZMEMPFybiIETrNXTuQO
UEl6WrDniCtfxz9hvNMt4kkRoj2F8ebv/yYK+tc=
-----END AGE ENCRYPTED FILE-----

Repeated encryption of the same file for the same recipient will give different output. Good thing to keep in mind if you were planning on using file hashes to check if the encrypted files have been generated by the same key. You shouldn’t.

$ cat /etc/hosts | age -r age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t -o hosts-2.age
$ diff -u hosts-1.age hosts-2.age
Binary files hosts-1.age and hosts-2.age differ

You can specify multiple recipients by repeating the -r parameter, or put them all in a recipients file and specifying the file using -R.

$ age-keygen -o key-alt.txt
Public key: age1sa3srvuxwx4g4sshgxvgc0cx8c5kwukv5kmvsmnp3d43levctehsla3m2f

$ cat key*.txt | age-keygen -y > recipients.txt

$ cat recipients.txt
age1sa3srvuxwx4g4sshgxvgc0cx8c5kwukv5kmvsmnp3d43levctehsla3m2f
age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t

$ cat /etc/hosts | age -R recipients.txt -o hosts-3.age

The same encrypted file can be decrypted by both the recipients. That is, you don’t have to generate different encrypted versions for each recipient.

The recipients file format supports comments, so you can document the recipient keys.

$ cat recipients.txt
# Build key
age1sa3srvuxwx4g4sshgxvgc0cx8c5kwukv5kmvsmnp3d43levctehsla3m2f

# QA key
age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t

You can mix SSH public keys and age public keys in the recipients file.

$ cat ~/.ssh/id_github.pub ~/.ssh/id_rsa.pub > r.txt

$ age-keygen -y ~/.age-chezmoi.txt >> r.txt

$ age -R r.txt -a < /etc/hosts
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1yc2EgU0wyUlJBCk5OS2QzSzRl
ZCtVUmJxMXgwVVRBNjFJWU00emxwbmRFbmlsWDRCUlhwNjF2WEJoZUJtZysxSEVm
...

Decryption

Here the same file encrypted for both the recipients can be decrypted with their respective private keys.

$ age -d -i key.txt hosts-3.age
##
# Host Database
#
...

$ age -d -i key-alt.txt hosts-3.age
##
# Host Database
#
...

Identity encryption

Normally the encrypted file can only be decrypted by the recipient public key. Even you cannot decrypt it afterwards.

You can specify your own public key as recipient to work around it. Or you can just specify your key file (using -i), containing your private key, for an identity encryption.

Here the file is being encrypted for a different recipient, as well as for the person having a private key locally.

$ age -e -i key.txt -r $(age-keygen -y key-alt.txt) -a /etc/hosts | age -d -i key.txt
##
# Host Database
#
To use the -i parameter, you have to explicitly use the -e or --encrypt parameter to specify you are trying to do encryption.

Using SSH keys for encryption

New versions of age can also use your existing SSH key pairs for encryption/decryption.

$ cat /etc/hosts | age -R ~/.ssh/id_rsa.pub -o hosts-4.age

$ age -d -i ~/.ssh/id_rsa hosts-4.age
##
# Host Database
#
...

While doing decryption, if your SSH private key was encrypted, you will be asked for the passphrase.

Using a simple password for encryption instead of a key pair

For even simpler use cases, age also supports simple passphrase based encryption.

$ cat /etc/hosts | age -p -o hosts-5.age
Enter passphrase (leave empty to autogenerate a secure one):
Confirm passphrase:

$ age -d hosts-5.age
Enter passphrase:
##
# Host Database
#

Other uses

age is a simple data encryption/decryption tool. It is not aware of file formats. So if you wanted to encrypt just a single key value in an YAML file, you will need to extract the value from the file, provide it to age for encryption, and then add the (probably armored) value back to the file.

Higher level tools like Mozilla SOPS understand file structure of formats like YAML, JSON etc. and can use age for encryption/decryption for individual values in these data structures.

tech
Converting audio to text from command line using commercial and opensource tools Using Giscus for commenting on this blog