NAME

Crypt::MultiKey::PKey - Object representing a Public/Private key pair (OpenSSL EVP_PKEY)

SYNOPSIS

# Generate a public/private keypair
my $pkey= Crypt::MultiKey::PKey->generate('x25519');

# encrypt the private half with a password
my $pass= Crypt::SecretBuffer->new;
$pass->append_console_line() or die;
$pkey->encrypt_private($pass);

# Throw away the private half
$pkey->clear_private;

# encrypt some other data with the public half of the key
my $cipherdata= $pkey->encrypt("Example Plaintext");
say JSON->new->encode($cipherdata); # It's a hashref that you can serialize

# restore the private key, using the password
$pkey->decrypt_private($pass); # croaks on wrong password

# Decrypt the data
say $pkey->decrypt($cipherdata); # "Example Plaintext"

DESCRIPTION

Crypt::MultiKey::PKey is a public/private key pair where the public half is always available, but the private half can be encrypted or removed. The PKey can always "encrypt" data, but the private half must be available to "decrypt" that data again.

CONSTRUCTORS

new

$key= Crypt::MultiKey::PKey->new(%options);

Construct a new Public/Private Key. Constructor options are limited to writable attributes, plus generate and password.

protection_scheme

If protection_scheme is a known subclass, this selects that subclass for the new object.

path

Specify the path attribute, which can be used as a default for "save". Use "load" to read a key from a file.

generate

Immediately generate a new key of the specified type. The private, private_encrypted, and public attributes are ignored.

private

A scalar or SecretBuffer containing a private key or encrypted private key. The format is auto-detected. If the format is encrypted and you do not also specify a password, this will become the private_encrypted attribute to be decrypted later using "decrypt_private".

private_encrypted

Encrypted PKCS#8 DER bytes to save for a later "decrypt_private".

private_encrypted_foreign

An encrypted private key in another format recognized by "load", to save for a later "decrypt_private".

public

Loading a private key always implies a public key, but if your private key is encrypted this attribute can be used to provide the public half, enabling the PKey object to be able to encrypt data even before the private half has been obtained.

password

If the private key is encrypted, this attempts a "decrypt_private" before the constructor returns and dies if the password is incorrect. In the opposite case, if you are calling the constructor with { generate => $type }, specifying a password will make an automatic call to "encrypt_private".

load

->load($filename, %options);
->load(\$buffer, %options);
->load($Crypt_SecretBuffer, %options);
->load($Crypt_SecretBuffer_Span, %options);
->load($Crypt_SecretBuffer_PEM, %options);

This attempts to parse a variety of key formats and load either a private key, public key, or encrypted private key. If the key format is encrypted and the %options do not include 'password', encrypted PKCS#8 DER will be stored in "private_encrypted", while other encrypted input formats will be stored in "private_encrypted_foreign". Either can be used by "decrypt_private" later when the password is available.

If the argument is a Crypt::SecretBuffer, Crypt::SecretBuffer::Span, Crypt::SecretBuffer::PEM, or scalar-ref, it will be parsed directly. Anything else is assumed to be a filename that must be opened/read first. If given a filename, this also sets the "path" attribute.

This can be called as a class constructor or as a method of an existing object to replace the contents of the object.

password

Provide a password to decrypt an encrypted key

path

Specify the 'path' attribute for the new object.

ATTRIBUTES

protection_scheme

The scheme used to protect and/or obtain the private half of the key. This is used as a class name suffix for the PKey object, such as 'Password' referring to Crypt::MultiKey::PKey::Password. The class defines the "obtain_private" and "can_obtain_private" methods, and may override the behavior of "encrypt_private" or introduce an entirely new workflow for setting up the key. Most schemes derive a password (or equivalent secret) for use with "encrypt_private", storing the encrypted private key locally in attribute "private_encrypted". However, they are not limited to this; the implementation could do something entirely different like fetching the private key from remote, or forwarding all encryption operations to a device that posesses the private key.

Calling "encrypt_private" on a PKey with no current protection scheme sets the scheme to 'Password', which protects the private key by encrypting it using OpenSSL's encrypted PKCS#8 support, stores it in the "private_encrypted" attribute, and obtains it by prompting the console for the password.

algorithm

The type of public-key cryptography used. This is selected during "generate", or implied if you "load" a pre-existing key.

fingerprint

OpenSSL-style "SHA256:base64..." used to help identify the key.

path

A disk path from which this key was loaded or to which it will be saved.

has_public

Boolean; whether this key currently has the public half loaded. This will be true except when loaded from a PEM file which only contained an encrypted private key, and the password hasn't yet been supplied.

has_private

Boolean; whether this key currently has the private half loaded. See "clear_private" and "decrypt_private".

public

Accessor for the public half of the key, as raw SubjectPublicKeyInfo bytes. It reads "export_spki" and writes "import_spki".

public_b64

Like "public", but base64-encoded for use in text formats.

private

Accessor for the private half of the key, as raw PKCS#8 bytes. It reads "export_pkcs8_unencrypted" and writes "import_pkcs8".

private_encrypted

Encrypted PKCS#8 DER bytes as returned by "export_pkcs8_encrypted". This attribute holds the input for the "decrypt_private" method. This attribute gets exported by "export" and "save", and (when base64-encoded and wrapped with PEM) is the same format used by OpenSSL for encrypted private keys.

private_encrypted_foreign

An encrypted private key in some other recognized input format (such as SSH encrypted private key format), kept verbatim so it can be decrypted later by "decrypt_private". This attribute is accepted by "decrypt_private", but is not emitted by "export" or "save".

METHODS

generate

Replace any current key with a newly generated key of 'type'. The attribute private_encrypted is deleted, if present, since it no longer matches the public key.

Supported types and aliases:

EC:curve=X
secp256k1   => EC:curve=secp256k1

ed25519
x25519

RSA:bits=N
RSA         => RSA:bits=4096
rsa4096     => RSA:bits=4096
rsa2048     => RSA:bits=2048
rsa1024     => RSA:bits=1024

(with OpenSSL >= 3.5)
ML-KEM      => ML-KEM-768
ML-KEM-512
ML-KEM-768
ML-KEM-2014

import_spki

$pkey->import_spki($der_bytes);

Import a public key from DER-encoded ASN.1 SubjectPublicKeyInfo bytes. SubjectPublicKeyInfo is the public-key structure defined by RFC5280 and used by OpenSSL's BEGIN PUBLIC KEY PEM files. DER is the deterministic binary encoding of ASN.1 data.

export_spki

$der_bytes= $pkey->export_spki;

Export the public key as DER-encoded SubjectPublicKeyInfo bytes.

import_pkcs8

$pkey->import_pkcs8($der_bytes);
$pkey->import_pkcs8($encrypted_der_bytes, $password);

Import a private key from DER-encoded PKCS#8 bytes. PKCS#8 is the standard private-key container used by OpenSSL's BEGIN PRIVATE KEY and BEGIN ENCRYPTED PRIVATE KEY PEM files. If the PKCS#8 structure is encrypted, $password is required.

Dies on failure. If the password is incorrect, the error will match qr/^password/. Note that in many cases it is hard to distinguish an incorrect password from a corrupt file.

export_pkcs8_unencrypted

$secret_buffer= $pkey->export_pkcs8_unencrypted;

Export the private key as unencrypted DER-encoded PKCS#8 bytes in a SecretBuffer.

export_pkcs8_encrypted

$der_bytes= $pkey->export_pkcs8_encrypted($password, $kdf_iter);

Export the private key as password-encrypted DER-encoded PKCS#8 bytes. Both $password and $kdf_iter are required.

export

$pkey->export->save_file($filename);

Export the PKey as a SecretBuffer object containing OpenSSL PEM text of PKCS#8 format data, but also with PEM headers that preserve the value of object attributes. If "protection_scheme" is undef, this will write out an unencrypted private key. If you called "encrypt_private", the protection_scheme changed to 'Password' and this will write out an encrypted private key in PKCS#8 format. If a protection scheme is defined but "private_encrypted" is not available, this exports only the public key.

All exports from this method will include PEM headers as needed to store the PKey attributes. OpenSSL cannot read PEM files if they have headers.

export_pem

Like "export", but return a PEM object prior to serializing.

export_pem_openssl_public_key

Export the PKey as a PEM object of SubjectPublicKeyInfo data (OpenSSL BEGIN PUBLIC KEY format) without any PEM headers so that openssl(1) can read it.

export_pem_openssl_private_key

Export the PKey as a PEM object of PKCS#8 data, unencrypted, and without any PEM headers so that openssl(1) can read it.

export_pem_openssl_encrypted_private_key

Export the PKey as a PEM object of password-encrypted PKCS#8 data from the "private_encrypted" attribute, without any Crypt::MultiKey metadata headers so that openssl(1) can read it.

save

$pkey->save;         # saves to $pkey->path
$pkey->save($path);  # saves to $path and sets $pkey->path if not already set

This is a shortcut for $pkey->export_pem->serialize->save_file($pkey->path, "rename").

encrypt_private

$pkey->encrypt_private($password, $kdf_iter=100_000);

Export the (private) key in PKCS#8 format encrypted with a password, and store it into attribute private_encrypted to be saved out by a subsequent "export" or "save". You may customize the number of iterations for the key-derivation-function (KDF) to resist brute-force attempts. If the password is known to be a string of hashed data with uniformly-distributed bits, you may reduce the KDF iterations to 1, but it cannot be zero due to OpenSSL API.

The password must be bytes, not wide characters, so make sure to encode it first. Ideally, $password is a SecretBuffer object, but scalars are also accepted.

If this PKey did not have a "protection_scheme", the protection_scheme gets initialized to 'Password'.

clear_private

Delete the private half of the public/private key pair. You should only call this after encrypting it, or saving it by some other means.

decrypt_private

$pkey->decrypt_private($password);

Using the supplied password, decrypt attribute private_encrypted or private_encrypted_foreign and import it.

The password must be bytes, not wide characters. Ideally, $password is a SecretBuffer object, but scalars are also accepted.

Dies on failure. If the password is incorrect, the error will match qr/^password/. Note that in many cases it is hard to distinguish an incorrect password from a corrupt file.

can_obtain_private

Returns true if the resources needed for obtaining the private half of the PKey are available, or seem to be available. (attempting may still fail)

This can return false even when the private key was already loaded; check "has_private" first.

obtain_private

Attempt to gain access to the private half of the PKey, either by decrypting it and loading it locally, or opening a connection to a device or service which is providing the private half. This may block as it prompts the user for a password or other type of confirmation. Dies on failure.

If the private half of the PKey is already available, this does nothing.

generate_key_material

$key_material= Crypt::SecretBuffer->new();
my (%tumbler1, %tumbler2);
$pkey1->generate_key_material(\%tumbler1, $key_material);
$pkey2->generate_key_material(\%tumbler2, $key_material);
...
$symmetric_key= Crypt::MultiKey::hkdf({ size => 32 }, $key_material);

Generate reproducible cryptographic bytes using the public half of this key and append them to a SecretBuffer. The parameters needed to reproduce those bytes are stored into a "tumbler". The key material should then be fed to "hkdf" in Crypt::MultiKey to derive an AES key.

Calling this method on multiple key objects (with a fresh tumbler hashref for each, but with the same buffer) allows you to build a compound secret which will then require all of the private keys to recreate.

recreate_key_material

$key_material= Crypt::SecretBuffer->new();
$pkey1->recreate_key_material(\%tumbler1, $key_material);
$pkey2->recreate_key_material(\%tumbler2, $key_material);
...
$symmetric_key= Crypt::MultiKey::hkdf({ size => 32 }, $key_material);

Reproduce the cryptographic bytes that were previously generated by "generate_key_material" using the private half of this key and the information in %tumbler.

encrypt

my $fields= $pkey->encrypt($secret);
my $fields2= $pkey->encrypt($secret, $ciphertext_out);

Encrypt a secret using the public half of this key. The secret is ideally a SecretBuffer object, but may also be a scalar. The return value is a hashref containing the encryption parameters and key-exchange data needed for decryption. The ciphertext is stored as $fields->{ciphertext} unless you pass an explicit $ciphertext_out scalar/buffer.

decrypt

$secret_buffer= $pkey->decrypt(\%fields);
$pkey->decrypt(\%fields, $secret_out);

Decrypt a secret using the private half of this key. (and dies if the private half of the key is not currently available). It reads ciphertext from $fields->{ciphertext}. The original secret is returned as a SecretBuffer object, or written into $secret_out if supplied. $secret_out must be a Crypt::SecretBuffer.

VERSION

version 0.000_001

AUTHOR

Michael Conrad <mike@nrdvana.net>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Michael Conrad.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.