Crypt-OpenSSL-RSA

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

    only checked on 3.x inside rsa_crypt()).
  - PR #178: Validate key size in generate_key() before calling OpenSSL.
    Reject negative, zero, and sub-512-bit key sizes with a clear croak
    instead of letting OpenSSL produce cryptic errors or hang.
  - PR #179 GH #174: Restore the lost configure_requires prereq on
    Crypt::OpenSSL::Guess in Makefile.PL.
  - PR #179 GH #175: Fix failing test 'Padding method pkcs1_pss is valid
    for signing with ripemd160'.

  [Improvements]
  - PR #180: Add optional passphrase argument to new_private_key_der(),
    enabling decryption of encrypted PKCS#8 DER (EncryptedPrivateKeyInfo)
    private keys. On OpenSSL 3.x the passphrase is passed to the existing
    OSSL_DECODER_CTX; on pre-3.x a d2i_PKCS8PrivateKey_bio() helper is
    used. Previously only PEM-encoded keys supported a passphrase.

  [Maintenance]
  - PR #172: Fix 'passphase' -> 'passphrase' typo throughout the codebase
    (RSA.xs internal names, RSA.pm POD for get_private_key_string, and the
    test variable in t/format.t). The typo dates to the original 0.33
    passphrase support. No functional change -- all renames are internal.

0.38 Apr 23 2026

  [Bug Fixes]
  - PR #103 GH #61: Re-enable PKCS#1 v1.5 padding for sign()/verify().  It
    was incorrectly disabled in 0.35; the Marvin attack only affects
    decryption, not signatures.
  - PR #168: Fix croak message to reference use_pkcs1_oaep_padding() (not

Changes  view on Meta::CPAN

  - PR #141: Reject non-RSA keys (EC, DSA, RSA-PSS) loaded via
    _load_rsa_key() on OpenSSL 3.x with a clear error instead of a
    confusing failure later.
  - PR #118: Fix private_encrypt() and public_decrypt() broken on OpenSSL
    3.x with any padding except NO_PADDING; rsa_crypt() now distinguishes
    encrypt vs. sign paths.
  - PR #142: Free signature buffer on RSA_sign() failure on pre-3.x.
  - PR #164 GH #152: Drain OpenSSL error queue after _get_key_parameters()
    on OpenSSL 3.x so a failed optional-param lookup does not pollute the
    error queue for subsequent operations.
  - PR #161 GH #152: Cache is_private_key flag in rsaData struct to avoid a
    per-call BIGNUM heap allocation on OpenSSL 3.x.
  - PR #159 GH #155: Check return values of EVP_PKEY_get_bn_param() in
    _get_key_parameters(); a failed mandatory param (n or e) now croaks
    instead of silently returning undef.
  - PR #160 GH #156: Use THROW macro for make_rsa_obj() result in
    _new_key_from_parameters() to prevent resource leak on a NULL return.
  - PR #158 GH #154: Extract setup_pss_sign_ctx() helper to deduplicate
    PSS context setup in sign() and verify(); the two paths could previously
    diverge silently.
  - PR #157 GH #153: Eliminate duplicate NID-to-name table in
    get_message_digest(); fixes whirlpool on OpenSSL 3.x where the old
    low-level WHIRLPOOL() API path was being used instead of EVP_MD_fetch().
  - PR #145: Fix BIO resource leak in extractBioString() error paths.
  - PR #143: Validate that a private key is present before attempting export
    in get_private_key_string().
  - PR #140: NULL out BIGNUMs after freeing them in _new_key_from_parameters()
    to prevent a double-free when make_rsa_obj() fails after they are freed.
  - PR #137: Use BN_clear_free() (instead of BN_free()) for private key
    BIGNUMs in _get_key_parameters() to scrub sensitive material.
  - PR #136: Remove static buffer in get_message_digest() that caused
    thread-safety problems under Perl ithreads.
  - PR #134: Add Perl-level stub for use_sslv23_padding() on OpenSSL 3.x
    where the underlying RSA_SSLV23_PADDING constant was removed.
  - PR #133: Fix PSS MGF1 setup to inspect the correct padding fields
    (sign_pad/verify_pad) instead of p_rsa->padding, preventing wrong

Changes  view on Meta::CPAN

    generate_key(), sign(), verify(), rsa_crypt(), check_key(),
    get_public_key_string(), _new_key_from_parameters(), and
    _get_key_parameters() across success and error paths.

  [Improvements]
  - PR #169: Make Crypt::OpenSSL::Bignum a hard runtime requirement (moved
    from recommended to required in Makefile.PL and added hard import in
    RSA.pm); it was already required in practice for get_key_parameters().
  - PR #126: new_public_key() now accepts DER-encoded public keys in addition
    to PEM; format is detected automatically via ASN.1 OID inspection.
  - PR #124: Add get_private_key_pkcs8_string() to export private keys in
    PKCS#8 PEM format.
  - PR #110: Add get_public_key_pkcs1_string() as an alias for
    get_public_key_string() for API symmetry with the X.509/PKCS#1 naming.
  - PR #111: Add optional check=>1 parameter to new_key_from_parameters()
    to validate the constructed key via check_key() before returning it.
  - PR #135: Add plaintext length pre-validation in rsa_crypt() with a
    descriptive croak before attempting the OpenSSL operation.
  - PR #151: Reject invalid (even-numbered) RSA exponents before passing
    them to OpenSSL, preventing a potential hang during key generation.

Changes  view on Meta::CPAN

      release of OpenSSL.
    - Apply patch from Jim Radford <radford@blackbean.org> to add support
      for SHA{224,256,384,512}

