Process a single AES-128-ECB block using C/C++ and openssl

ilya picture ilya · May 15, 2013 · Viewed 10.4k times · Source

I want to en- and decode a single 16 bytes block of data using AES-128-ECB cipher. First I did it via the openssl command line utility using the ASCII 16 characters string "a_key_simple_key" as the key and the ASCII 16 characters string "1234567890uvwxyz" as the message. The command line utility printed the hexadecimal string "142f 7d9e ad8c 0682 30e0 f165 a52f f789" as ciphered message and then successfully decoded it back to original message, see below:

$ echo -n "1234567890uvwxyz" | openssl aes-128-ecb -K $(echo -n "a_key_simple_key" | xxd -ps) -nopad | xxd
0000000: 142f 7d9e ad8c 0682 30e0 f165 a52f f789  ./}.....0..e./..
$ echo "142f 7d9e ad8c 0682 30e0 f165 a52f f789" | xxd -r -ps | openssl aes-128-ecb -d -K $(echo -n "a_key_simple_key" | xxd -ps) -nopad 
1234567890uvwxyz

Now I've written a short C++ program which should do the same. It doesn't work. There are 2 issues:

  1. The output of encoding part is 32 bytes long instead of 16 bytes (the first half of these 32 bytes is exactly the ciphered text I expected to see)
  2. The decoding part is failing in the finalization step with the following openssl message:

error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt

I suspect that the both issues are somehow connected to padding, but I don't understand what is wrong exactly and how to fix it. Here is the full output of the program:

$ g++ -Wall -g ssl-aes-128-ecb.c++ -lcrypto -lssl && ./a.out 2>&1 | less
ENCODING: FAIL
ENCODING: 1234567890uvwxyz --> ^T/}<9E><AD><8C>^F<82>0<E0><F1>e<A5>/<F7><89>^X<A0>P<U+DACE>R<F8>a^R^A<8A><97>GF*
error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Aborting in u_string decode(u_string, u_string) at ssl-aes-128-ecb.c++:56

And here is the C++ program itself (with line numbers):

01 #include <string>
02 #include <iostream>
03 #include <openssl/evp.h>
04 #include <openssl/err.h>
05 #include <openssl/ssl.h>
06 #define ABORT() (fprintf(stderr, "%s\nAborting in %s at %s:%d\n", ERR_error_string(ERR_get_error(), NULL), __PRETTY_FUNCTION__, __FILE__, __LINE__), abort(), 0)
07
08 typedef std::basic_string<unsigned char> u_string;
09 static u_string encode(u_string key, u_string data);
10 static u_string decode(u_string key, u_string data);
11
12 // echo -n "1234567890uvwxyz" | openssl aes-128-ecb -K $(echo -n "a_key_simple_key" | xxd -ps) -nopad | xxd
13 // echo "142f 7d9e ad8c 0682 30e0 f165 a52f f789" | xxd -r -ps | openssl aes-128-ecb -d -K $(echo -n "a_key_simple_key" | xxd -ps) -nopad
14
15 int main()
16 {
17   SSL_load_error_strings();
18
19   u_string key = (unsigned char *) "a_key_simple_key";
20   u_string clear_text = (unsigned char *) "1234567890uvwxyz";
21   u_string secret_txt = (unsigned char *) "\x14\x2f" "\x7d\x9e" "\xad\x8c" "\x06\x82" "\x30\xe0" "\xf1\x65" "\xa5\x2f" "\xf7\x89";
22
23   std::cerr << "ENCODING: " << (encode(key, clear_text)==secret_txt ? "ok" : "FAIL") << std::endl;
24   std::cerr << "ENCODING: " << (char*)clear_text.c_str() << " --> " << (char*)encode(key, clear_text).c_str() << std::endl;
25   std::cerr << "DECODING: " << (decode(key, secret_txt)==clear_text ? "ok" : "FAIL") << std::endl;
26   std::cerr << "DECODING: " << (char*)secret_txt.c_str() << " --> " << (char*)decode(key, secret_txt).c_str() << std::endl;
27
28   return 0;
29 }
30
31 static u_string encode(u_string key, u_string data)
32 {
33   EVP_CIPHER_CTX ctx;
34   EVP_CIPHER_CTX_init(&ctx);
35   EVP_CIPHER_CTX_set_padding(&ctx, false);
36   EVP_EncryptInit_ex (&ctx, EVP_aes_128_ecb(), NULL, key.c_str(), NULL);
37   unsigned char buffer[1024], *pointer = buffer;
38   int outlen;
39   EVP_EncryptUpdate (&ctx, pointer, &outlen, data.c_str(), data.length()) or ABORT();
40   pointer += outlen;
41   EVP_EncryptFinal_ex(&ctx, pointer, &outlen) or ABORT();
42   pointer += outlen;
43   return u_string(buffer, pointer-buffer);
44 }
45
46 static u_string decode(u_string key, u_string data)
47 {
48   EVP_CIPHER_CTX ctx;
49   EVP_CIPHER_CTX_init(&ctx);
50   EVP_CIPHER_CTX_set_padding(&ctx, false);
51   EVP_DecryptInit_ex (&ctx, EVP_aes_128_ecb(), NULL, key.c_str(), NULL);
52   unsigned char buffer[1024], *pointer = buffer;
53   int outlen;
54   EVP_DecryptUpdate (&ctx, pointer, &outlen, data.c_str(), data.length()) or ABORT();
55   pointer += outlen;
56   EVP_DecryptFinal_ex(&ctx, pointer, &outlen) or ABORT();
57   pointer += outlen;
58   return u_string(buffer, pointer-buffer);
59 }

Answer

ilya picture ilya · May 15, 2013

Ok, it seems I figured out what's wrong:

EVP_CIPHER_CTX_set_padding() call must be done AFTER the EVP_DecryptInit_ex() call

So the solution is to exchange lines 50/51 and 35/36.