From b24e1c087a0c8fb997b4671b298a77874a774864 Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Fri, 31 Oct 2025 21:36:39 -0300 Subject: [PATCH 1/3] Make padding selection clearer and update documentation Also add t/padding.t that attempts to test the paddingi combinations more thoroughly or possible more clearly? --- RSA.pm | 32 ++++++++++- RSA.xs | 19 +++++-- t/padding.t | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 6 deletions(-) create mode 100644 t/padding.t diff --git a/RSA.pm b/RSA.pm index b9d8d06..346d609 100644 --- a/RSA.pm +++ b/RSA.pm @@ -85,7 +85,9 @@ Crypt::OpenSSL::RSA - RSA encoding and decoding, using the openSSL libraries Version 0.35 makes the use of PKCS#1 v1.5 padding a fatal error. It is very difficult to implement PKCS#1 v1.5 padding securely. If you are still using RSA in in general, you should be looking at alternative encryption -algorithms. +algorithms. Version 0.36 implements RSA-PSS padding (PKCS#1 v2.1) and makes +setting an invalid padding a fatal error. Note, PKCS1_OAEP can only be used +for encryption and PKCS1_PSS can only be used for signing. =head1 DESCRIPTION @@ -112,6 +114,10 @@ C<-----BEGIN...-----> and C<-----END...-----> lines. The padding is set to PKCS1_OAEP, but can be changed with the C methods. +Note, PKCS1_OAEP can only be used for encryption. You must specifically +call use_pkcs1_pss_padding (or use_pkcs1_pss_padding) prior to signing +operations. + =item new_private_key Create a new C object by loading a private key in @@ -235,6 +241,22 @@ Sign a string using the secret (portion of the) key. Check the signature on a text. +=back + +=head1 Padding Methods + +Versions prior to 0.35 allowed using pkcs1 padding for both encryption +and signature operations but has been disabled for security reasons. + +While B can be used for encryption or signature operations +B is used for signature operations and +B is used for encryption operations. + +Version 0.38 sets the appropriate padding for each operation unless +B is called before either operation. + +=over + =item use_no_padding Use raw RSA encryption. This mode should only be used to implement @@ -254,7 +276,7 @@ L padding as defined in PKCS #1 v2.0 with SHA-1, MGF1 and an empty encoding parameter. This mode of padding is recommended for all new applications. It is the default mode used by -C. +C but is only valid for encryption/decryption. =item use_pkcs1_pss_padding @@ -273,6 +295,12 @@ denotes that the server is SSL3 capable. Not available since OpenSSL 3. +=back + +=head1 Hash/Digest Methods + +=over + =item use_md5_hash Use the RFC 1321 MD5 hashing algorithm by Ron Rivest when signing and diff --git a/RSA.xs b/RSA.xs index e3b3c27..7b2ba3e 100644 --- a/RSA.xs +++ b/RSA.xs @@ -339,7 +339,11 @@ SV* rsa_crypt(rsaData* p_rsa, SV* p_from, CHECK_OPEN_SSL(ctx); CHECK_OPEN_SSL(init_crypt(ctx) == 1); - CHECK_OPEN_SSL(EVP_PKEY_CTX_set_rsa_padding(ctx, p_rsa->padding) > 0); + int crypt_pad = p_rsa->padding; + if (p_rsa->padding != RSA_NO_PADDING) { + crypt_pad = RSA_PKCS1_OAEP_PADDING; + } + CHECK_OPEN_SSL(EVP_PKEY_CTX_set_rsa_padding(ctx, crypt_pad) > 0); CHECK_OPEN_SSL(p_crypt(ctx, NULL, &to_length, from, from_length) == 1); CHECK_OPEN_SSL(p_crypt(ctx, to, &to_length, from, from_length) == 1); @@ -980,7 +984,11 @@ sign(p_rsa, text_SV) CHECK_OPEN_SSL(ctx); CHECK_OPEN_SSL(EVP_PKEY_sign_init(ctx)); /* FIXME: Issue setting padding in some cases */ - CHECK_OPEN_SSL(EVP_PKEY_CTX_set_rsa_padding(ctx, p_rsa->padding) > 0); + int sign_pad = p_rsa->padding; + if (p_rsa->padding != RSA_NO_PADDING) { + sign_pad = RSA_PKCS1_PSS_PADDING; + } + CHECK_OPEN_SSL(EVP_PKEY_CTX_set_rsa_padding(ctx, sign_pad) > 0); EVP_MD* md = get_md_bynid(p_rsa->hashMode); CHECK_OPEN_SSL(md != NULL); @@ -1040,8 +1048,11 @@ PPCODE: CHECK_OPEN_SSL(ctx); CHECK_OPEN_SSL(EVP_PKEY_verify_init(ctx) == 1); /* FIXME: Issue setting padding in some cases */ - CHECK_OPEN_SSL(EVP_PKEY_CTX_set_rsa_padding(ctx, p_rsa->padding) > 0); - + int verify_pad = p_rsa->padding; + if (p_rsa->padding != RSA_NO_PADDING) { + verify_pad = RSA_PKCS1_PSS_PADDING; + } + CHECK_OPEN_SSL(EVP_PKEY_CTX_set_rsa_padding(ctx, verify_pad) > 0); EVP_MD* md = get_md_bynid(p_rsa->hashMode); CHECK_OPEN_SSL(md != NULL); diff --git a/t/padding.t b/t/padding.t new file mode 100644 index 0000000..092f50c --- /dev/null +++ b/t/padding.t @@ -0,0 +1,160 @@ +use strict; +use Test::More; + +use Crypt::OpenSSL::Random; +use Crypt::OpenSSL::RSA; +use Crypt::OpenSSL::Guess qw(openssl_version); + +my ($major, $minor, $patch) = openssl_version; + +BEGIN { + plan tests => 87 + ( UNIVERSAL::can( "Crypt::OpenSSL::RSA", "use_sha512_hash" ) ? 4 * 5 : 0 ); +} + +sub _Test_Encrypt_And_Decrypt { + my ( $p_plaintext_length, $p_rsa, $p_check_private_encrypt, $padding, $hash ) = @_; + + my ( $ciphertext, $decoded_text ); + my $plaintext = pack( + "C${p_plaintext_length}", + ( + 1, 255, 0, 128, 4, # Make sure these characters work + map { int( rand 256 ) } ( 1 .. $p_plaintext_length - 5 ) + ) + ); + ok( $ciphertext = $p_rsa->encrypt($plaintext), "Padding method $padding is valid for encrypting with $hash" ); + ok( $decoded_text = $p_rsa->decrypt($ciphertext), "Padding method $padding is valid for decrypting with $hash" ); + ok( $decoded_text eq $plaintext ); + + if ($p_check_private_encrypt) { + ok( $ciphertext = $p_rsa->private_encrypt($plaintext), "Padding method $padding is valid for private_encrypt with $hash" ); + ok( $decoded_text = $p_rsa->public_decrypt($ciphertext), "Padding method $padding is valid for private_decrypt with $hash" ); + ok( $decoded_text eq $plaintext ); + } +} + +sub _Test_Sign_And_Verify { + my ( $p_plaintext_length, $rsa, $rsa_pub, $padding, $hash ) = @_; + + my $plaintext = pack( + "C${p_plaintext_length}", + ( + 1, 255, 0, 128, 4, # Make sure these characters work + map { int( rand 256 ) } ( 1 .. $p_plaintext_length - 5 ) + ) + ); + + my $sig = eval { $rsa->sign($plaintext) }; + + SKIP: { + skip "OpenSSL error: illegal or unsupported padding mode - $hash", 6 if $@ =~ /illegal or unsupported padding mode/i; + skip "OpenSSL error: invalid digest - $hash", 6 if $@ =~ /invalid digest/i; + ok(!$@, "Padding method $padding is valid for signing with $hash"); + ok( $rsa_pub->verify( $plaintext, $sig ), "Padding method $padding is valid for verifying with $hash"); + + my $false_sig = unpack "H*", $sig; + $false_sig =~ tr/[a-f]/[0a-d]/; + ok( !$rsa_pub->verify( $plaintext, pack( "H*", $false_sig )), "rsa_pub: False signature does not verify"); + ok( !$rsa->verify( $plaintext, pack( "H*", $false_sig )), "rsa: False signature does not verify"); + + my $sig_of_other = $rsa->sign("different"); + ok( !$rsa_pub->verify( $plaintext, $sig_of_other ), "rsa_pub: plaintext does not match signature" ); + ok( !$rsa->verify( $plaintext, $sig_of_other ), "rsa: plaintext does not match signature"); + } +} + +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); +ok( $rsa->size() * 8 == 2048); +ok( $rsa->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 ); + +my $plaintext = "The quick brown fox jumped over the lazy dog"; +my $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($private_key_string); +ok( $plaintext eq $rsa_priv->decrypt( $rsa_priv->encrypt($plaintext) ) ); + +my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($public_key_string); + +my @unsupported_paddings = qw/pkcs1 sslv23/; + +$plaintext .= $plaintext x 5; +# pkcs1 sslv23 are unsupported methods +foreach my $pad (@unsupported_paddings) { + my $method = "use_${pad}_padding"; + SKIP: { + skip "OpenSSL version less than 3.0 supports sslv23", 1 + if $major lt '3.0' && $pad eq 'sslv23'; + eval { + $rsa->$method; + }; + ok($@, "Padding method $pad unsupported"); + } +} + +my @supported_paddings = qw/no pkcs1_pss pkcs1_oaep/; +# no pkcs1_pss pkcs1_oaep are supported methods +foreach my $pad (@supported_paddings) { + my $method = "use_${pad}_padding"; + eval { + $rsa->$method; + }; + ok(!$@, "Padding method $pad supported"); +} + +my @hashes = qw/md5 sha1 sha224 sha256 sha384 sha512 ripemd160/; # whirlpool/; + +my %padding_methods = ( + 'no' => {'sign' => 1, 'encrypt' => 1, 'pad' => 0}, + 'pkcs1_pss' => {'sign' => 1, 'encrypt' => 0, 'pad' => 1}, + 'pkcs1_oaep' => {'sign' => 0, 'encrypt' => 1, 'pad' => 42}, + 'pkcs1' => {'sign' => 0, 'encrypt' => 0, 'pad' => 11}, + #'sslv23' => {'sign' => 0, 'encrypt' => 0, 'pad' => 11}, + ); + + +foreach my $padding (keys %padding_methods) { + diag $padding; + foreach my $hash (@hashes) { + next if $hash ne 'sha256' && $padding eq 'x931'; + my $props = $padding_methods{$padding}; + my $sign = $props->{sign}; + my $encrypt = $props->{encrypt}; + my $pad = $props->{pad}; + + my $hash_mth = "use_${hash}_hash"; + $rsa->$hash_mth; + $rsa_pub->$hash_mth; + my $method = "use_${padding}_padding"; + if ($sign || $encrypt ) { + $rsa->$method; + $rsa_pub->$method; + } + # Valid signing methods + if ($sign && $pad) { + _Test_Sign_And_Verify( $rsa->size() - $pad, $rsa, $rsa_pub, $padding, $hash ); + } + + # Invalid signing methods + if ((!$sign) && $pad) { + eval { + $rsa->$method; + $rsa->sign($plaintext); + }; + ok(defined $@, "Padding $padding is invalid for signing"); + } + + # Valid encryption methods with padding + if ($encrypt) { + _Test_Encrypt_And_Decrypt( $rsa->size() - $pad, $rsa, 0, $padding, $hash ); + } + + } +} + +# Try From 60a4b569e53f5be801905ab7ac3d79fc8d79aa14 Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Sat, 1 Nov 2025 21:09:34 -0300 Subject: [PATCH 2/3] Update README files to reflect pod updates --- README | 47 +++++++++++++++++++++++++++++++++++++++++++---- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 87 insertions(+), 8 deletions(-) diff --git a/README b/README index 4fe1a46..0ed8661 100644 --- a/README +++ b/README @@ -13,7 +13,7 @@ SYNOPSIS $ciphertext = $rsa->encrypt($plaintext); $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($key_string); - $plaintext = $rsa->encrypt($ciphertext); + $plaintext = $rsa->decrypt($ciphertext); $rsa = Crypt::OpenSSL::RSA->generate_key(1024); # or $rsa = Crypt::OpenSSL::RSA->generate_key(1024, $prime); @@ -28,6 +28,15 @@ SYNOPSIS $signature = $rsa_priv->sign($plaintext); print "Signed correctly\n" if ($rsa->verify($plaintext, $signature)); +SECURITY + Version 0.35 makes the use of PKCS#1 v1.5 padding a fatal error. It is + very difficult to implement PKCS#1 v1.5 padding securely. If you are + still using RSA in in general, you should be looking at alternative + encryption algorithms. Version 0.36 implements RSA-PSS padding (PKCS#1 + v2.1) and makes setting an invalid padding a fatal error. Note, + PKCS1_OAEP can only be used for encryption and PKCS1_PSS can only be + used for signing. + DESCRIPTION "Crypt::OpenSSL::RSA" provides the ability to RSA encrypt strings which are somewhat shorter than the block size of a key. It also allows for @@ -48,6 +57,10 @@ Class Methods The padding is set to PKCS1_OAEP, but can be changed with the "use_xxx_padding" methods. + Note, PKCS1_OAEP can only be used for encryption. You must + specifically call use_pkcs1_pss_padding (or use_pkcs1_pss_padding) + prior to signing operations. + new_private_key Create a new "Crypt::OpenSSL::RSA" object by loading a private key in from an string containing the Base64/DER encoding of the PKCS1 @@ -140,20 +153,45 @@ Instance Methods verify Check the signature on a text. +Padding Methods + Versions prior to 0.35 allowed using pkcs1 padding for both encryption + and signature operations but has been disabled for security reasons. + + While use_no_padding can be used for encryption or signature operations + use_pkcs1_pss_padding is used for signature operations and + use_pkcs1_oaep_padding is used for encryption operations. + + Version 0.38 sets the appropriate padding for each operation unless + use_no_padding is called before either operation. + use_no_padding Use raw RSA encryption. This mode should only be used to implement cryptographically sound padding modes in the application code. Encrypting user data directly with RSA is insecure. use_pkcs1_padding - Use PKCS #1 v1.5 padding. This currently is the most widely used - mode of padding. + PKCS #1 v1.5 padding has been disabled as it is nearly impossible to + use this padding method in a secure manner. It is known to be + vulnerable to timing based side channel attacks. use_pkcs1_padding() + results in a fatal error. + + Marvin Attack + use_pkcs1_oaep_padding Use "EME-OAEP" padding as defined in PKCS #1 v2.0 with SHA-1, MGF1 and an empty encoding parameter. This mode of padding is recommended for all new applications. It is the default mode used by - "Crypt::OpenSSL::RSA". + "Crypt::OpenSSL::RSA" but is only valid for encryption/decryption. + + use_pkcs1_pss_padding + Use RSA-PSS padding as defined in PKCS#1 v2.1. In general, RSA-PSS + should be used as a replacement for RSA-PKCS#1 v1.5. The module + specifies the message digest being requested and the appropriate mgf1 + setting and salt length for the digest. + + Note: RSA-PSS cannot be used for encryption/decryption and results in + a fatal error. Call use_pkcs1_oaep_padding for encryption operations. use_sslv23_padding Use "PKCS #1 v1.5" padding with an SSL-specific modification that @@ -161,6 +199,7 @@ Instance Methods Not available since OpenSSL 3. +Hash/Digest Methods use_md5_hash Use the RFC 1321 MD5 hashing algorithm by Ron Rivest when signing and verifying messages. diff --git a/README.md b/README.md index d0f37a8..4b85a6c 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Crypt::OpenSSL::RSA - RSA encoding and decoding, using the openSSL libraries $ciphertext = $rsa->encrypt($plaintext); $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($key_string); - $plaintext = $rsa->encrypt($ciphertext); + $plaintext = $rsa->decrypt($ciphertext); $rsa = Crypt::OpenSSL::RSA->generate_key(1024); # or $rsa = Crypt::OpenSSL::RSA->generate_key(1024, $prime); @@ -31,6 +31,15 @@ Crypt::OpenSSL::RSA - RSA encoding and decoding, using the openSSL libraries $signature = $rsa_priv->sign($plaintext); print "Signed correctly\n" if ($rsa->verify($plaintext, $signature)); +# SECURITY + +Version 0.35 makes the use of PKCS#1 v1.5 padding a fatal error. It is +very difficult to implement PKCS#1 v1.5 padding securely. If you are still +using RSA in in general, you should be looking at alternative encryption +algorithms. Version 0.36 implements RSA-PSS padding (PKCS#1 v2.1) and makes +setting an invalid padding a fatal error. Note, PKCS1\_OAEP can only be used +for encryption and PKCS1\_PSS can only be used for signing. + # DESCRIPTION `Crypt::OpenSSL::RSA` provides the ability to RSA encrypt strings which are @@ -54,6 +63,10 @@ this (never documented) behavior is no longer the case. The padding is set to PKCS1\_OAEP, but can be changed with the `use_xxx_padding` methods. + Note, PKCS1\_OAEP can only be used for encryption. You must specifically + call use\_pkcs1\_pss\_padding (or use\_pkcs1\_pss\_padding) prior to signing + operations. + - new\_private\_key Create a new `Crypt::OpenSSL::RSA` object by loading a private key in @@ -165,6 +178,18 @@ this (never documented) behavior is no longer the case. Check the signature on a text. +# Padding Methods + +Versions prior to 0.35 allowed using pkcs1 padding for both encryption +and signature operations but has been disabled for security reasons. + +While **use\_no\_padding** can be used for encryption or signature operations +**use\_pkcs1\_pss\_padding** is used for signature operations and +**use\_pkcs1\_oaep\_padding** is used for encryption operations. + +Version 0.38 sets the appropriate padding for each operation unless +**use\_no\_padding** is called before either operation. + - use\_no\_padding Use raw RSA encryption. This mode should only be used to implement @@ -173,15 +198,28 @@ this (never documented) behavior is no longer the case. - use\_pkcs1\_padding - Use PKCS #1 v1.5 padding. This currently is the most widely used mode - of padding. + PKCS #1 v1.5 padding has been disabled as it is nearly impossible to use this + padding method in a secure manner. It is known to be vulnerable to timing + based side channel attacks. use\_pkcs1\_padding() results in a fatal error. + + [Marvin Attack](https://github.com/tomato42/marvin-toolkit/blob/master/README.md) - use\_pkcs1\_oaep\_padding Use `EME-OAEP` padding as defined in PKCS #1 v2.0 with SHA-1, MGF1 and an empty encoding parameter. This mode of padding is recommended for all new applications. It is the default mode used by - `Crypt::OpenSSL::RSA`. + `Crypt::OpenSSL::RSA` but is only valid for encryption/decryption. + +- use\_pkcs1\_pss\_padding + + Use `RSA-PSS` padding as defined in PKCS#1 v2.1. In general, RSA-PSS + should be used as a replacement for RSA-PKCS#1 v1.5. The module specifies + the message digest being requested and the appropriate mgf1 setting and + salt length for the digest. + + **Note**: RSA-PSS cannot be used for encryption/decryption and results in a + fatal error. Call `use_pkcs1_oaep_padding` for encryption operations. - use\_sslv23\_padding @@ -190,6 +228,8 @@ this (never documented) behavior is no longer the case. Not available since OpenSSL 3. +# Hash/Digest Methods + - use\_md5\_hash Use the RFC 1321 MD5 hashing algorithm by Ron Rivest when signing and From 06d12750b5bf4d0f7fb43644bad04628de6cc1fc Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Fri, 31 Oct 2025 22:21:18 -0300 Subject: [PATCH 3/3] Free openssl objects when done --- RSA.xs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/RSA.xs b/RSA.xs index 7b2ba3e..d6017ed 100644 --- a/RSA.xs +++ b/RSA.xs @@ -348,6 +348,7 @@ SV* rsa_crypt(rsaData* p_rsa, SV* p_from, CHECK_OPEN_SSL(p_crypt(ctx, to, &to_length, from, from_length) == 1); EVP_PKEY_CTX_free(ctx); + OSSL_LIB_CTX_free(ossllibctx); #else to_length = p_crypt( from_length, from, (unsigned char*) to, p_rsa->rsa, p_rsa->padding); @@ -1008,6 +1009,8 @@ sign(p_rsa, text_SV) CHECK_OPEN_SSL(EVP_PKEY_sign(ctx, signature, &signature_length, digest, get_digest_length(p_rsa->hashMode)) == 1); CHECK_OPEN_SSL(signature); + EVP_MD_free(md); + EVP_PKEY_CTX_free(ctx); #else CHECK_OPEN_SSL(RSA_sign(p_rsa->hashMode, digest, @@ -1084,6 +1087,10 @@ PPCODE: CHECK_OPEN_SSL(0); break; } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD_free(md); + EVP_PKEY_CTX_free(ctx); +#endif } int