0.22  Mon Nov 15 2005 21:13:20
    - Add public_decrypt, private_encrypt methods, contributed
      by Paul G. Weiss <paul@weiss.name>
    - Some changes to help builds on Redhat9
    - Remove deprecated methods:
      * the no-arg new constructor - use new_from_public_key,
    new_from_private_key or Crypt::OpenSSL::RSA->generate_key instead
      * load_public_key - use new_from_public_key
      * load_private_key - use new_from_private_key
      * generate_key as an instance method - use it as a class constructor
    method instead.
      * set_padding_mode - use use_no_padding, use_pkcs1_padding,
    use_pkcs1_oaep_padding, or use_sslv23_padding instead.
      * get_padding_mode
    - Eliminate all(most all) memory leaks.
    - fix email address
    - Stop returning true from methods just to indicate success.
    - Change default public exponent from 65535 to 65537

Changes  view on Meta::CPAN

      get_key_parameters, which, working with
      Crypt::OpenSSL::Bignum, allow working directly with the
      paramaters of an rsa key.

0.17  Mon Jan 06 2003 22:43:31
    - Workaround for gcc 3.2 compile problems:
      "/usr/include/openssl/des.h:193: parse error before '&' token"
      (Patch by Rob Brown <bbb@cpan.org>)
    - Deprecate no-arg constructor, load_*_key methods and the
      instance method generate_key; switch to three constructors:
      new_public_key, new_private_key and generate_key (as a class
      method)
    - Deprecate set_padding_mode method; replace with
      use_xxx_padding.
    - move tests into t directory, use Test as a framework

0.16  Tue Jun 11 22:01:45
    - Fix bug reported by Rob McMillin <rlm@pricegrabber.com> which
      prevented subclassing.

0.15  Fri Jun 07 09:13:12

README.md  view on Meta::CPAN


    use Crypt::OpenSSL::Random;
    use Crypt::OpenSSL::RSA;

    # not necessary if we have /dev/random:
    Crypt::OpenSSL::Random::random_seed($good_entropy);
    Crypt::OpenSSL::RSA->import_random_seed();
    $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($key_string);
    $ciphertext = $rsa->encrypt($plaintext);

    $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($key_string);
    $plaintext = $rsa->decrypt($ciphertext);

    $rsa = Crypt::OpenSSL::RSA->generate_key(1024); # or
    $rsa = Crypt::OpenSSL::RSA->generate_key(1024, $prime);

    print "private key is:\n", $rsa->get_private_key_string();
    print "public key (in PKCS1 format) is:\n",
          $rsa->get_public_key_string();
    print "public key (in X509 format) is:\n",
          $rsa->get_public_key_x509_string();

    $rsa_priv->use_md5_hash(); # insecure. use_sha256_hash or use_sha1_hash are the default
    $signature = $rsa_priv->sign($plaintext);
    print "Signed correctly\n" if ($rsa->verify($plaintext, $signature));

# SECURITY

README.md  view on Meta::CPAN

    Return the Base64/DER-encoded PKCS#8 representation of the private
    key.  This string has header and footer lines:

        -----BEGIN PRIVATE KEY-----
        -----END PRIVATE KEY-----

    This is the format produced by `openssl pkey -outform PEM`, and is
    the private-key counterpart of `get_public_key_x509_string`.

    Accepts the same optional passphrase and cipher-name parameters as
    `get_private_key_string`.

- encrypt

    Encrypt a binary "string" using the public (portion of the) key.

- decrypt

    Decrypt a binary "string".  Croaks if the key is public only.

- private\_encrypt

RSA.pm  view on Meta::CPAN

            # No OID — assume PKCS#1 RSAPublicKey, let OpenSSL reject invalid data
            return $proto->_new_public_key_pkcs1_der($p_key_string);
        }
    }
    else {
        croak "unrecognized key format: expected PEM-encoded key (starting with '-----BEGIN') "
            . "or DER-encoded key (binary ASN.1 data)";
    }
}

sub new_private_key {
    my ( $proto, $p_key_string, @rest ) = @_;
    croak "unrecognized key format: expected PEM-encoded key (starting with '-----BEGIN') "
        . "or DER-encoded key (binary ASN.1 data)"
        unless defined $p_key_string && length($p_key_string) > 0;
    if ( $p_key_string =~ /^-----/ ) {
        return $proto->_new_private_key_pem($p_key_string, @rest);
    }
    elsif ( substr($p_key_string, 0, 1) eq "\x30" ) {
        # ASN.1 SEQUENCE tag detected — likely DER-encoded private key.
        return $proto->_new_private_key_der($p_key_string, @rest);
    }
    else {
        croak "unrecognized key format: expected PEM-encoded key (starting with '-----BEGIN') "
            . "or DER-encoded key (binary ASN.1 data)";
    }
}

sub new_key_from_parameters {
    my ( $proto, $n, $e, $d, $p, $q, %opts ) = @_;
    my $rsa = $proto->_new_key_from_parameters( map { $_ ? $_->pointer_copy() : 0 } $n, $e, $d, $p, $q );

RSA.pm  view on Meta::CPAN


  use Crypt::OpenSSL::Random;
  use Crypt::OpenSSL::RSA;

  # not necessary if we have /dev/random:
  Crypt::OpenSSL::Random::random_seed($good_entropy);
  Crypt::OpenSSL::RSA->import_random_seed();
  $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($key_string);
  $ciphertext = $rsa->encrypt($plaintext);

  $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($key_string);
  $plaintext = $rsa->decrypt($ciphertext);

  $rsa = Crypt::OpenSSL::RSA->generate_key(1024); # or
  $rsa = Crypt::OpenSSL::RSA->generate_key(1024, $prime);

  print "private key is:\n", $rsa->get_private_key_string();
  print "public key (in PKCS1 format) is:\n",
        $rsa->get_public_key_string();
  print "public key (in X509 format) is:\n",
        $rsa->get_public_key_x509_string();

  $rsa_priv->use_md5_hash(); # insecure. use_sha256_hash or use_sha1_hash are the default
  $signature = $rsa_priv->sign($plaintext);
  print "Signed correctly\n" if ($rsa->verify($plaintext, $signature));

=head1 SECURITY

RSA.pm  view on Meta::CPAN


DER-encoded keys (raw binary ASN.1) are also accepted and the format
(PKCS#1 vs X.509) is auto-detected.

The padding is set to PKCS1_OAEP, but can be changed with the
C<use_xxx_padding> methods.

Note, PKCS1_OAEP can only be used for encryption.  Call
C<use_pkcs1_pss_padding> or C<use_pkcs1_padding> prior to signing operations.

=item new_private_key

Create a new C<Crypt::OpenSSL::RSA> object by loading a private key in
from a string containing either PEM or DER encoding of the key.

For PEM keys, the string should include the C<-----BEGIN...-----> and
C<-----END...-----> lines.  The padding is set to PKCS1_OAEP, but can
be changed with C<use_xxx_padding>.

DER-encoded keys (raw binary ASN.1) are also accepted.

RSA.pm  view on Meta::CPAN


Return the Base64/DER-encoded representation of the "subject
public key", suitable for use in X509 certificates.  This string has
header and footer lines:

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

and is the format that is produced by running C<openssl rsa -pubout>.

=item get_private_key_string

Return the Base64/DER-encoded PKCS1 representation of the private
key.  This string has
header and footer lines:

  -----BEGIN RSA PRIVATE KEY------
  -----END RSA PRIVATE KEY------

2 optional parameters can be passed for passphrase protected private key
string:

RSA.pm  view on Meta::CPAN


The passphrase which protects the private key.

=item cipher name

The cipher algorithm used to protect the private key. Default to
'des3'.

=back

=item get_private_key_pkcs8_string

Return the Base64/DER-encoded PKCS#8 representation of the private
key.  This string has header and footer lines:

  -----BEGIN PRIVATE KEY-----
  -----END PRIVATE KEY-----

This is the format produced by C<openssl pkey -outform PEM>, and is
the private-key counterpart of C<get_public_key_x509_string>.

Accepts the same optional passphrase and cipher-name parameters as
C<get_private_key_string>.

=item encrypt

Encrypt a binary "string" using the public (portion of the) key.

=item decrypt

Decrypt a binary "string".  Croaks if the key is public only.

=item private_encrypt

RSA.xs  view on Meta::CPAN

#define PEM_read_bio_RSA_PUBKEY PEM_read_bio_RSA_PUBKEY
#define PEM_write_bio_PUBKEY(o,p) PEM_write_bio_RSA_PUBKEY(o,p)
#define PEM_write_bio_PrivateKey_traditional(m, n, o, p, q, r, s) PEM_write_bio_RSAPrivateKey(m, n , o, p, q, r, s)
#endif

typedef struct
{
    EVP_PKEY* rsa;
    int padding;
    int hashMode;
    int is_private_key;  /* cached once at construction; avoids per-call BIGNUM alloc on 3.x */
} rsaData;

/* Key names for the rsa hash structure */

#define KEY_KEY "_Key"
#define PADDING_KEY "_Padding"
#define HASH_KEY "_Hash_Mode"

#define PACKAGE_NAME "Crypt::OpenSSL::RSA"

RSA.xs  view on Meta::CPAN


#define PACKAGE_CROAK(p_message) croak("%s", (p_message))
#define CHECK_NEW(p_var, p_size, p_type) \
  if (New(0, p_var, p_size, p_type) == NULL) \
    { PACKAGE_CROAK("unable to alloc buffer"); }

#define THROW(p_result) if (!(p_result)) { error = 1; goto err; }

char _is_private(rsaData* p_rsa)
{
    return p_rsa->is_private_key;
}

static int _detect_private_key(EVP_PKEY* p_rsa)
{
#if OLD_CRUFTY_SSL_VERSION
    return (p_rsa->d != NULL);
#else
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    BIGNUM* d = NULL;
    EVP_PKEY_get_bn_param(p_rsa, OSSL_PKEY_PARAM_RSA_D, &d);
    if (d) {
        BN_clear_free(d);
        return 1;

RSA.xs  view on Meta::CPAN

    rsaData* rsa;

    CHECK_NEW(rsa, 1, rsaData);
    rsa->rsa = p_rsa;
#ifdef SHA512_DIGEST_LENGTH
    rsa->hashMode = NID_sha256;
#else
    rsa->hashMode = NID_sha1;
#endif
    rsa->padding = RSA_PKCS1_OAEP_PADDING;
    rsa->is_private_key = _detect_private_key(p_rsa);
    return sv_bless(
        newRV_noinc(newSViv((IV) rsa)),
        (SvROK(p_proto) ? SvSTASH(SvRV(p_proto)) : gv_stashsv(p_proto, 1)));
}

int get_digest_length(int hash_method)
{
    switch(hash_method)
    {
        case NID_md5:

RSA.xs  view on Meta::CPAN

#if OPENSSL_VERSION_NUMBER < 0x10100000L
    # might introduce memory leak without calling EVP_cleanup() on exit
    # see https://wiki.openssl.org/index.php/Library_Initialization
    ERR_load_crypto_strings();
    OpenSSL_add_all_algorithms();
#else
    # NOOP
#endif

SV*
_new_private_key_pem(proto, key_string_SV, passphrase_SV=&PL_sv_undef)
    SV* proto;
    SV* key_string_SV;
    SV* passphrase_SV;
  CODE:
    RETVAL = make_rsa_obj(
        proto, _load_rsa_key(key_string_SV, PEM_read_bio_PrivateKey, passphrase_SV));
  OUTPUT:
    RETVAL

SV*

RSA.xs  view on Meta::CPAN

#else
    pkey = d2i_RSAPublicKey_bio(bio, NULL);
#endif
    BIO_free(bio);
    CHECK_OPEN_SSL(pkey);
    RETVAL = make_rsa_obj(proto, pkey);
  OUTPUT:
    RETVAL

SV*
_new_private_key_der(proto, key_string_SV, passphrase_SV=&PL_sv_undef)
    SV* proto;
    SV* key_string_SV;
    SV* passphrase_SV;
  PREINIT:
    STRLEN keyStringLength;
    char* keyString;
    EVP_PKEY* pkey;
    BIO* bio;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    OSSL_DECODER_CTX* dctx;

RSA.xs  view on Meta::CPAN

    RETVAL

void
DESTROY(p_rsa)
    rsaData* p_rsa;
  CODE:
    EVP_PKEY_free(p_rsa->rsa);
    Safefree(p_rsa);

SV*
get_private_key_string(p_rsa, passphrase_SV=&PL_sv_undef, cipher_name_SV=&PL_sv_undef)
    rsaData* p_rsa;
    SV* passphrase_SV;
    SV* cipher_name_SV;
  PREINIT:
    BIO* stringBIO;
    char* passphrase = NULL;
    STRLEN passphraseLength = 0;
    char* cipher_name;
    const EVP_CIPHER* enc = NULL;
  CODE:

RSA.xs  view on Meta::CPAN


    CHECK_OPEN_SSL(stringBIO = BIO_new(BIO_s_mem()));
    CHECK_OPEN_SSL_BIO(PEM_write_bio_PrivateKey_traditional(
        stringBIO, p_rsa->rsa, enc, (unsigned char* ) passphrase, passphraseLength, NULL, NULL), stringBIO);
    RETVAL = extractBioString(stringBIO);

  OUTPUT:
    RETVAL

SV*
get_private_key_pkcs8_string(p_rsa, passphrase_SV=&PL_sv_undef, cipher_name_SV=&PL_sv_undef)
    rsaData* p_rsa;
    SV* passphrase_SV;
    SV* cipher_name_SV;
  PREINIT:
    BIO* stringBIO;
    char* passphrase = NULL;
    STRLEN passphraseLength = 0;
    char* cipher_name;
    const EVP_CIPHER* enc = NULL;
  CODE:

t/der.t  view on Meta::CPAN

$pub_from_x509_der->use_sha256_hash();
ok( $pub_from_x509_der->verify($plaintext, $sig),
    "X.509 DER-loaded key verifies signature" );

$pub_from_pkcs1_der->use_sha256_hash();
ok( $pub_from_pkcs1_der->verify($plaintext, $sig),
    "PKCS#1 DER-loaded key verifies signature" );

# --- Private key DER support ---

my $priv_pem = $rsa->get_private_key_string();
my $priv_der = pem_to_der($priv_pem);

is( ord(substr($priv_der, 0, 1)), 0x30, "Private key DER starts with SEQUENCE tag" );

my $priv_from_der;
ok( $priv_from_der = Crypt::OpenSSL::RSA->new_private_key($priv_der),
    "new_private_key loads DER-encoded private key" );

ok( $priv_from_der->is_private(),
    "DER-loaded private key is recognized as private" );

is( $priv_from_der->get_public_key_x509_string(), $x509_pem,
    "DER-loaded private key exports same public key" );

# Verify DER-loaded private key can sign and original public key can verify
$priv_from_der->use_sha256_hash();
my $sig2 = $priv_from_der->sign($plaintext);
ok( $pub_from_x509_der->verify($plaintext, $sig2),
    "signature from DER-loaded private key verifies" );

# Error: DER-like data for private key
eval { Crypt::OpenSSL::RSA->new_private_key("\x30\x00") };
ok( $@, "new_private_key croaks on truncated DER data" );

# Error: bogus binary data for private key
eval { Crypt::OpenSSL::RSA->new_private_key("\x01\x02\x03\x04") };
like( $@, qr/unrecognized key format/,
    "new_private_key gives helpful error on random binary data" );

# PEM private keys still work through the wrapper
my $priv_from_pem;
ok( $priv_from_pem = Crypt::OpenSSL::RSA->new_private_key($priv_pem),
    "new_private_key still loads PEM-encoded private key" );

# --- Error cases ---

# DER-like data that isn't a valid key (no RSA OID, so falls through to PKCS#1 path)
eval { Crypt::OpenSSL::RSA->new_public_key("\x30\x00") };
ok( $@, "new_public_key croaks on truncated DER data" );

# Completely bogus binary data (not starting with 0x30)
eval { Crypt::OpenSSL::RSA->new_public_key("\x01\x02\x03\x04") };
like( $@, qr/unrecognized key format/,
    "new_public_key gives helpful error on random binary data" );

# Empty string
eval { Crypt::OpenSSL::RSA->new_public_key("") };
like( $@, qr/unrecognized key format/,
    "new_public_key gives helpful error on empty string" );

# --- Encrypted PKCS#8 DER private key with passphrase ---

my $passphrase = 'test_der_pass';
my $enc_pkcs8_pem = $rsa->get_private_key_pkcs8_string($passphrase, 'aes-128-cbc');
my $enc_pkcs8_der = pem_to_der($enc_pkcs8_pem);

is( ord(substr($enc_pkcs8_der, 0, 1)), 0x30,
    "Encrypted PKCS#8 DER starts with SEQUENCE tag" );

my $priv_from_enc_der;
ok( $priv_from_enc_der = Crypt::OpenSSL::RSA->new_private_key($enc_pkcs8_der, $passphrase),
    "new_private_key loads encrypted PKCS#8 DER with passphrase" );

ok( $priv_from_enc_der->is_private(),
    "Encrypted PKCS#8 DER-loaded key is private" );

is( $priv_from_enc_der->get_public_key_x509_string(), $x509_pem,
    "Encrypted PKCS#8 DER key exports same public key as original" );

$priv_from_enc_der->use_sha256_hash();
my $sig3 = $priv_from_enc_der->sign($plaintext);
ok( $pub_from_x509_der->verify($plaintext, $sig3),
    "Signature from encrypted PKCS#8 DER-loaded key verifies" );

eval { Crypt::OpenSSL::RSA->new_private_key($enc_pkcs8_der, 'wrong_pass') };
ok( $@, "new_private_key croaks on wrong passphrase for encrypted PKCS#8 DER" );

# PEM header for wrong type
eval { Crypt::OpenSSL::RSA->new_public_key("-----BEGIN CERTIFICATE-----\nfoo\n-----END CERTIFICATE-----\n") };
like( $@, qr/unrecognized key format/,
    "new_public_key gives helpful error on certificate PEM" );

# --- Non-RSA DER key rejection ---
# On OpenSSL 3.x, d2i_PUBKEY_bio() accepts any key type.
# _new_public_key_x509_der must reject non-RSA keys.

t/error.t  view on Meta::CPAN

use Crypt::OpenSSL::RSA;

Crypt::OpenSSL::Random::random_seed("OpenSSL needs at least 32 bytes.");
Crypt::OpenSSL::RSA->import_random_seed();

my $rsa = Crypt::OpenSSL::RSA->generate_key(2048);
my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($rsa->get_public_key_string());

# --- Malformed key loading ---

eval { Crypt::OpenSSL::RSA->new_private_key("not a key at all") };
ok($@, "new_private_key croaks on garbage input");

eval { Crypt::OpenSSL::RSA->new_private_key("") };
ok($@, "new_private_key croaks on empty string");

eval { Crypt::OpenSSL::RSA->new_private_key(undef) };
ok($@, "new_private_key croaks on undef");

eval {
    Crypt::OpenSSL::RSA->new_private_key(
        "-----BEGIN RSA PRIVATE KEY-----\ngarbage\n-----END RSA PRIVATE KEY-----\n"
    );
};
ok($@, "new_private_key croaks on corrupted PEM body");

eval { Crypt::OpenSSL::RSA->_new_public_key_pkcs1("not a key") };
ok($@, "_new_public_key_pkcs1 croaks on garbage input");

eval { Crypt::OpenSSL::RSA->_new_public_key_x509("not a key") };
ok($@, "_new_public_key_x509 croaks on garbage input");

# --- Unrecognized public key format (Perl-level croak) ---

eval { Crypt::OpenSSL::RSA->new_public_key("-----BEGIN CERTIFICATE-----\nfoo\n-----END CERTIFICATE-----\n") };
like($@, qr/unrecognized key format/, "new_public_key croaks on unrecognized PEM header");

eval { Crypt::OpenSSL::RSA->new_public_key("just plain text") };
like($@, qr/unrecognized key format/, "new_public_key croaks on plain text");

# --- Wrong passphrase on encrypted key ---

my $encrypted_pem = $rsa->get_private_key_string("correct_passphrase", "aes-128-cbc");
eval { Crypt::OpenSSL::RSA->new_private_key($encrypted_pem, "wrong_passphrase") };
ok($@, "new_private_key croaks on wrong passphrase");

# Note: testing with no passphrase or empty passphrase is intentionally
# omitted — OpenSSL may prompt on the terminal, hanging non-interactive runs.

# --- Public key cannot perform private operations ---

eval { $rsa_pub->sign("hello") };
like($@, qr/Public keys cannot sign/i, "public key cannot sign");

eval { $rsa_pub->decrypt("hello") };

t/format.t  view on Meta::CPAN

pV/9iVvswnnSsxEanoLchzA1bAaDNa9vkIU/BrFwQO9ctw+RQbHrvc/5KPbZoGsq
bfQ/wOXUnQJBAMs/ZGlziX19lOEGfziugMR33ybLxkBS7qcrpBebAED/8etijASp
LgMEOKeRz11WAVJJ5A4wi1yxD4fnBxp44xkCQG4RejNbPVByYQdlJPeD5Aijxta6
nBWGVuKNPuC80XjHpz6Yj9lDt5wH+EkJhA1ZaJKztWNbRoZ5e4x4PcubYXECQHA0
KubcVcblkU85Gvrbu1K7KoJsdKIGJqI7QXeWpmk74v4jhVD9ZN1dczlvEZ9hX5Fi
IXiD7Cvbw8svC4jdu+ECQQCw1ZlQPz2rGE+pFQiKOFPprH+pT+zkINh1d83jeMYd
GG7hKgfQB5J/B0u8/XzEtGnCq8m0xTADx2eplIoKhAFi
-----END RSA PRIVATE KEY-----
EOF

my ( $private_key, $public_key, $private_key2 );

ok( $private_key = Crypt::OpenSSL::RSA->new_private_key($PRIVATE_KEY_STRING), "load private key from string" );
is( $private_key->get_private_key_string(), $PRIVATE_KEY_STRING, "private key round-trips" );
is( $private_key->get_public_key_string(), $PUBLIC_KEY_PKCS1_STRING, "PKCS1 public key matches expected" );
is( $private_key->get_public_key_x509_string(), $PUBLIC_KEY_X509_STRING, "X509 public key matches expected" );

ok( $public_key = Crypt::OpenSSL::RSA->new_public_key($PUBLIC_KEY_PKCS1_STRING), "load PKCS1 public key" );
is( $public_key->get_public_key_string(), $PUBLIC_KEY_PKCS1_STRING, "PKCS1 public key round-trips" );
is( $public_key->get_public_key_x509_string(), $PUBLIC_KEY_X509_STRING, "PKCS1 key exports to X509 correctly" );

ok( $public_key = Crypt::OpenSSL::RSA->new_public_key($PUBLIC_KEY_X509_STRING), "load X509 public key" );
is( $public_key->get_public_key_string(), $PUBLIC_KEY_PKCS1_STRING, "X509 key exports to PKCS1 correctly" );
is( $public_key->get_public_key_x509_string(), $PUBLIC_KEY_X509_STRING, "X509 public key round-trips" );

# get_public_key_pkcs1_string is an alias for get_public_key_string
is( $private_key->get_public_key_pkcs1_string(), $PUBLIC_KEY_PKCS1_STRING, "get_public_key_pkcs1_string returns PKCS1 from private key" );
is( $public_key->get_public_key_pkcs1_string(), $PUBLIC_KEY_PKCS1_STRING, "get_public_key_pkcs1_string returns PKCS1 from public key" );
is( $private_key->get_public_key_pkcs1_string(), $private_key->get_public_key_string(), "pkcs1 alias matches get_public_key_string on private key" );
ok( $public_key = Crypt::OpenSSL::RSA->new_public_key($private_key->get_public_key_pkcs1_string()), "new_public_key accepts output of get_public_key_pkcs1_string" );

my $passphrase = '123456';
ok( $private_key = Crypt::OpenSSL::RSA->new_private_key( $ENCRYPT_PRIVATE_KEY_STRING, $passphrase ), "load encrypted private key" );
is( $private_key->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "encrypted key decrypts to expected private key" );
ok( $private_key  = Crypt::OpenSSL::RSA->new_private_key($DECRYPT_PRIVATE_KEY_STRING), "load decrypted private key" );
ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string($passphrase), $passphrase ), "re-encrypt and reload with passphrase" );
is( $private_key2->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "re-encrypted key round-trips" );
ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string( $passphrase, 'des3' ), $passphrase ), "encrypt with des3 and reload" );
is( $private_key2->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "des3-encrypted key round-trips" );
ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string( $passphrase, 'aes-128-cbc' ), $passphrase ), "encrypt with aes-128-cbc and reload" );
is( $private_key2->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "aes-128-cbc-encrypted key round-trips" );

# --- Additional cipher algorithms ---

ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string( $passphrase, 'aes-256-cbc' ), $passphrase ), "encrypt with aes-256-cbc and reload" );
is( $private_key2->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "aes-256-cbc-encrypted key round-trips" );

ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string( $passphrase, 'aes-192-cbc' ), $passphrase ), "encrypt with aes-192-cbc and reload" );
is( $private_key2->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "aes-192-cbc-encrypted key round-trips" );

# --- Passphrase with special characters ---

my $special_pass = q{p@ss!w0rd#$%^&*()};
ok( $private_key2 = Crypt::OpenSSL::RSA->new_private_key( $private_key->get_private_key_string($special_pass), $special_pass ), "passphrase with special characters round-trips" );
is( $private_key2->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING, "special-char passphrase key decrypts correctly" );

# --- Error: cipher specified without passphrase ---

eval { $private_key->get_private_key_string(undef, 'des3') };
like($@, qr/Passphrase is required for cipher/, "get_private_key_string croaks when cipher given without passphrase");

# --- Error: unsupported cipher name ---

eval { $private_key->get_private_key_string($passphrase, 'bogus-cipher-xyz') };
like($@, qr/Unsupported cipher/, "get_private_key_string croaks on unsupported cipher");

# --- Error: export private key from public-only key ---

my $pub_only = Crypt::OpenSSL::RSA->new_public_key($PUBLIC_KEY_PKCS1_STRING);
eval { $pub_only->get_private_key_string() };
like($@, qr/Public keys cannot export private key strings/,
    "get_private_key_string croaks on public-only key");

# --- Error: wrong passphrase on re-import ---

my $encrypted_pem = $private_key->get_private_key_string($passphrase, 'aes-128-cbc');
eval { Crypt::OpenSSL::RSA->new_private_key($encrypted_pem, 'wrong_passphrase') };
ok($@, "new_private_key croaks on wrong passphrase");

# --- Error: garbage / truncated private key input ---

eval { Crypt::OpenSSL::RSA->new_private_key("not a valid PEM key\n") };
ok( $@, "new_private_key croaks on garbage input" );

eval { Crypt::OpenSSL::RSA->new_private_key("-----BEGIN RSA PRIVATE KEY-----\ngarbage\n-----END RSA PRIVATE KEY-----\n") };
ok( $@, "new_private_key croaks on truncated PEM" );

# --- Public key format detection ---

eval { Crypt::OpenSSL::RSA->new_public_key("-----BEGIN CERTIFICATE-----\nfoo\n-----END CERTIFICATE-----\n") };
like($@, qr/unrecognized key format/, "new_public_key croaks on certificate PEM header");

eval { Crypt::OpenSSL::RSA->new_public_key("not a PEM key at all") };
like($@, qr/unrecognized key format/, "new_public_key croaks on non-PEM input");

# --- PKCS#8 private key export ---

{
    my $rsa = Crypt::OpenSSL::RSA->new_private_key($DECRYPT_PRIVATE_KEY_STRING);
    my $pkcs8_pem = $rsa->get_private_key_pkcs8_string();
    like($pkcs8_pem, qr/^-----BEGIN PRIVATE KEY-----/m, "PKCS#8 output has correct header");
    like($pkcs8_pem, qr/-----END PRIVATE KEY-----\s*$/m, "PKCS#8 output has correct footer");
    unlike($pkcs8_pem, qr/BEGIN RSA PRIVATE KEY/, "PKCS#8 output is not PKCS#1 format");

    # encrypted PKCS#8 export
    my $pass = 'test_pkcs8_pass';
    my $enc_pem = $rsa->get_private_key_pkcs8_string($pass, 'aes-128-cbc');
    like($enc_pem, qr/^-----BEGIN ENCRYPTED PRIVATE KEY-----/m,
         "encrypted PKCS#8 has correct header");

    # Round-trip tests require new_private_key to read PKCS#8.  On pre-3.x
    # PEM_read_bio_PrivateKey is macro'd to PEM_read_bio_RSAPrivateKey which
    # only reads PKCS#1, so these must be skipped.
    SKIP: {
        skip "new_private_key cannot read PKCS#8 on OpenSSL < 3.x", 3
            if $major < 3;

        my $reimported = Crypt::OpenSSL::RSA->new_private_key($pkcs8_pem);
        is($reimported->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING,
           "PKCS#8 round-trip: re-import then export as PKCS#1 matches original");
        is($reimported->get_private_key_pkcs8_string(), $pkcs8_pem,
           "PKCS#8 round-trip: re-export as PKCS#8 matches");

        my $dec_rsa = Crypt::OpenSSL::RSA->new_private_key($enc_pem, $pass);
        is($dec_rsa->get_private_key_string(), $DECRYPT_PRIVATE_KEY_STRING,
           "encrypted PKCS#8 round-trip decrypts to original key");
    }

    # error: cipher without passphrase
    eval { $rsa->get_private_key_pkcs8_string(undef, 'des3') };
    like($@, qr/Passphrase is required for cipher/,
         "get_private_key_pkcs8_string croaks when cipher given without passphrase");

    # error: unsupported cipher
    eval { $rsa->get_private_key_pkcs8_string($pass, 'bogus-cipher-xyz') };
    like($@, qr/Unsupported cipher/,
         "get_private_key_pkcs8_string croaks on unsupported cipher");
}

# --- X509 public key from private key matches PKCS1 ---

my $priv_for_x509 = Crypt::OpenSSL::RSA->new_private_key($PRIVATE_KEY_STRING);
ok( $public_key = Crypt::OpenSSL::RSA->new_public_key($priv_for_x509->get_public_key_x509_string()), "load X509 public key from private key" );
is( $public_key->get_public_key_string(), $PUBLIC_KEY_PKCS1_STRING, "X509 from private key matches PKCS1" );

# --- Non-RSA key rejection ---
# On OpenSSL 3.x, the generic PEM loaders accept any key type.
# Verify we reject non-RSA keys with a clear error.

SKIP: {
    my $ec_pem = `openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 2>/dev/null`;
    skip "EC key generation not available", 4
        unless ($? >> 8) == 0 && $ec_pem =~ /-----BEGIN PRIVATE KEY-----/;

    eval { Crypt::OpenSSL::RSA->new_private_key($ec_pem) };
    ok($@, "new_private_key rejects EC private key");
    like($@, qr/not an RSA key|expecting an rsa key|ASN1/i, "EC private key error message mentions RSA");

    my ($tmpfh, $tmpfile) = tempfile(UNLINK => 1);
    print $tmpfh $ec_pem;
    close $tmpfh;
    my $ec_pub = `openssl pkey -in $tmpfile -pubout 2>/dev/null`;
    skip "EC public key export failed", 2
        unless ($? >> 8) == 0 && $ec_pub =~ /-----BEGIN PUBLIC KEY-----/;
    eval { Crypt::OpenSSL::RSA->new_public_key($ec_pub) };
    ok($@, "new_public_key rejects EC public key");

t/format.t  view on Meta::CPAN

# traditional RSA, so RSA-PSS keys should also be rejected.

SKIP: {
    my $rsa_pss_pem = `openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:2048 2>/dev/null`;
    skip "RSA-PSS key generation not available", 4
        unless ($? >> 8) == 0 && $rsa_pss_pem =~ /-----BEGIN PRIVATE KEY-----/;

    # On pre-3.x OpenSSL, RSA-PSS keys are loaded via RSA-specific PEM
    # readers which accept them (they are structurally RSA).  The
    # EVP_PKEY_get_base_id() rejection only exists on OpenSSL 3.x+.
    eval { Crypt::OpenSSL::RSA->new_private_key($rsa_pss_pem) };
    skip "RSA-PSS rejection not supported on this OpenSSL version (pre-3.x)", 4
        unless $@;

    ok(1, "new_private_key rejects RSA-PSS private key");
    like($@, qr/not an RSA key|expecting an rsa key|ASN1/i, "RSA-PSS private key error message mentions RSA");

    my ($tmpfh, $tmpfile) = tempfile(UNLINK => 1);
    print $tmpfh $rsa_pss_pem;
    close $tmpfh;
    my $rsa_pss_pub = `openssl pkey -in $tmpfile -pubout 2>/dev/null`;
    skip "RSA-PSS public key export failed", 2
        unless ($? >> 8) == 0 && $rsa_pss_pub =~ /-----BEGIN PUBLIC KEY-----/;
    eval { Crypt::OpenSSL::RSA->new_public_key($rsa_pss_pub) };
    ok($@, "new_public_key rejects RSA-PSS public key");

t/key_lifecycle.t  view on Meta::CPAN

$cross_verify = eval { $rsa1->verify($plaintext, $sig2) };
ok( !$@, "reverse cross-key verify does not croak" );
ok( !$cross_verify, "reverse cross-key verify returns false" );

# Same key should verify its own signature
ok( $rsa1->verify($plaintext, $sig1), "key1 verifies its own signature" );
ok( $rsa2->verify($plaintext, $sig2), "key2 verifies its own signature" );

# --- Key export → import → sign/verify round-trip ---

my $priv_pem  = $rsa1->get_private_key_string();
my $pub_pem   = $rsa1->get_public_key_string();
my $rsa1_copy = Crypt::OpenSSL::RSA->new_private_key($priv_pem);
my $rsa1_pub  = Crypt::OpenSSL::RSA->new_public_key($pub_pem);

$rsa1_copy->use_pkcs1_pss_padding();
$rsa1_pub->use_pkcs1_pss_padding();

my $sig_copy = $rsa1_copy->sign($plaintext);
ok( $rsa1_pub->verify($plaintext, $sig_copy),
    "exported private key signs, exported public key verifies" );
ok( $rsa1->verify($plaintext, $sig_copy),
    "original key verifies signature from exported key" );

t/openssl_der.t  view on Meta::CPAN

my $priv_d = get_parameter($priv_output, 'privateExponent', 'prime1');
my $priv_p = get_parameter($priv_output, 'prime1', 'prime2');
my $priv_q = get_parameter($priv_output, 'prime2', 'exponent1');
my $priv_dmp1 = get_parameter($priv_output, 'exponent1', 'exponent2');
my $priv_dmq1 = get_parameter($priv_output, 'exponent2', 'coefficient');
my $priv_iqmp = get_parameter($priv_output, 'coefficient', '-----BEGIN .*PRIVATE KEY-----');
my $priv_pem = extract_pem_body($priv_output,
    '-----BEGIN .*PRIVATE KEY-----', '-----END .*PRIVATE KEY-----');

# Load the private key from the DER (base64 decoded PEM)
my $rsa = Crypt::OpenSSL::RSA->new_private_key(decode_base64($priv_pem));

# Get the private key parameters
my ($n, $e, $d, $p, $q, $dmp1, $dmq1, $iqmp) = $rsa->get_key_parameters();

# Check each private key parameter to the expected values
ok(compare_bignum_to_hex($n, $priv_n) == 0, "Imported DER n parameter matches expected");
ok(compare_bignum_to_hex($e, $priv_e) == 0, "Imported DER e parameter matches expected");
ok(compare_bignum_to_hex($d, $priv_d) == 0, "Imported DER d parameter matches expected");
ok(compare_bignum_to_hex($p, $priv_p) == 0, "Imported DER p parameter matches expected");
ok(compare_bignum_to_hex($q, $priv_q) == 0, "Imported DER q parameter matches expected");

t/padding.t  view on Meta::CPAN

    }
}

Crypt::OpenSSL::Random::random_seed("OpenSSL needs at least 32 bytes.");
Crypt::OpenSSL::RSA->import_random_seed();

my $rsa = Crypt::OpenSSL::RSA->generate_key(2048);
is( $rsa->size() * 8, 2048, "2048-bit key has correct size" );
ok( $rsa->check_key(), "2048-bit key passes check_key()" );

my $private_key_string = $rsa->get_private_key_string();
my $public_key_string  = $rsa->get_public_key_string();

ok( $private_key_string and $public_key_string, "key strings are non-empty" );

my $plaintext = "The quick brown fox jumped over the lazy dog";
my $rsa_priv  = Crypt::OpenSSL::RSA->new_private_key($private_key_string);
is( $rsa_priv->decrypt( $rsa_priv->encrypt($plaintext) ), $plaintext, "private key round-trips encrypt/decrypt" );

my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($public_key_string);

$plaintext .= $plaintext x 5;
# sslv23 is unsupported on OpenSSL 3.x but LibreSSL still supports it
SKIP: {
    skip "sslv23 is available on OpenSSL < 3.0 and LibreSSL", 2
        if $major lt '3.0' || $is_libressl;
    eval {

t/pkcs1_sign.t  view on Meta::CPAN

    like($@, qr/Marvin|vulnerable/i,
         "PKCS#1 v1.5 encryption still blocked (Marvin)");
}

# --- Reload key from PEM and verify signature ---
{
    $rsa->use_pkcs1_padding();
    $rsa->use_sha256_hash();
    my $sig = $rsa->sign("persistence test");

    my $priv_pem = $rsa->get_private_key_string();
    my $rsa2 = Crypt::OpenSSL::RSA->new_private_key($priv_pem);
    $rsa2->use_pkcs1_padding();
    $rsa2->use_sha256_hash();
    ok($rsa2->verify("persistence test", $sig),
       "signature verifies after key round-trip through PEM");

    my $rsa_pub2 = Crypt::OpenSSL::RSA->new_public_key($pub_pem);
    $rsa_pub2->use_pkcs1_padding();
    $rsa_pub2->use_sha256_hash();
    ok($rsa_pub2->verify("persistence test", $sig),
       "signature verifies with fresh public key object");

t/rsa.t  view on Meta::CPAN

$rsa->use_no_padding();
_Test_Encrypt_And_Decrypt( $rsa->size(), $rsa, 1 );

$rsa->use_pkcs1_oaep_padding();

# private_encrypt does not work with pkcs1_oaep_padding
_Test_Encrypt_And_Decrypt( $rsa->size() - 42, $rsa, 0 );

#FIXME - use_sslv23_padding seems to fail on decryption.  openssl bug?

my $private_key_string = $rsa->get_private_key_string();
my $public_key_string  = $rsa->get_public_key_string();

ok( $private_key_string and $public_key_string, "get_private_key_string and get_public_key_string return data" );

my $plaintext = "The quick brown fox jumped over the lazy dog";
my $rsa_priv  = Crypt::OpenSSL::RSA->new_private_key($private_key_string);
is( $rsa_priv->decrypt( $rsa_priv->encrypt($plaintext) ), $plaintext, "private key round-trips encrypt/decrypt" );

my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($public_key_string);
$rsa->use_pkcs1_oaep_padding();
is( $rsa->decrypt( $rsa_pub->encrypt($plaintext) ), $plaintext, "pub encrypt + priv decrypt round-trips" );

ok( $rsa_priv->is_private(), "private key reports is_private() true" );
ok( !$rsa_pub->is_private(), "public key reports is_private() false" );

_check_for_croak(



( run in 2.049 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )