Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

undocumented backwards compatibility break since 0.02 #9

Closed
maaarghk opened this issue Nov 8, 2023 · 2 comments · Fixed by #10
Closed

undocumented backwards compatibility break since 0.02 #9

maaarghk opened this issue Nov 8, 2023 · 2 comments · Fixed by #10

Comments

@maaarghk
Copy link

maaarghk commented Nov 8, 2023

hey,

in the POD you state:

The original version supports only AES 256 ECB (electronic codebook mode encryption).

this is not true. the "original" version supported 128, 192 and 256 based on the provided key length:

keysize = SvCUR(key);
if (keysize != 16 && keysize != 24 && keysize != 32)
croak ("The key must be 128, 192 or 256 bits long");
Newz(0, RETVAL, 1, struct state);
AES_set_encrypt_key(SvPV_nolen(key),keysize*8,&RETVAL->enc_key);
AES_set_decrypt_key(SvPV_nolen(key),keysize*8,&RETVAL->dec_key);

(note keysize*8 is passed)

i think for that reason it is wrong to set the cipher to AES-256-ECB when no options hash is provided. in get_cipher, in the case where name == NULL, keysize should be checked to determine the cipher.

here is an updated version of the original test file which passes on 0.02 but fails on latest:

# Before `make install' is performed this script should be runnable with
# `make test'. After `make install' it should work as `perl Crypt-OpenSSL-AES.t'

#########################

# change 'tests => 1' to 'tests => last_test_to_print';

use Test::More tests => 10;
BEGIN { use_ok('Crypt::OpenSSL::AES') };

my $key;
my $plaintext;
my $expected_enc;
my $c;

#
# test AES-256-ECB
#
$key = pack("C*",0x30,0x31,0x32,0x33,0x30,0x31,0x32,0x33,0x30,0x31,0x32,0x33,0x30,0x31,0x32,0x33,0x30,0x31,0x32,0x33,0x30,0x31,0x32
,0x33,0x30,0x31,0x32,0x33,0x30,0x31,0x32,0x33);
$plaintext = pack("C*",0x41,0x42,0x43,0x44,0x41,0x42,0x43,0x44,0x41,0x42,0x43,0x44,0x41,0x42,0x43,0x44);
$expected_enc = pack("C*", 0x9b, 0xc3, 0x7f, 0x1b, 0x92, 0x93, 0xcc, 0xf9, 0x6b, 0x64, 0x00, 0xae, 0xa3, 0xc8, 0x85, 0xbb);
$c = new Crypt::OpenSSL::AES($key);

ok(($encrypted = $c->encrypt($plaintext)) eq $expected_enc, "encrypt with key length 32 (AES-256-ECB)");
ok($c->decrypt($encrypted) eq $plaintext, "decrypt with key length 32 (AES-256-ECB)");
ok($c->decrypt($c->encrypt("Hello World. 123")) eq "Hello World. 123", "Simple String Encrypted/Decrypted Successfully with key length 32 AES-256-ECB");

# echo -n "ABCDABCDABCDABCD"| openssl enc -nopad -e -aes-256-ecb -K '3031323330313233303132333031323330313233303132333031323330313233' | xxd -i
# echo -n "ABCDABCDABCDABCD"| openssl enc -nopad -e -aes-192-ecb -K '303132333031323330313233303132333031323330313233'
# echo -n "ABCDABCDABCDABCD"| openssl enc -nopad -e -aes-128-ecb -K '30313233303132333031323330313233' | xxd -i

#
# test AES-192-ECB
#
$key = pack("C*",0x30,0x31,0x32,0x33,0x30,0x31,0x32,0x33,0x30,0x31,0x32,0x33,0x30,0x31,0x32,0x33,0x30,0x31,0x32,0x33,0x30,0x31,0x32,0x33);
$plaintext = pack("C*",0x41,0x42,0x43,0x44,0x41,0x42,0x43,0x44,0x41,0x42,0x43,0x44,0x41,0x42,0x43,0x44);
$expected_enc = pack("C*", 0xd3, 0xb8, 0xa7, 0xf0, 0xaa, 0x8f, 0x62, 0xc7, 0xb8, 0x78, 0xb7, 0xb3, 0xee, 0x47, 0x6a, 0x9f);
$c = new Crypt::OpenSSL::AES($key);

ok(($encrypted = $c->encrypt($plaintext)) eq $expected_enc, "encrypt with key length 24 (AES-192-ECB)");
ok($c->decrypt($encrypted) eq $plaintext, "decrypt with key length 24 (AES-192-ECB)");
ok($c->decrypt($c->encrypt("Hello World. 123")) eq "Hello World. 123", "Simple String Encrypted/Decrypted Successfully with key length 24 AES-192-ECB");

#
# test AES-128-ECB
#
$key = pack("C*",0x30,0x31,0x32,0x33,0x30,0x31,0x32,0x33,0x30,0x31,0x32,0x33,0x30,0x31,0x32,0x33);
$plaintext = pack("C*",0x41,0x42,0x43,0x44,0x41,0x42,0x43,0x44,0x41,0x42,0x43,0x44,0x41,0x42,0x43,0x44);
$expected_enc = pack("C*", 0x6a, 0xf2, 0xaa, 0x85, 0x28, 0xfc, 0x73, 0x3c, 0xda, 0x30, 0xd4, 0x9f, 0x43, 0x5c, 0x30, 0x4e);
$c = new Crypt::OpenSSL::AES($key);

ok(($encrypted = $c->encrypt($plaintext)) eq $expected_enc, "encrypt with key length 16 (AES-128-ECB)");
ok($c->decrypt($encrypted) eq $plaintext, "decrypt with key length 16 (AES-128-ECB)");
ok($c->decrypt($c->encrypt("Hello World. 123")) eq "Hello World. 123", "Simple String Encrypted/Decrypted Successfully with key length 16 AES-128-ECB");

cheers.

M

@timlegge
Copy link
Contributor

timlegge commented Nov 8, 2023

You make a great point. I will fix it some time this week. Thanks for the test. One of the problems updating this module was that the tests were a little sparse so it was difficult to be sure I was not breaking something.

@timlegge
Copy link
Contributor

timlegge commented Nov 8, 2023

This is a first stab at it. Once I looked at it I noticed that I was also assuming that the user would match the cipher to the key size. I may change it again to have the perl code remove the size from the cipher being passed in.

I noticed that I had also used a test with a mismatched cipher and a key that I had to change.

Tim

