CONTENTS

NAME

Crypt::PK::RSA - Public key cryptography based on RSA

SYNOPSIS

### OO interface

my $message = 'hello world';
my $private_key = Crypt::PK::RSA->new();
$private_key->generate_key(256, 65537);

my $public_der = $private_key->export_key_der('public');
my $public_key = Crypt::PK::RSA->new(\$public_der);

my $ciphertext = $public_key->encrypt($message);
my $plaintext = $private_key->decrypt($ciphertext);

my $signature = $private_key->sign_message($message, 'SHA256');
$public_key->verify_message($signature, $message, 'SHA256') or die "ERROR";

my $private_der = $private_key->export_key_der('private');
my $private_pem = $private_key->export_key_pem('private');
my $public_pem = $public_key->export_key_pem('public');

DESCRIPTION

The module provides a full featured RSA implementation.

Legacy function-style wrappers still exist in code for backwards compatibility, but they are intentionally undocumented.

METHODS

new

my $source = Crypt::PK::RSA->new();
$source->generate_key(256, 65537);

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

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

Passing $filename or \$buffer to new is equivalent: both forms immediately import the key material into the new object.

generate_key

Uses the bundled chacha20 PRNG via libtomcrypt's rng_make_prng. Returns the object itself (for chaining).

$pk->generate_key($size, $e);
# $size .. [integer] key size: 128-512 bytes (DEFAULT is 256)
# $e ..... [integer] exponent: 3, 17, 257 or 65537 (DEFAULT is 65537)

The $size and $e arguments use Perl's usual numeric-to-integer coercion before reaching the XS layer. Pass exact integers; values like 10.9 or "1e2" are coerced rather than rejected.

import_key

Loads private or public key in DER or PEM format.

my $source = Crypt::PK::RSA->new();
$source->generate_key(256, 65537);

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

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

The same method also accepts filenames instead of buffers.

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({
  e  => "10001", #public exponent
  d  => "9ED5C3D3F866E06957CA0E9478A273C39BBDA4EEAC5B...", #private exponent
  N  => "D0A5CCCAE03DF9C2F5C4C8C0CE840D62CDE279990DC6...", #modulus
  p  => "D3EF0028FFAB508E2773C659E428A80FB0E9211346B4...", #p factor of N
  q  => "FC07E46B163CAB6A83B8E467D169534B2077DCDEECAE...", #q factor of N
  qP => "88C6D406F833DF73C8B734548E0385261AD51F4187CF...", #1/q mod p CRT param
  dP => "486F142FEF0A1F53269AC43D2EE4D263E2841B60DA36...", #d mod (p - 1) CRT param
  dQ => "4597284B2968B72C4212DB7E8F24360B987B80514DA9...", #d mod (q - 1) CRT param
});

# or a hash with items corresponding to JWK (JSON Web Key)
$pk->import_key({
  kty => "RSA",
  n   => "0vx7agoebGcQSuuPiLJXZpt...eZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
  e   => "AQAB",
  d   => "X4cTteJY_gn4FYPsXB8rdXi...FLN5EEaG6RoVH-HLKD9Mdx5ooGURknhnrRwUkC7h5fJLMWbFAKLWY2v7B6NqSzUvx0_YSf",
  p   => "83i-7IvMGXoMXCskv73TKr8...Z27zvoj6pbUQyLPBQxtPnwD20-60eTmD2ujMt5PoMrm8RmNhVWtjjMmMjOpSicFHjXOuVI",
  q   => "3dfOR9cuYq-0S-mkFLzgItg...q3hWeMuG0ouqnb3obLyuqjVZQ1dIrdgTnCdYzBcOW5r37AFXjift_NGiovonzhKpoVVS78",
  dp  => "G4sPXkc6Ya9y8oJW9_ILj4...zi_H7TkS8x5SdX3oE0oiYwxIiemTAu0UOa5pgFGyJ4c8t2VF40XRugKTP8akhFo5tA77Qe",
  dq  => "s9lAH9fggBsoFR8Oac2R_E...T2kGOhvIllTE1efA6huUvMfBcpn8lqW6vzzYY5SSF7pMd_agI3G8IbpBUb0JiraRNUfLhc",
  qi  => "GyM_p6JrXySiz1toFgKbWV...4ypu9bMWx3QJBfm0FoYzUIZEVEcOqwmRN81oDAaaBk0KWGDjJHDdDmFW3AN7I-pux_mHZG",
});

Supported key formats:

# all formats can be loaded from a file
my $pk = Crypt::PK::RSA->new($filename);

# or from a buffer containing the key
my $pk = Crypt::PK::RSA->new(\$buffer_with_key);

export_key_der

Returns the key as a binary DER-encoded string.

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).

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

With parameter 'public' uses header and footer lines:

-----BEGIN RSA PUBLIC KEY------
-----END RSA PUBLIC KEY------

With parameter 'public_x509' uses header and footer lines:

-----BEGIN PUBLIC KEY------
-----END PUBLIC KEY------

Support for password protected PEM keys

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)

export_key_jwk

Since: CryptX-0.022

Returns a JSON string, or a hashref if the optional second argument is true.

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.

export_key_jwk_thumbprint

Since: CryptX-0.031

Exports the key's JSON Web Key Thumbprint as a string.

If you don't know what this is, see RFC 7638 https://www.rfc-editor.org/rfc/rfc7638.

my $thumbprint = $pk->export_key_jwk_thumbprint('SHA256');

encrypt

Returns the ciphertext as a binary string.

my $pk = Crypt::PK::RSA->new($pub_key_filename);
my $ct = $pk->encrypt($message);
#or
my $ct = $pk->encrypt($message, $padding);
#or
my $ct = $pk->encrypt($message, 'oaep', $hash_name, $lparam);

# $padding .................... [string] 'oaep' (DEFAULT), 'v1.5' or 'none' (INSECURE)
# $hash_name (only for oaep) .. [string] 'SHA1' (DEFAULT), 'SHA256' or any other hash supported by Crypt::Digest
# $lparam (only for oaep) ..... [binary string] DEFAULT is empty string

decrypt

Returns the plaintext as a binary string.

my $pk = Crypt::PK::RSA->new($priv_key_filename);
my $pt = $pk->decrypt($ciphertext);
#or
my $pt = $pk->decrypt($ciphertext, $padding);
#or
my $pt = $pk->decrypt($ciphertext, 'oaep', $hash_name, $lparam);

# $padding .................... [string] 'oaep' (DEFAULT), 'v1.5' or 'none' (INSECURE)
# $hash_name (only for oaep) .. [string] 'SHA1' (DEFAULT), 'SHA256' or any other hash supported by Crypt::Digest
# $lparam (only for oaep) ..... [binary string] DEFAULT is empty string

sign_message

Returns the signature as a binary string.

my $pk = Crypt::PK::RSA->new($priv_key_filename);
my $signature = $pk->sign_message($message);
#or
my $signature = $pk->sign_message($message, $hash_name);
#or
my $signature = $pk->sign_message($message, $hash_name, $padding);
#or
my $signature = $pk->sign_message($message, $hash_name, 'pss', $saltlen);

# $hash_name ............... [string] 'SHA1' (DEFAULT, INSECURE), 'SHA256' or any other hash supported by Crypt::Digest
# $padding ................. [string] 'pss' (DEFAULT) or 'v1.5' or 'none' (INSECURE)
# $saltlen (only for pss) .. [integer] DEFAULT is 12

BEWARE: The $hash_name default is 'SHA1' only for backward compatibility. SHA-1 is vulnerable to practical collision attacks and is not safe for signing messages whose content may be influenced by an attacker. Always pass an explicit hash name such as 'SHA256'. The same applies to "verify_message", "sign_hash" and "verify_hash".

For RSASSA-PSS with SHAKE per RFC 8702 use 'SHAKE128' (output 32 bytes, $saltlen = 32) or 'SHAKE256' (output 64 bytes, $saltlen = 64); both the message hash and the internal MGF use SHAKE directly as defined by RFC 8702.

my $sig = $pk->sign_message($message, 'SHAKE128', 'pss', 32);  # id-RSASSA-PSS-SHAKE128
my $sig = $pk->sign_message($message, 'SHAKE256', 'pss', 64);  # id-RSASSA-PSS-SHAKE256

verify_message

Returns 1 if the signature is valid, 0 otherwise.

my $pk = Crypt::PK::RSA->new($pub_key_filename);
my $valid = $pk->verify_message($signature, $message);
#or
my $valid = $pk->verify_message($signature, $message, $hash_name);
#or
my $valid = $pk->verify_message($signature, $message, $hash_name, $padding);
#or
my $valid = $pk->verify_message($signature, $message, $hash_name, 'pss', $saltlen);

# $hash_name ............... [string] 'SHA1' (DEFAULT, INSECURE), 'SHA256' or any other hash supported by Crypt::Digest
# $padding ................. [string] 'pss' (DEFAULT) or 'v1.5' or 'none' (INSECURE)
# $saltlen (only for pss) .. [integer] DEFAULT is 12

sign_hash

Returns the signature as a binary string.

my $pk = Crypt::PK::RSA->new($priv_key_filename);
my $signature = $pk->sign_hash($message_hash);
#or
my $signature = $pk->sign_hash($message_hash, $hash_name);
#or
my $signature = $pk->sign_hash($message_hash, $hash_name, $padding);
#or
my $signature = $pk->sign_hash($message_hash, $hash_name, 'pss', $saltlen);
#or
my $signature = $pk->sign_hash($message_hash, $hash_name, 'pss', $saltlen, $mgf_hash_name);

# $hash_name ................. [string] 'SHA1' (DEFAULT, INSECURE), 'SHA256' or any other hash supported by Crypt::Digest
# $padding ................... [string] 'pss' (DEFAULT) or 'v1.5' or 'none' (INSECURE)
# $saltlen (pss only) ........ [integer] DEFAULT is 12
# $mgf_hash_name (pss only) .. [string] MGF hash function name (DEFAULT: the $hash_name value)

verify_hash

Returns 1 if the signature is valid, 0 otherwise.

my $pk = Crypt::PK::RSA->new($pub_key_filename);
my $valid = $pk->verify_hash($signature, $message_hash);
#or
my $valid = $pk->verify_hash($signature, $message_hash, $hash_name);
#or
my $valid = $pk->verify_hash($signature, $message_hash, $hash_name, $padding);
#or
my $valid = $pk->verify_hash($signature, $message_hash, $hash_name, 'pss', $saltlen);
#or
my $valid = $pk->verify_hash($signature, $message_hash, $hash_name, 'pss', $saltlen, $mgf_hash_name);

# $hash_name ................. [string] 'SHA1' (DEFAULT, INSECURE), 'SHA256' or any other hash supported by Crypt::Digest
# $padding ................... [string] 'pss' (DEFAULT) or 'v1.5' or 'none' (INSECURE)
# $saltlen (pss only) ........ [integer] DEFAULT is 12
# $mgf_hash_name (pss only) .. [string] MGF hash function name (DEFAULT: the $hash_name value)

is_private

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

size

my $size = $pk->size;
# returns key size in bytes or undef if no key loaded

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):
{
  type => 1,   # integer: 1 .. private, 0 .. public
  size => 256, # integer: key size in bytes
  # all the rest are hex strings
  e  => "10001", #public exponent
  d  => "9ED5C3D3F866E06957CA0E9478A273C39BBDA4EEAC5B...", #private exponent
  N  => "D0A5CCCAE03DF9C2F5C4C8C0CE840D62CDE279990DC6...", #modulus
  p  => "D3EF0028FFAB508E2773C659E428A80FB0E9211346B4...", #p factor of N
  q  => "FC07E46B163CAB6A83B8E467D169534B2077DCDEECAE...", #q factor of N
  qP => "88C6D406F833DF73C8B734548E0385261AD51F4187CF...", #1/q mod p CRT param
  dP => "486F142FEF0A1F53269AC43D2EE4D263E2841B60DA36...", #d mod (p - 1) CRT param
  dQ => "4597284B2968B72C4212DB7E8F24360B987B80514DA9...", #d mod (q - 1) CRT param
}

OpenSSL interoperability

### let's have:
# RSA private key in PEM format - rsakey.priv.pem
# RSA public key in PEM format  - rsakey.pub.pem
# data file to be signed or encrypted - input.data

Encrypt by OpenSSL, decrypt by Crypt::PK::RSA

Create encrypted file (from commandline):

openssl rsautl -encrypt -inkey rsakey.pub.pem -pubin -out input.encrypted.rsa -in input.data

Decrypt file (Perl code):

use Crypt::PK::RSA;
use Crypt::Misc 'read_rawfile';

my $pkrsa = Crypt::PK::RSA->new("rsakey.priv.pem");
my $encfile = read_rawfile("input.encrypted.rsa");
my $plaintext = $pkrsa->decrypt($encfile, 'v1.5');
print $plaintext;

Encrypt by Crypt::PK::RSA, decrypt by OpenSSL

Create encrypted file (Perl code):

use Crypt::PK::RSA;
use Crypt::Misc 'write_rawfile';

my $plaintext = 'secret message';
my $pkrsa = Crypt::PK::RSA->new("rsakey.pub.pem");
my $encrypted = $pkrsa->encrypt($plaintext, 'v1.5');
write_rawfile("input.encrypted.rsa", $encrypted);

Decrypt file (from commandline):

openssl rsautl -decrypt -inkey rsakey.priv.pem -in input.encrypted.rsa

Sign by OpenSSL, verify by Crypt::PK::RSA

Create signature (from commandline):

openssl dgst -sha256 -sign rsakey.priv.pem -out input.sha256-rsa.sig input.data

Verify signature (Perl code):

use Crypt::PK::RSA;
use Crypt::Digest 'digest_file';
use Crypt::Misc 'read_rawfile';

my $pkrsa = Crypt::PK::RSA->new("rsakey.pub.pem");
my $signature = read_rawfile("input.sha256-rsa.sig");
my $valid = $pkrsa->verify_hash($signature, digest_file("SHA256", "input.data"), "SHA256", "v1.5");
print $valid ? "SUCCESS" : "FAILURE";

Sign by Crypt::PK::RSA, verify by OpenSSL

Create signature (Perl code):

use Crypt::PK::RSA;
use Crypt::Digest 'digest_file';
use Crypt::Misc 'write_rawfile';

my $pkrsa = Crypt::PK::RSA->new("rsakey.priv.pem");
my $signature = $pkrsa->sign_hash(digest_file("SHA256", "input.data"), "SHA256", "v1.5");
write_rawfile("input.sha256-rsa.sig", $signature);

Verify signature (from commandline):

openssl dgst -sha256 -verify rsakey.pub.pem -signature input.sha256-rsa.sig input.data

Keys generated by Crypt::PK::RSA

Generate keys (Perl code):

use Crypt::PK::RSA;
use Crypt::Misc 'write_rawfile';

my $pkrsa = Crypt::PK::RSA->new;
$pkrsa->generate_key(256, 65537);
write_rawfile("rsakey.pub.der",  $pkrsa->export_key_der('public'));
write_rawfile("rsakey.priv.der", $pkrsa->export_key_der('private'));
write_rawfile("rsakey.pub.pem",  $pkrsa->export_key_pem('public_x509'));
write_rawfile("rsakey.priv.pem", $pkrsa->export_key_pem('private'));
write_rawfile("rsakey-passwd.priv.pem", $pkrsa->export_key_pem('private', 'secret'));

Use keys by OpenSSL:

openssl rsa -in rsakey.priv.der -text -inform der
openssl rsa -in rsakey.priv.pem -text
openssl rsa -in rsakey-passwd.priv.pem -text -inform pem -passin pass:secret
openssl rsa -in rsakey.pub.der -pubin -text -inform der
openssl rsa -in rsakey.pub.pem -pubin -text

Keys generated by OpenSSL

Generate keys:

openssl genrsa -out rsakey.priv.pem 1024
openssl rsa -in rsakey.priv.pem -out rsakey.priv.der -outform der
openssl rsa -in rsakey.priv.pem -out rsakey.pub.pem -pubout
openssl rsa -in rsakey.priv.pem -out rsakey.pub.der -outform der -pubout
openssl rsa -in rsakey.priv.pem -passout pass:secret -des3 -out rsakey-passwd.priv.pem

Load keys (Perl code):

use Crypt::PK::RSA;

my $pkrsa = Crypt::PK::RSA->new;
$pkrsa->import_key("rsakey.pub.der");
$pkrsa->import_key("rsakey.priv.der");
$pkrsa->import_key("rsakey.pub.pem");
$pkrsa->import_key("rsakey.priv.pem");
$pkrsa->import_key("rsakey-passwd.priv.pem", "secret");

SEE ALSO