C++ OpenSSL API : How to compute CLI default IV from PBKDF2 derivation - c++

I'm trying to implement AES decryption into one of my C++ program. The idea would be to use the following openSSL command line to generate the ciphered text (but to use the C++ API to decipher) :
openssl enc -aes-256-cbc -in plaintext.txt -base64 -md sha512 -pbkdf2 -pass pass:<passwd>
As the official doc is a bit too complicated I based my implementation on this tutorial to implement the decryption : https://eclipsesource.com/blogs/2017/01/17/tutorial-aes-encryption-and-decryption-with-openssl/
It does works well, but uses a deprecated key-derivation algorithm which I wanna replace with PBKDF2.
As far as I understand I should then use PKCS5_PBKDF2_HMAC() rather than the EVP_BytesToKey() suggested in the tutorial. My problem is that EVP_BytesToKey was able to derivate both key and IV from salt and password, where PKCS5_PBKDF2_HMAC only seems to derivate one at a time.
I couldn't find any more information/tutorial on how to get both key and IV, and tried several implementations, but couldn't find how the openSSL CLI generates the IV.
I'd really like to avoid to write the IV in either the CLI or the payload, the implementation of the tutorial was really convenient for that.
Could someone help me ?
Thanks, best regards

I realize the question is about a month old by now but I came across it in my search of information on doing something similar. Given the lack of answers here I went to the source for answers.
TL;DR (direct answer)
PKCS5_PBKDF2_HMAC() generates both key and IV at the same time. Although it's concatenated to one string. It's up you to split the string into the needed parts.
const EVP_CIPHER *cipher = EVP_aes_256_cbc();
int iklen = EVP_CIPHER_key_length(cipher);
int ivlen = EVP_CIPHER_iv_length(cipher);
PKCS5_PBKDF2_HMAC(pass, -1, salt, 8, iter, EVP_sha512(), iklen + ivlen, keyivpair);
memcpy(key, keyivpair, iklen);
memcpy(iv, keyivpair + iklen, ivlen);
Detailed description
Before going into specifics I feel that I should mention that I'm using C and not C++. I do however hope that the information provided is helpful even for C++.
Before anything else the string needs to be decoded from base64 in the application. After that we can move along to the key and IV generation.
The openssl tool indicates that a salt is being used by starting the encrypted string with the string 'Salted__' followed by 8 bytes of salt (at least for aes-256-cbc). In addition to the salt we also need to know the length of both the key and the IV. Luckily there are API calls for this.
const EVP_CIPHER *cipher = EVP_aes_256_cbc();
int iklen = EVP_CIPHER_key_length(cipher);
int ivlen = EVP_CIPHER_iv_length(cipher);
We also need to know the number of iterations (the default in openssl 1.1.1 when using -pbkdf2 is 10000), as well as the message digest function which in this case will be EVP_sha512() (as specified by option -md sha512).
When we have all of the above it's time to call PKCS5_PBKDF2_HMAC().
PKCS5_PBKDF2_HMAC(pass, -1, salt, 8, iter, EVP_sha512(), iklen + ivlen, keyivpair);
Short info on the arguments
pass is of type (const char *)
password length (int), if set to -1 the length will be determined by strlen(pass)
salt is of type (const unsigned char *)
salt length (int)
iteration count (int)
message digest (const EVP_MD *), in this case returned by EVP_sha512()
total length of key + iv (int)
keyivpair (unsigned char *), this is where the key and IV is stored
Now we need to split the key and IV apart and store them i separate variables.
unsigned char key[EVP_MAX_KEY_LENGTH];
unsigned char iv[EVP_MAX_IV_LENGTH];
memcpy(key, keyivpair, iklen);
memcpy(iv, keyivpair + iklen, ivlen);
And now we have a key and IV which can be used to decrypt data encrypted by the openssl tool.
PoC
To further clarify I wrote the following proof of concept (written on and for Linux).
/*
* PoC written by zoke
* Compiled with gcc decrypt-poc.c -o decrypt-poc -lcrypto -ggdb3 -Wall -Wextra
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
void bail() {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]) {
if(argc < 3)
bail();
unsigned char key[EVP_MAX_KEY_LENGTH];
unsigned char iv[EVP_MAX_IV_LENGTH];
unsigned char salt[8]; // openssl tool uses 8 bytes for salt
unsigned char decodeddata[256];
unsigned char ciphertext[256];
unsigned char plaintext[256];
const char *pass = argv[1]; // use first argument as password (PoC only)
unsigned char *encodeddata = (unsigned char *)argv[2]; // use second argument
int decodeddata_len, ciphertext_len, plaintext_len, len;
// Decode base64 string provided as second option
EVP_ENCODE_CTX *ctx;
if(!(ctx = EVP_ENCODE_CTX_new()))
bail();
EVP_DecodeInit(ctx);
EVP_DecodeUpdate(ctx, decodeddata, &len, encodeddata, strlen((const char*)encodeddata));
decodeddata_len = len;
if(!EVP_DecodeFinal(ctx, decodeddata, &len))
bail();
EVP_ENCODE_CTX_free(ctx);
// openssl tool format seems to be 'Salted__' + salt + encrypted data
// take it apart
memcpy(salt, decodeddata + 8, 8); // 8 bytes starting at 8th byte
memcpy(ciphertext, decodeddata + 16, decodeddata_len - 16); // all but the 16 first bytes
ciphertext_len = decodeddata_len - 16;
// Get some needed information
const EVP_CIPHER *cipher = EVP_aes_256_cbc();
int iklen = EVP_CIPHER_key_length(cipher);
int ivlen = EVP_CIPHER_iv_length(cipher);
int iter = 10000; // default in openssl 1.1.1
unsigned char keyivpair[iklen + ivlen];
// Generate the actual key IV pair
if(!PKCS5_PBKDF2_HMAC(pass, -1, salt, 8, iter, EVP_sha512(), iklen + ivlen, keyivpair))
bail();
memcpy(key, keyivpair, iklen);
memcpy(iv, keyivpair + iklen, ivlen);
// Decrypt data
EVP_CIPHER_CTX *cipherctx;
if(!(cipherctx = EVP_CIPHER_CTX_new()))
bail();
if(!EVP_DecryptInit_ex(cipherctx, cipher, NULL, key, iv))
bail();
if(!EVP_DecryptUpdate(cipherctx, plaintext, &len, ciphertext, ciphertext_len))
bail();
plaintext_len = len;
if(!EVP_DecryptFinal_ex(cipherctx, plaintext + len, &len))
bail();
plaintext_len += len;
EVP_CIPHER_CTX_free(cipherctx);
plaintext[plaintext_len] = '\0'; // add null termination
printf("%s", plaintext);
exit(EXIT_SUCCESS);
}
Application tested by running
$ openssl aes-256-cbc -e -a -md sha512 -pbkdf2 -pass pass:test321 <<< "Some secret data"
U2FsdGVkX19ZNjDQXX/aACg7d4OopxqvpjclkaSuybeAxOhVRIONXoCmCQaG/Vg9
$ ./decrypt-poc test321 U2FsdGVkX19ZNjDQXX/aACg7d4OopxqvpjclkaSuybeAxOhVRIONXoCmCQaG/Vg9
Some secret data
The Key/IV generation used by the command line tool is in apps/enc.c and was very helpful when figuring this out.

Related

OpenSSL AES_CBC-256 decrypting without original text length in C++

I have this code that I found on SO and it works. My problem is that encryption and decryption are in the same file. Naturally, I want to separate them into two functions. The problem is the decoder needs the original input length. Isn't it a security vulnerability? How can I decrpyt without knowing the original length of the input?
/* computes the ciphertext from plaintext and key using AES256-CBC algorithm */
string cipher_AES(string key, string message)
{
size_t inputslength = message.length();
unsigned char aes_input[inputslength];
unsigned char aes_key[AES_KEYLENGTH];
memset(aes_input, 0, inputslength/8);
memset(aes_key, 0, AES_KEYLENGTH/8);
strcpy((char*) aes_input, message.c_str());
strcpy((char*) aes_key, key.c_str());
/* init vector */
unsigned char iv[AES_BLOCK_SIZE];
memset(iv, 0x00, AES_BLOCK_SIZE);
// buffers for encryption and decryption
const size_t encslength = ((inputslength + AES_BLOCK_SIZE) / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
unsigned char enc_out[encslength];
unsigned char dec_out[inputslength];
memset(enc_out, 0, sizeof(enc_out));
memset(dec_out, 0, sizeof(dec_out));
AES_KEY enc_key, dec_key;
AES_set_encrypt_key(aes_key, AES_KEYLENGTH, &enc_key);
AES_cbc_encrypt(aes_input, enc_out, inputslength, &enc_key, iv, AES_ENCRYPT);
AES_set_decrypt_key(aes_key, AES_KEYLENGTH, &dec_key);
AES_cbc_encrypt(enc_out, dec_out, encslength, &dec_key, iv, AES_DECRYPT);
stringstream ss;
for(int i = 0; i < encslength; i++)
{
ss << enc_out[i];
}
return ss.str(););
}
First of all, AES encryption takes place 1-to-1 in blocks of 128 bits, so you already know the message size with a 16-byte accuracy by just looking at the ciphertext.
Then, for the last block you just need to determine where the message ends. The standard solution for that is to use padding (e.g. PKCS#7). Or just store the message length at the beginning and encrypt it together with the message.
You can of course continue using OpenSSL AES API, and implement padding or some other mechanism yourself. But OpenSSL already has higher-level API (EVP), which does AES, CBC and PKCS padding automatically.
See EVP Symmetric Encryption and Decryption official OpenSSL wiki page for an example of using the EVP API.
Unrelated notes:
a fixed IV (especially zero IV) is insecure. Consider generating a random IV and storing it together with the ciphertext (e.g. using RAND_bytes).
check out also AES GCM mode for authenticated encryption (encryption + secure checksum), this way the encrypted message additionally becomes tamper-proof. See this example.

OpenSSL RSA Encryption returning gibberish. C++

I am new to socket programming, so be kind :)
I am writing a client-server application in C++ and using OpenSSL. Till now I have generated the public-private keys for the client and server and have exchanged it over the network. Now is the part where I want to encrypt my client's message using the server's public key. But my public_encrypt function returns gibberish. I know the methods which I am using are deprecated and there are better methods but the purpose is to get the hands dirty only.
Below is the function that invokes the encryption API. (Ignore the if part, it's for sending the clients public key)
#define RSA_SIZE 256
void sendMessage(int clientFD, uint16_t type, char *data, serverState *server){
uint16_t length = strlen(data);
unsigned char message[MESSAGE_SIZE];
if (server->state == 0)
{
memcpy(message, (char *)&length, sizeof(length));
memcpy(message + 2, (char *)&type, sizeof(type));
memcpy(message + 4, data, length);
send(clientFD, message, 4 + length, 0);
server->state = 1;
}
else
{
unsigned char encrypted[RSA_SIZE] = {0};
length = public_encrypt(reinterpret_cast<unsigned char *>(data), length, server->key, encrypted);
assert(length != -1);
printf("%s\n", encrypted);
memcpy(message, (char *)&length, sizeof(length));
memcpy(message + 2, (char *)&type, sizeof(type));
memcpy(message + 4, encrypted, length);
send(clientFD, message, 4 + length, 0);
}}
This is the code for the encryption
int padding = RSA_PKCS1_OAEP_PADDING;
RSA *createRSA(unsigned char *key, int pub){
RSA *rsa = NULL;
BIO *keybio;
keybio = BIO_new_mem_buf(key, -1);
if (keybio == NULL)
{
printf("Failed to create key BIO");
return 0;
}
if (pub)
{
rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa, NULL, NULL);
}
else
{
rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);
}
if (rsa == NULL)
{
printf("Failed to create RSA");
}
return rsa;}
int public_encrypt(unsigned char *data, int data_len, unsigned char *key, unsigned char *encrypted){
printf("Data:%s\n:", data);
printf("Data Length:%d\n:", data_len);
printf("Server's Key:\n%s\n:", key);
RSA *rsa = createRSA(key, 1);
int result = RSA_public_encrypt(data_len, data, encrypted, rsa, padding);
return result;}
Please check out the link https://i.stack.imgur.com/WJn7e.png to see my output.
PS: Sorry for such a long post.
The output of RSA is a random value between 0 and the modulus of the RSA private key, encoded as an unsigned big endian octet string (octet string is just another name for byte array, a char[] in C / C++). It contains bytes with any value, and it is therefore certainly not ASCII. If you want ASCII you have to base 64 encode the ciphertext.
However, quite often ciphertext is "stringified" for no good reason at all, so only do this if this is necessary within your protocol / system. Python strings are made somewhat readable for you by the Python runtime. I'm not sure if that's a good thing or not - it's certainly not a good idea to copy that string as it is only Python proprietary.
C is not as forgiving, if you treat the binary array as text you'll run into trouble, as it can contain any character, including control characters and the NUL character (00), which can play merry hell with functions such as strlen and many others that expect a textual string instead of an array of bytes (both are usually based on char in C/C++).

Decrypting file in C++, which was encrypted with openssl -aes-128-cbc

I'm trying to decrypt a file in C++. This file is encrypted with the following command:
openssl enc -nosalt -aes-128-cbc -pass pass:test -in "test.txt" -out "test_enc.txt" -p
The console shows the key=098F6BCD4621D373CADE4E832627B4F6 and iv=0A9172716AE6428409885B8B829CCB05.
In C++ I have included the #include openssl/aes.h line and try to decrypt with the following code:
const char *indata = string.toAscii().constData();
unsigned char outdata[strlen(indata)];
unsigned char ckey[] = "098F6BCD4621D373CADE4E832627B4F6";
unsigned char ivec[] = "0A9172716AE6428409885B8B829CCB05";
/* data structure that contains the key itself */
AES_KEY key;
/* set the encryption key */
AES_set_decrypt_key(ckey, 256, &key);
AES_cbc_encrypt((unsigned char*) indata, outdata, strlen(indata), &key, ivec, AES_DECRYPT);
QString result = QString((const char*) outdata);
return result;
The variable outdata contains different value than before encryption with OpenSSL.
You specify -aes-128-cbc as an option on OpenSSL so the key and initialization vector will be 128 bits long. openssl prints these out as hex strings, as they would be obfuscated on the console if printed binary.
Therefor you should initialize your ckey[] and ivec[] as the binary value of the hex strings like this:
unsigned char ckey[] = "\x09\x8F\x6B\xCD\x46\x21\xD3\x73\xCA\xDE\x4E\x83\x26\x27\xB4\xF6";
unsigned char ivec[] = "\x0A\x91\x72\x71\x6A\xE6\x42\x84\x09\x88\x5B\x8B\x82\x9C\xCB\x05";
and also, use key length 128 instead of 256 in:
AES_set_decrypt_key(ckey, 128, &key);
OpenSSL creates the key using the password you offer, and also the vector you specified is related to decryption and encryption. Make sure you have the same key and vector while decrypting the text.

How to use salsa20 (or ChaCha)?

Assume that we have a large file which can be read in chunks of 1024 bytes or so, how can I encrypt and decrypt each chunk using Salsa or Chacha 20?
Also, where would I specify the number of rounds (i.e. 8, 12, or 20)?
So far, I haven't been able to figure it out by looking at the eSTREAM test package :(
I've downloaded the following files via eSTREAM and the Salsa20 homepage:
chacha.c
ecrypt-config.h
ecrypt-machine.h
ecrypt-portable.h
ecrypt-sync.h
And I see the comments in encrypt-sync.h talk about calling functions in this order:
ECRYPT_keysetup();
ECRYPT_ivsetup();
ECRYPT_encrypt_bytes();
But I have absolutely no idea exactly what I'm supposed to be supplying as parameters to make this work...
Here's my best attempt so far, starting with one small string of plaintext (my C is rusty... it's possible I've made some basic mistake, though I can't see it):
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "ecrypt-sync.h"
#define CHUNKSIZE 1024
void getRandomBytes(u8 **str, u32 len);
void showData(u8 *data, u8 *header);
int main(int argc, char** argv)
{
char plaintext[CHUNKSIZE] = "THIS IS A TEST";
ECRYPT_ctx ctx;
u8 *key, *IV, *ciphertext, *result;
/*
Don't use random values till we get it working with zeroes at least
getRandomBytes(&key, ECRYPT_MAXKEYSIZE/8);
getRandomBytes(&IV, ECRYPT_MAXIVSIZE/8);
*/
key = (u8 *)calloc((size_t)ECRYPT_MAXKEYSIZE/8, sizeof(u8));
IV = (u8 *)calloc((size_t)ECRYPT_MAXIVSIZE/8, sizeof(u8));
printf("Encrypting [%s] using random %d bit key and %d bit IV:\n", plaintext, ECRYPT_MAXKEYSIZE, ECRYPT_MAXIVSIZE);
ECRYPT_init();
ECRYPT_keysetup(&ctx, key, ECRYPT_MAXKEYSIZE, ECRYPT_MAXIVSIZE);
ECRYPT_ivsetup(&ctx, IV);
ciphertext = (u8 *)calloc((size_t)CHUNKSIZE, sizeof(u8));
ECRYPT_encrypt_bytes(&ctx, plaintext, ciphertext, CHUNKSIZE);
//showData(ciphertext, "CIPHERTEXT");
result = (u8 *)calloc((size_t)CHUNKSIZE, sizeof(u8));
ECRYPT_decrypt_bytes(&ctx, ciphertext, result, CHUNKSIZE);
printf("And now decrypting back: [%s]\n", result);
return 0;
}
void showData(u8 *data, u8 *header) {
printf("\n-----BEGIN %s-----\n%s\n-----END %s-----\n", header, data, header);
}
void getRandomBytes(u8 **str, u32 len) {
int fd = open("/dev/random", O_RDONLY);
char *ptr = malloc((size_t) + 1);
read(fd, ptr, len);
close(fd);
ptr[len] = '\0';
*str = ptr;
}
Results are like:
Encrypting [THIS IS A TEST] using random 256 bit key and 64 bit IV:
And now decrypting back: [(bunch of random characters)]
Where it should be:
And now decrypting back: [THIS IS A TEST]
Feel free to provide your solution in either C or C++
Thank you!
If you are going to use Salsa20 in real code and you are asking questions like this, you probably want to use the NaCl library with nice friendly C++ wrappers.
See The NaCl website.
To answer your actual question: you need to set the IV up again for the decryption operation. The IV consists of your nonce and a block offset. The encrypt/decrypt functions increment the offset, giving your code a different IV for the encryption and decryption functions.

My AES encryption/decryption functions don't work with random ivecs

I was bored and wrote a wrapper around openSSL to do AES encryption with less work. If I do it like this:
http://pastebin.com/V1eqz4jp (ivec = 0)
Everything works fine, but the default ivec is all 0's, which has some security problems. Since I'm passing the data back as a string anyway, I figured, why not generate a random ivec and stick it to the front, the take it back off when I decrypt the string? For some reason it doesn't work though.
Well actually, it almost works. It seems to decrypt the middle of the string, but not the beginning or end:
String is: 0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
Encrypting..
���l%%1u���B!
�����`pN)�ɶ���[l�ӏ��{�Q�?�2�/�HԵ�y"�=Z�Cu����l%%1u���B!
Decrypting..
String is: �%���G*�5J�0��0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
I honestly have no idea what's going wrong. Maybe some stupid mistake, or maybe I'm missing something about AES?
Here's the code: (Edited to incorporate Steve Jessop's solution to my first problem)
/*!
* Simple AES
* Brendan Long
* March 29, 2010
*
* Simplified encryption and decryption using OpenSSL's AES library.
* Remember to compile with -lcrypto and link against the library
* g++ (your stuff) -lcrypto simpleAes.cpp (or simpleAes.o)
*
* Implementation note: Using the default ivec (0) is not secure. For
* the full security that AES offers, use a different
* ivec each time (it does not need to be secret,
* just different.
*
* This code is released into the public domain. Yada yada..
* Read this for details: http://creativecommons.org/licenses/publicdomain/
*
* If for some reason public domain isn't good enough, you may use, alter,
* distribute or do anything else you want with this code with no restrictions.
*/
#include <openssl/aes.h>
#include <iostream>
#include <stdlib.h>
#include <time.h>
bool seed = true;
/*!
* Encrypts a string using AES with a 256 bit key
* Note: If the key is less than 32 bytes, it will be null padded.
* If the key is greater than 32 bytes, it will be truncated
* \param in The string to encrypt
* \param key The key to encrypt with
* \return The encrypted data
*/
std::string aes_encrypt(std::string in, std::string key){
// Seed the random number generator once
if(seed){
srand( (unsigned int) time(NULL));
seed = false;
}
// Generate a random ivec
unsigned char ivec[16];
for(int i=0; i<16; i++){
ivec[i] = (unsigned char) rand();
}
// Round up to AES_BLOCK_SIZE
size_t textLength = ((in.length() / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE;
// Always pad the key to 32 bits.. because we can
if(key.length() < 32){
key.append(32 - key.length(), '\0');
}
// Get some space ready for the output
unsigned char *output = new unsigned char[textLength];
// Generate a key
AES_KEY *aesKey = new AES_KEY;
AES_set_encrypt_key((unsigned char*)key.c_str(), 256, aesKey);
// Encrypt the data
AES_cbc_encrypt((unsigned char*)in.c_str(), output, in.length() + 1, aesKey, ivec, AES_ENCRYPT);
// Make the data into a string
std::string ret((char*) output, textLength);
// Add the ivec to the front
ret = std::string((char*)ivec, 16) + ret;
// Clean up
delete output;
delete aesKey;
return ret;
}
/*!
* Decrypts a string using AES with a 256 bit key
* Note: If the key is less than 32 bytes, it will be null padded.
* If the key is greater than 32 bytes, it will be truncated
* \param in The string to decrypt
* \param key The key to decrypt with
* \return The decrypted data
*/
std::string aes_decrypt(std::string in, std::string key){
// Get the ivec from the front
unsigned char ivec[16];
for(int i=0;i<16; i++){
ivec[i] = in[i];
}
in = in.substr(16);
// Always pad the key to 32 bits.. because we can
if(key.length() < 32){
key.append(32 - key.length(), '\0');
}
// Create some space for output
unsigned char *output = new unsigned char[in.length()];
// Generate a key
AES_KEY *aesKey = new AES_KEY;
AES_set_decrypt_key((unsigned char*)key.c_str(), 256, aesKey); // key length is in bits, so 32 * 8 = 256
// Decrypt the data
AES_cbc_encrypt((unsigned char*)in.c_str(), output, in.length(), aesKey, ivec, AES_DECRYPT);
// Make the output into a string
std::string ret((char*) output);
// Clean up
delete output;
delete aesKey;
return ret;
}
You should save the ivec[16] into 'output' BEFORE encrypting.
That's it...
I'd also like to add that it'll be much simpler to work with char* instead of string.
This line is wrong:
std::string ret((char*) output);
The decrypted data doesn't have a nul terminator, since you encrypted in.length() bytes. That accounts for the garbage at the end, but not the garbage at the beginning. There may be other problems as well.
A friend of mine figured out the problem. I'm doing this:
Generate random number and store it in ivec
Encrypt data with ivec
Append ivec to beginning of output data
The problem is that step 2 changes the contents of ivec. I was basically storing random numbers at the beginning of my string. The solution was to add this:
unsigned char ivec[16];
// set ivec to random numbers
std::string ivecString((char*) ivec, 16);
// encrypt data
return ivecString + encryptedData;
In general, you cannot treat the output of the encryption stage as a string, unless you perform an additional step, such as Base 64 encoding the output. Any output byte could be a nul.