diff --git a/AES.xs b/AES.xs
index 2769d06..3db13f5 100644
--- a/AES.xs
+++ b/AES.xs
@@ -61,38 +61,42 @@ char * get_option_svalue (pTHX_ HV * options, char * name) {
 
 #if OPENSSL_VERSION_NUMBER >= 0x00908000L
 #ifdef LIBRESSL_VERSION_NUMBER
-const EVP_CIPHER * get_cipher(pTHX_ HV * options) {
+const EVP_CIPHER * get_cipher(pTHX_ HV * options, STRLEN keysize) {
 #else
-EVP_CIPHER * get_cipher(pTHX_ HV * options) {
+EVP_CIPHER * get_cipher(pTHX_ HV * options, STRLEN keysize) {
 #endif
     char * name = get_option_svalue(aTHX_ options, "cipher");
 
-    if (name == NULL)
+    if (name == NULL && keysize == 16)
+        return (EVP_CIPHER * ) EVP_aes_128_ecb();
+    else if (name == NULL && keysize == 24)
+        return (EVP_CIPHER * ) EVP_aes_192_ecb();
+    else if (name == NULL && keysize == 32)
         return (EVP_CIPHER * ) EVP_aes_256_ecb();
-    else if (strcmp(name, "AES-128-ECB") == 0)
+    else if (strcmp(name, "AES-128-ECB") == 0 && keysize == 16)
         return (EVP_CIPHER * ) EVP_aes_128_ecb();
-    else if (strcmp(name, "AES-192-ECB") == 0)
+    else if (strcmp(name, "AES-192-ECB") == 0 && keysize == 24)
         return (EVP_CIPHER * ) EVP_aes_192_ecb();
-    else if (strcmp(name, "AES-256-ECB") == 0)
+    else if (strcmp(name, "AES-256-ECB") == 0 && keysize == 32)
         return (EVP_CIPHER * ) EVP_aes_256_ecb();
-    else if (strcmp(name, "AES-128-CBC") == 0)
+    else if (strcmp(name, "AES-128-CBC") == 0 && keysize == 16)
         return (EVP_CIPHER * ) EVP_aes_128_cbc();
-    else if (strcmp(name, "AES-192-CBC") == 0)
+    else if (strcmp(name, "AES-192-CBC") == 0 && keysize == 24)
         return (EVP_CIPHER * ) EVP_aes_192_cbc();
-    else if (strcmp(name, "AES-256-CBC") == 0)
+    else if (strcmp(name, "AES-256-CBC") == 0 && keysize == 32)
         return (EVP_CIPHER * ) EVP_aes_256_cbc();
-    else if (strcmp(name, "AES-128-CFB") == 0)
+    else if (strcmp(name, "AES-128-CFB") == 0 && keysize == 16)
         return (EVP_CIPHER * ) EVP_aes_128_cfb();
-    else if (strcmp(name, "AES-192-CFB") == 0)
+    else if (strcmp(name, "AES-192-CFB") == 0 && keysize == 24)
         return (EVP_CIPHER * ) EVP_aes_192_cfb();
-    else if (strcmp(name, "AES-256-CFB") == 0)
+    else if (strcmp(name, "AES-256-CFB") == 0 && keysize == 32)
         return (EVP_CIPHER * ) EVP_aes_256_cfb();
 #if OPENSSL_VERSION_NUMBER >=  0x10001000L
-    else if (strcmp(name, "AES-128-CTR") == 0)
+    else if (strcmp(name, "AES-128-CTR") == 0 && keysize == 16)
         return (EVP_CIPHER * ) EVP_aes_128_ctr();
-    else if (strcmp(name, "AES-192-CTR") == 0)
+    else if (strcmp(name, "AES-192-CTR") == 0 && keysize == 24)
         return (EVP_CIPHER * ) EVP_aes_192_ctr();
-    else if (strcmp(name, "AES-256-CTR") == 0)
+    else if (strcmp(name, "AES-256-CTR") == 0 && keysize == 32)
         return (EVP_CIPHER * ) EVP_aes_256_ctr();
 #else
     else if (
@@ -101,21 +105,28 @@ EVP_CIPHER * get_cipher(pTHX_ HV * options) {
         (strcmp(name, "AES-256-CTR") == 0))
         croak ("CTR ciphers not supported on this version of OpenSSL");
 #endif
-    else if (strcmp(name, "AES-128-OFB") == 0)
+    else if (strcmp(name, "AES-128-OFB") == 0 && keysize == 16)
         return (EVP_CIPHER * ) EVP_aes_128_ofb();
-    else if (strcmp(name, "AES-192-OFB") == 0)
+    else if (strcmp(name, "AES-192-OFB") == 0 && keysize == 24)
         return (EVP_CIPHER * ) EVP_aes_192_ofb();
-    else if (strcmp(name, "AES-256-OFB") == 0)
+    else if (strcmp(name, "AES-256-OFB") == 0 && keysize == 32)
         return (EVP_CIPHER * ) EVP_aes_256_ofb();
     else
         croak ("You specified an unsupported cipher");
 }
 #endif
 
-char * get_cipher_name (pTHX_ HV * options) {
+char * get_cipher_name (pTHX_ HV * options, long long keysize) {
     char * value = get_option_svalue(aTHX_ options, "cipher");
     if (value == NULL)
-        return "AES-256-ECB";
+        if (keysize == 16)
+            return "AES-128-ECB";
+        else if (keysize == 24)
+            return "AES-192-ECB";
+        else if (keysize == 32)
+            return "AES-256-ECB";
+        else
+            croak ("get_cipher_name - Unsupported Key Size");
 
     return value;
 }
@@ -182,9 +193,9 @@ CODE:
         Newz(0, RETVAL, 1, struct state);
         RETVAL->padding = get_padding(aTHX_ options);
 #if OPENSSL_VERSION_NUMBER >= 0x00908000L
-        cipher = get_cipher(aTHX_ options);
+        cipher = get_cipher(aTHX_ options, keysize);
         iv = get_iv(aTHX_ options);
-        cipher_name = get_cipher_name(aTHX_ options);
+        cipher_name = get_cipher_name(aTHX_ options, keysize);
         if ((strcmp(cipher_name, "AES-128-ECB") == 0 ||
             strcmp(cipher_name, "AES-192-ECB") == 0 ||
             strcmp(cipher_name, "AES-256-ECB") == 0)
diff --git a/t/02-algorithms.t b/t/02-algorithms.t
index aec72ca..1b2c3b0 100644
--- a/t/02-algorithms.t
+++ b/t/02-algorithms.t
@@ -65,9 +65,9 @@ unlike ($@, qr/AES: Data size must be multiple of blocksize/, "Padding and data
 {
     eval {
         $c = Crypt::OpenSSL::AES->new(pack("H*", $key),
-            { cipher => "AES-192-ECB", iv => pack("H*", substr($iv, 0, 32)), });
+            { cipher => "AES-256-ECB", iv => pack("H*", substr($iv, 0, 32)), });
     };
-    like ($@, qr/AES-192-ECB does not use IV/, "AES-192-ECB does not use IV");
+    like ($@, qr/AES-256-ECB does not use IV/, "AES-256-ECB does not use IV");
 }
 
 {

timlegge added a commit that referenced this issue Nov 10, 2023
openssl deals with mismatches of the keysize and cipher chosen

by truncating the key or padding it with zeros

this module does not do that.  Use the correct key size for the cipher chosen

Thanks to @maaarghk for the issue report and the test to replicate it
@timlegge timlegge linked a pull request Nov 10, 2023 that will close this issue
timlegge added a commit that referenced this issue Nov 10, 2023
Fixes #9: keysize should determine the cipher used
timlegge added a commit that referenced this issue Nov 10, 2023
    [Significant updates since 0.15]
    Thanks to @maaarghk for reporting a breaking change post 0.02
    The module now ensures that the cipher chosen (if not specified)
    is the correct size according to the keysize.  It also enforces
    that the keysize and ciphers can not be mismatched if the
    cipher is specified.

    [Detailed Change Log]
    - ad7a761 Increment version etc. for release
    - 5cdb783 Strawberry Perl uses dmake
    - ae50f2b Fixed #9: keysize should determine the cipher used
    - 94731c4 v0.17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants