Crypt::PK::X25519(3)

πŸ’œ Crypt::PK::X25519 β€” the manual page, but make it make sense

okay so bestie, welcome to Crypt::PK::X25519, a page in the User Contributed Perl Documentation section of the manual. It's the Perl module for asymmetric cryptography based on X25519 β€” yes, that Curve25519 bestie, the one everybody's obsessed with πŸ’…


✨ NAME ✨

Crypt::PK::X25519 β€” Asymmetric cryptography based on X25519. That's the whole vibe. Public/private key pairs, shared secrets, the works. Slay.


πŸŽ€ SYNOPSIS πŸŽ€

okay so here's your first group-chat demo β€” Alice and Bob doing the classic shared-secret handshake, no cap:

use Crypt::PK::X25519;

my $alice = Crypt::PK::X25519->new->generate_key;
my $bob = Crypt::PK::X25519->new->generate_key;
my $alice_secret = $alice->shared_secret($bob);
my $bob_secret = $bob->shared_secret($alice);
die "ERROR" unless $alice_secret eq $bob_secret;

like, they both compute the same secret and if they don't, die. iconic.

Then loading a key from hex, because sometimes you already have the key and you're just importing it:

#Load key
my $pk = Crypt::PK::X25519->new;
my $pk_hex = "EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41";
$pk->import_key_raw(pack("H*", $pk_hex), "public");
my $sk = Crypt::PK::X25519->new;
my $sk_hex = "002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651";
$sk->import_key_raw(pack("H*", $sk_hex), "private");

And here's the full glow-up tour of key generation + every export format, all at once:

#Key generation
my $pk = Crypt::PK::X25519->new->generate_key;
my $private_der = $pk->export_key_der('private');
my $public_der  = $pk->export_key_der('public');
my $private_pem = $pk->export_key_pem('private');
my $public_pem  = $pk->export_key_pem('public');
my $private_raw = $pk->export_key_raw('private');
my $public_raw  = $pk->export_key_raw('public');
my $private_jwk = $pk->export_key_jwk('private');
my $public_jwk  = $pk->export_key_jwk('public');

πŸ¦‹ DESCRIPTION πŸ¦‹

Since: CryptX-0.067 β€” yes, she's been around, and she's been serving since then πŸ’–


🌸 METHODS 🌸

πŸŽ€ new

okay so new is your main character constructor. It can make you an empty object, or slurp a key straight out of a file or a buffer. Look:

my $source = Crypt::PK::X25519->new();
$source->generate_key;

my $public_der = $source->export_key_der('public');
my $pub = Crypt::PK::X25519->new(\$public_der);

my $private_pem = $source->export_key_pem('private', 'secret', 'AES-256-CBC');
my $priv = Crypt::PK::X25519->new(\$private_pem, 'secret');

Passing $filename or "\$buffer" to new is equivalent β€” both forms immediately import the key material into the new object. fr fr, same thing, no difference, pick your fave πŸ’…


✨ generate_key ✨

She uses the bundled chacha20 PRNG via rng_make_prng(). The exact OS entropy source is handled by the underlying LibTomCrypt RNG setup β€” that's the tea, we don't gotta worry about it.

Returns the object itself (for chaining), so you can go ->new->generate_key and keep the party going πŸŽ‰

$pk->generate_key;

🧚 import_key 🧚

Loads a private or public key in DER or PEM format. Also eats hashrefs, we'll get there.

my $source = Crypt::PK::X25519->new();
$source->generate_key;

my $public_der = $source->export_key_der('public');
my $pub = Crypt::PK::X25519->new();
$pub->import_key(\$public_der);

my $private_pem = $source->export_key_pem('private', 'secret', 'AES-256-CBC');
my $priv = Crypt::PK::X25519->new();
$priv->import_key(\$private_pem, 'secret');

The same method also accepts filenames instead of buffers. convenient king πŸ‘‘

Loading private or public keys from a Perl HASH:

$pk->import_key($hashref);

# the $hashref is either a key exported via key2hash
$pk->import_key({
     curve => "x25519",
     pub   => "EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41",
     priv  => "002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651",
});

# or a hash with items corresponding to JWK (JSON Web Key)
$pk->import_key({
      kty => "OKP",
      crv => "X25519",
      d   => "AC-T0Qulco2N2OlSdyHaujJhwLsb7957S72sYx1FRlE",
      x   => "6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE",
});

πŸ’– Supported key formats πŸ’–

all formats can be loaded from a file:

my $pk = Crypt::PK::X25519->new($filename);

or from a buffer containing the key:

my $pk = Crypt::PK::X25519->new(\$buffer_with_key);

okay now the lineup, one by one, all of them, no skips:

  • X25519 private keys in PEM format
-----BEGIN X25519 PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEIAAvk9ELpXKNjdjpUnch2royYcC7G+/ee0u9rGMdRUZR
-----END X25519 PRIVATE KEY-----
  • X25519 private keys in password protected PEM format
-----BEGIN X25519 PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-CBC,DEEFD3D6B714E75A

dfFWP5bKn49aZ993NVAhQQPdFWgsTb4j8CWhRjGBVTPl6ITstAL17deBIRBwZb7h
pAyIka81Kfs=
-----END X25519 PRIVATE KEY-----
  • X25519 public keys in PEM format
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VuAyEA6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE=
-----END PUBLIC KEY-----
  • PKCS#8 private keys
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEIAAvk9ELpXKNjdjpUnch2royYcC7G+/ee0u9rGMdRUZR
-----END PRIVATE KEY-----
  • PKCS#8 encrypted private keys
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIGHMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAiS0NOFZmjJswICCAAw
DAYIKoZIhvcNAgkFADARBgUrDgMCBwQIGd40Hdso8Y4EONSRCTrqvftl9hl3zbH9
2QmHF1KJ4HDMdLDRxD7EynonCw2SV7BO+XNRHzw2yONqiTybfte7nk9t
-----END ENCRYPTED PRIVATE KEY-----
  • X25519 private keys in JSON Web Key (JWK) format

See <https://www.rfc-editor.org/rfc/rfc8037>

 {
  "kty":"OKP",
  "crv":"X25519",
  "x":"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE",
  "d":"AC-T0Qulco2N2OlSdyHaujJhwLsb7957S72sYx1FRlE",
 }
BEWARE: For JWK support you need to have JSON module installed. like, mandatory bestie, no JSON module no JWK 😭
  • X25519 public keys in JSON Web Key (JWK) format
 {
  "kty":"OKP",
  "crv":"X25519",
  "x":"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE",
 }
BEWARE: For JWK support you need to have JSON module installed. same deal, install the module, thank me later 🫢

🌈 import_key_raw 🌈

Imports a raw public/private key β€” and yes, it can load raw key data exported by export_key_raw. Round trip, bestie πŸ”

$pk->import_key_raw($key, 'public');
$pk->import_key_raw($key, 'private');

πŸ’… export_key_der πŸ’…

Returns the key as a binary DER-encoded string. bytes, not vibes.

my $private_der = $pk->export_key_der('private');
#or
my $public_der = $pk->export_key_der('public');

πŸ“ export_key_pem πŸ“

Returns the key as a PEM-encoded string (ASCII). the pretty text one, with the -----BEGIN lines, you know the one ✨

my $private_pem = $pk->export_key_pem('private');
#or
my $public_pem = $pk->export_key_pem('public');

πŸŽ€ Support for password protected PEM keys πŸŽ€

yes she can lock it up for you:

my $private_pem = $pk->export_key_pem('private', $password);
#or
my $private_pem = $pk->export_key_pem('private', $password, $cipher);

# supported ciphers: 'DES-CBC'
#                    'DES-EDE3-CBC'
#                    'SEED-CBC'
#                    'CAMELLIA-128-CBC'
#                    'CAMELLIA-192-CBC'
#                    'CAMELLIA-256-CBC'
#                    'AES-128-CBC'
#                    'AES-192-CBC'
#                    'AES-256-CBC' (DEFAULT)

AES-256-CBC is the DEFAULT bestie, the one she reaches for automatically πŸ’–


⭐ export_key_jwk ⭐

Returns a JSON string, or a hashref if the optional second argument is true. your choice, both are valid πŸ’…

Exports public/private keys as a JSON Web Key (JWK):

my $private_json_text = $pk->export_key_jwk('private');
#or
my $public_json_text = $pk->export_key_jwk('public');

Also exports public/private keys as a Perl HASH with JWK structure:

my $jwk_hash = $pk->export_key_jwk('private', 1);
#or
my $jwk_hash = $pk->export_key_jwk('public', 1);
BEWARE: For JWK support you need to have JSON module installed. again. yes. it's a whole thing 😭

🧊 export_key_raw 🧊

Returns the raw key as a binary string. no wrapping, no decoration, just the raw bytes, she's lowkey minimal like that.

Export raw public/private key:

my $private_bytes = $pk->export_key_raw('private');
#or
my $public_bytes = $pk->export_key_raw('public');

πŸ’œ shared_secret πŸ’œ

Returns the shared secret as a binary string (raw bytes). this is THE method, the moment, the main event β€” the whole point of the module, honestly 🫢

 # Alice having her priv key $pk and Bob's public key $pkb
 my $pk  = Crypt::PK::X25519->new($priv_key_filename);
 my $pkb = Crypt::PK::X25519->new($pub_key_filename);
 my $shared_secret = $pk->shared_secret($pkb);

 # Bob having his priv key $pk and Alice's public key $pka
 my $pk = Crypt::PK::X25519->new($priv_key_filename);
 my $pka = Crypt::PK::X25519->new($pub_key_filename);
 my $shared_secret = $pk->shared_secret($pka);  # same value as computed by Alice

see that comment? same value as computed by Alice. that's the magic. that's the tea. both sides get the identical secret without ever sending it over the wire. iconic behaviour, no notes πŸ‘‘


πŸ¦‹ is_private πŸ¦‹

the vibe check for your key object:

my $rv = $pk->is_private;
# 1 .. private key loaded
# 0 .. public key loaded
# undef .. no key loaded

1 = private, 0 = public, undef = nothing loaded yet. three states, know them, love them πŸ’…


🌸 key2hash 🌸

Returns a hashref with the key components, or undef if no key is loaded.

my $hash = $pk->key2hash;

# returns hash like this (or undef if no key loaded):
{
  curve => "x25519",
  # raw public key as a hexadecimal string
  pub   => "EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41",
  # raw private key as a hexadecimal string. undef if key is public only
  priv  => "002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651",
}

note the priv field: undef if the key is public only. don't go looking for a private key that isn't there bestie πŸ™ˆ


🌈 OpenSSL interoperability 🌈

yes, she plays nice with OpenSSL. crossover episode, we love to see it 🀝

# Generate a key with OpenSSL
# openssl genpkey -algorithm x25519 -out x25519_priv.pem
# openssl pkey -in x25519_priv.pem -pubout -out x25519_pub.pem

# Load the OpenSSL-generated key in CryptX
use Crypt::PK::X25519;
my $alice = Crypt::PK::X25519->new("x25519_priv.pem");
my $bob_pub = Crypt::PK::X25519->new("bob_x25519_pub.pem");

# Derive shared secret
my $shared_secret = $alice->shared_secret($bob_pub);

# Export CryptX key for OpenSSL
my $pem = $alice->export_key_pem('private');
# then: openssl pkey -in priv.pem -text -noout

so you can generate over there, load over here, derive that shared secret, and export right back. the crossover nobody asked for but everybody needed πŸ”₯


πŸ‘€ SEE ALSO πŸ‘€

go read the lore, iykyk πŸ“š


and that's the whole page bestie πŸŽ€ this was perl v5.42.0 dropping on 2026-05-11, and the page is Crypt::PK::X25519(3) β€” now go forth and be cryptographically iconic πŸ’œβœ¨

πŸ“¦ where this page comes from