An idiomatic R interface to the age file encryption format. Encrypt and decrypt raw vectors, files, and strings — to public-key recipients or with a passphrase — with optional ASCII armor.
The cryptography is a vendored copy of the C implementation agec, and randomness
comes from the operating system, so the package has no external
library dependencies (no OpenSSL, no Go or Rust toolchain, no
age binary to install). Files it produces interoperate with
the reference age
implementation in both directions.
Install the development version from GitHub:
# install.packages("pak")
pak::pak("pedrobtz/agecrypt")Once released on CRAN:
install.packages("agecrypt")Either way there is nothing else to install — no system libraries and no external programs.
age has two kinds of keys, and this package keeps that vocabulary:
age1…)
you encrypt to;AGE-SECRET-KEY-1…) you decrypt with.library(agecrypt)
id <- age_keygen() # a new identity (keypair)
rec <- age_pubkey(id) # its recipient (public) string
id
#> <age_identity>
#> public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8pThe secret key of a generated identity is created in C and
kept there — the age_identity object never exposes it, and
its print method shows only the public key. (Identities you supply
yourself, as AGE-SECRET-KEY-1… strings or key files,
unavoidably exist in R since you pass them in.)
Three I/O shapes, all following the same recipients = /
identities = pattern.
ct <- age_encrypt_raw(charToRaw("hello world"), recipients = rec)
rawToChar(age_decrypt_raw(ct, identities = id))
#> [1] "hello world"Ideal for secrets pulled from a vault or object store and decrypted without ever writing plaintext to disk:
creds <- readBin("secrets.yaml.age", "raw", 1e6) |>
age_decrypt_raw(identities = id) |>
rawToChar() |>
yaml::yaml.load()age_encrypt_file("data.csv", recipients = rec) # writes data.csv.age
age_decrypt_file("data.csv.age", identities = id) # writes data.csvLarge files are streamed chunk by chunk in C, so a multi-gigabyte
file is never loaded into an R vector. Existing outputs are not
overwritten unless you pass overwrite = TRUE.
ct <- age_encrypt_text("db_password_123", recipients = rec)
cat(ct)
#> -----BEGIN AGE ENCRYPTED FILE-----
#> ...
#> -----END AGE ENCRYPTED FILE-----
age_decrypt_text(ct, identities = id)
#> [1] "db_password_123"age_encrypt_text() is armored by default so the result
pastes cleanly into a chat message, a commit, or an
.Renviron.
Encrypt once, and each recipient can decrypt with their own identity:
age_encrypt_file(
"backup.tar",
recipients = c(
"age1alice…",
"age1bob…",
"age1backup…"
)
)Write a key to disk in the standard age key-file format:
age_keygen("~/.config/age/key.txt")Anywhere an identities = argument appears, you can pass
an age_identity object, an inline
"AGE-SECRET-KEY-1…" string, or a path to a key file — it is
auto-detected per element:
age_decrypt_file("data.csv.age", identities = "~/.config/age/key.txt")
age_decrypt_raw(ct, identities = Sys.getenv("AGE_KEY")) # inline, no fileYou can also keep the identity in the OS keychain via the optional keyring
package:
secret <- readLines("~/.config/age/key.txt")
secret <- secret[startsWith(secret, "AGE-SECRET-KEY-1")]
keyring::key_set_with_value("agecrypt", "default", secret)
age_decrypt_file("data.csv.age",
identities = keyring::key_get("agecrypt", "default"))A key file may hold several identities; decryption tries each until one matches.
A separate set of verbs (never a passphrase = flag on
the recipient-based functions, to keep each call unambiguously one
mode):
ct <- age_encrypt_raw_passphrase(charToRaw("secret"), passphrase = "correct horse")
rawToChar(age_decrypt_raw_passphrase(ct, passphrase = "correct horse"))
#> [1] "secret"
age_encrypt_file_passphrase("notes.txt") # prompts securely (via askpass)
age_decrypt_file_passphrase("notes.txt.age")If passphrase = NULL and the session is interactive, you
are prompted securely via askpass.
The scrypt work factor is a plain log_n argument (default
18).
Failures are signalled with typed conditions, all inheriting from
age_error, so batch callers can tell a wrong key from
corrupt ciphertext from a disk problem:
| Condition class | Raised when |
|---|---|
age_error_recipient |
a recipient string fails to parse |
age_error_identity |
an identity string or key file fails to parse |
age_error_encrypt |
encryption fails after inputs are valid |
age_error_decrypt |
no identity/passphrase matches, or authentication fails |
age_error_io |
a file cannot be read or written |
tryCatch(
age_decrypt_file("data.csv.age", identities = id),
age_error_decrypt = function(e) log_and_retry(e),
age_error_identity = function(e) stop("misconfigured key", call. = FALSE)
)| Public key (recipients) | Passphrase | |
|---|---|---|
| raw | age_encrypt_raw() / age_decrypt_raw() |
age_encrypt_raw_passphrase() /
age_decrypt_raw_passphrase() |
| file | age_encrypt_file() /
age_decrypt_file() |
age_encrypt_file_passphrase() /
age_decrypt_file_passphrase() |
| text | age_encrypt_text() /
age_decrypt_text() |
— |
| keys | age_keygen(), age_pubkey(),
age_identity(), age_identity_free() |
age CLI
fixtures are exercised by the test suite, and reverse interoperability
is checked when those fixtures are generated.getentropy / arc4random_buf
/ BCryptGenRandom).age_identity object.
Secrets and passphrases you pass in as strings, or read from a key file,
necessarily live in R for their normal lifetime — the package does not
(and cannot) hide those.Supported: X25519 recipients, scrypt passphrases, and ASCII armor — the full age v1 format for the common cases.
Not currently supported: SSH-key recipients
(ssh-ed25519 …), and plugin / hardware / post-quantum
recipients. These would require new stanza types in the C backend rather
than R glue.
Concurrency: encryption and decryption must run on R’s main thread, one operation at a time — the usual way R calls compiled code. The C backend keeps some process-global state, so the functions are not reentrant and must not be called from multiple threads in parallel (e.g. a threaded worker pool). Normal sequential use is unaffected.
age CLIawesome-age
— other implementations and toolsMIT © the agecrypt authors. The vendored agec C sources
are distributed under the 0BSD license; see
inst/COPYRIGHTS.