My C++ program which implements OpenSSL Encryption and Decryption has an inconsistency relating to the padding when I attempt to decrypt an encrypted file.
The encryption function is
<code>const int Cipher::cIterations = 1000;
const int Cipher::cPassSizeBytes = 48;
const int Cipher::cOk = 1;
int Cipher::Encrypt(const string &plainText,
const string &passPhrase,
string &cipherText)
{
const int cKeySizeBytes = 32;
const int cSaltSizeBytes = 32;
const int cIvSizeBytes = 32;
try
{
string key(cKeySizeBytes, 0);
string salt(cSaltSizeBytes, 0);
string iv(cIvSizeBytes, 0);
string password(cPassSizeBytes, 0);
string encryptedStr(cipherText.size(), 0);
int actual_size = 0;
int final_size;
createRandString(salt);
createRandString(iv);
rfc2898DeriveBytes(passPhrase, salt, cIterations, key);
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX_init(ctx);
if(cOk != EVP_EncryptInit_ex(ctx,
EVP_aes_256_cbc(),
NULL,
reinterpret_cast<unsigned char *>(&key[0]),
reinterpret_cast<unsigned char *>(&iv[0])))
{
handleErrors();
}
if(cOk != EVP_EncryptUpdate(ctx,
reinterpret_cast<unsigned char *>(&encryptedStr[0]),
&actual_size,
reinterpret_cast<const unsigned char *>(&plainText[0]),
plainText.size()))
{
handleErrors();
}
if(cOk != EVP_EncryptFinal_ex(ctx,
reinterpret_cast<unsigned char *>(&encryptedStr[actual_size]),
&final_size))
{
handleErrors();
}
actual_size += final_size;
encryptedStr.resize(actual_size);
cipherText = salt + iv + encryptedStr;
EVP_CIPHER_CTX_cleanup(ctx);
}
catch (std::runtime_error &ex)
{
throw ex;
}
catch (std::exception &ex)
{
std::cerr << ex.what() << std::endl;
}
return cipherText.length();
}
</code>
<code>const int Cipher::cIterations = 1000;
const int Cipher::cPassSizeBytes = 48;
const int Cipher::cOk = 1;
int Cipher::Encrypt(const string &plainText,
const string &passPhrase,
string &cipherText)
{
const int cKeySizeBytes = 32;
const int cSaltSizeBytes = 32;
const int cIvSizeBytes = 32;
try
{
string key(cKeySizeBytes, 0);
string salt(cSaltSizeBytes, 0);
string iv(cIvSizeBytes, 0);
string password(cPassSizeBytes, 0);
string encryptedStr(cipherText.size(), 0);
int actual_size = 0;
int final_size;
createRandString(salt);
createRandString(iv);
rfc2898DeriveBytes(passPhrase, salt, cIterations, key);
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX_init(ctx);
if(cOk != EVP_EncryptInit_ex(ctx,
EVP_aes_256_cbc(),
NULL,
reinterpret_cast<unsigned char *>(&key[0]),
reinterpret_cast<unsigned char *>(&iv[0])))
{
handleErrors();
}
if(cOk != EVP_EncryptUpdate(ctx,
reinterpret_cast<unsigned char *>(&encryptedStr[0]),
&actual_size,
reinterpret_cast<const unsigned char *>(&plainText[0]),
plainText.size()))
{
handleErrors();
}
if(cOk != EVP_EncryptFinal_ex(ctx,
reinterpret_cast<unsigned char *>(&encryptedStr[actual_size]),
&final_size))
{
handleErrors();
}
actual_size += final_size;
encryptedStr.resize(actual_size);
cipherText = salt + iv + encryptedStr;
EVP_CIPHER_CTX_cleanup(ctx);
}
catch (std::runtime_error &ex)
{
throw ex;
}
catch (std::exception &ex)
{
std::cerr << ex.what() << std::endl;
}
return cipherText.length();
}
</code>
const int Cipher::cIterations = 1000;
const int Cipher::cPassSizeBytes = 48;
const int Cipher::cOk = 1;
int Cipher::Encrypt(const string &plainText,
const string &passPhrase,
string &cipherText)
{
const int cKeySizeBytes = 32;
const int cSaltSizeBytes = 32;
const int cIvSizeBytes = 32;
try
{
string key(cKeySizeBytes, 0);
string salt(cSaltSizeBytes, 0);
string iv(cIvSizeBytes, 0);
string password(cPassSizeBytes, 0);
string encryptedStr(cipherText.size(), 0);
int actual_size = 0;
int final_size;
createRandString(salt);
createRandString(iv);
rfc2898DeriveBytes(passPhrase, salt, cIterations, key);
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX_init(ctx);
if(cOk != EVP_EncryptInit_ex(ctx,
EVP_aes_256_cbc(),
NULL,
reinterpret_cast<unsigned char *>(&key[0]),
reinterpret_cast<unsigned char *>(&iv[0])))
{
handleErrors();
}
if(cOk != EVP_EncryptUpdate(ctx,
reinterpret_cast<unsigned char *>(&encryptedStr[0]),
&actual_size,
reinterpret_cast<const unsigned char *>(&plainText[0]),
plainText.size()))
{
handleErrors();
}
if(cOk != EVP_EncryptFinal_ex(ctx,
reinterpret_cast<unsigned char *>(&encryptedStr[actual_size]),
&final_size))
{
handleErrors();
}
actual_size += final_size;
encryptedStr.resize(actual_size);
cipherText = salt + iv + encryptedStr;
EVP_CIPHER_CTX_cleanup(ctx);
}
catch (std::runtime_error &ex)
{
throw ex;
}
catch (std::exception &ex)
{
std::cerr << ex.what() << std::endl;
}
return cipherText.length();
}
And the decryption function is
<code>int Cipher::Decrypt(const string &cipherText,
const string &passPhrase,
string &plainText)
{
const int cKeySizeBytes = 32;
const int cSaltSizeBytes = 32;
const int cIvSizeBytes = 32;
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int actual_size = 0;
int final_size = 0;
string key(cKeySizeBytes, 0);
string salt = cipherText.substr(0, cKeySizeBytes);
string iv = cipherText.substr(cKeySizeBytes, cIvSizeBytes);
try
{
rfc2898DeriveBytes(passPhrase, salt, cIterations, key);
EVP_CIPHER_CTX_init(ctx);
// Enc is 1 to encrypt, 0 to decrypt, or -1 (see documentation).
if(cOk != EVP_DecryptInit_ex(ctx,
EVP_aes_256_cbc(),
NULL,
reinterpret_cast<unsigned char *>(&key[0]),
reinterpret_cast<unsigned char *>(&iv[0])))
{
handleErrors();
}
if(cOk != EVP_DecryptUpdate(ctx,
reinterpret_cast<unsigned char *>(&plainText[0]),
&actual_size,
reinterpret_cast<const unsigned char *>(&cipherText[cKeySizeBytes + cIvSizeBytes]),
cipherText.size()- (cKeySizeBytes + cIvSizeBytes)))
{
handleErrors();
}
if(cOk != EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(&plainText[actual_size]), &final_size))
{
handleErrors();
}
actual_size += final_size;
plainText.resize(actual_size);
EVP_CIPHER_CTX_cleanup(ctx);
}
catch (std::runtime_error &ex)
{
throw ex;
}
catch (std::exception &ex)
{
std::cerr << ex.what() << std::endl;
}
return plainText.length();
}
void Cipher::rfc2898DeriveBytes(const string &passphrase,
const string &salt,
unsigned int iterations,
string &data)
{
try
{
PKCS5_PBKDF2_HMAC(passphrase.c_str(),
passphrase.size(),
reinterpret_cast<const unsigned char *>(&salt[0]),
salt.size(),
iterations,
EVP_sha1(),
data.size(),
reinterpret_cast<unsigned char *>(&data[0]));
}
catch (std::runtime_error &ex)
{
throw ex;
}
catch (std::exception &ex)
{
std::cerr << ex.what() << std::endl;
}
catch (...)
{
std::cerr << "Cipher::rfc2898DeriveBytes" << std::endl;
}
}
</code>
<code>int Cipher::Decrypt(const string &cipherText,
const string &passPhrase,
string &plainText)
{
const int cKeySizeBytes = 32;
const int cSaltSizeBytes = 32;
const int cIvSizeBytes = 32;
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int actual_size = 0;
int final_size = 0;
string key(cKeySizeBytes, 0);
string salt = cipherText.substr(0, cKeySizeBytes);
string iv = cipherText.substr(cKeySizeBytes, cIvSizeBytes);
try
{
rfc2898DeriveBytes(passPhrase, salt, cIterations, key);
EVP_CIPHER_CTX_init(ctx);
// Enc is 1 to encrypt, 0 to decrypt, or -1 (see documentation).
if(cOk != EVP_DecryptInit_ex(ctx,
EVP_aes_256_cbc(),
NULL,
reinterpret_cast<unsigned char *>(&key[0]),
reinterpret_cast<unsigned char *>(&iv[0])))
{
handleErrors();
}
if(cOk != EVP_DecryptUpdate(ctx,
reinterpret_cast<unsigned char *>(&plainText[0]),
&actual_size,
reinterpret_cast<const unsigned char *>(&cipherText[cKeySizeBytes + cIvSizeBytes]),
cipherText.size()- (cKeySizeBytes + cIvSizeBytes)))
{
handleErrors();
}
if(cOk != EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(&plainText[actual_size]), &final_size))
{
handleErrors();
}
actual_size += final_size;
plainText.resize(actual_size);
EVP_CIPHER_CTX_cleanup(ctx);
}
catch (std::runtime_error &ex)
{
throw ex;
}
catch (std::exception &ex)
{
std::cerr << ex.what() << std::endl;
}
return plainText.length();
}
void Cipher::rfc2898DeriveBytes(const string &passphrase,
const string &salt,
unsigned int iterations,
string &data)
{
try
{
PKCS5_PBKDF2_HMAC(passphrase.c_str(),
passphrase.size(),
reinterpret_cast<const unsigned char *>(&salt[0]),
salt.size(),
iterations,
EVP_sha1(),
data.size(),
reinterpret_cast<unsigned char *>(&data[0]));
}
catch (std::runtime_error &ex)
{
throw ex;
}
catch (std::exception &ex)
{
std::cerr << ex.what() << std::endl;
}
catch (...)
{
std::cerr << "Cipher::rfc2898DeriveBytes" << std::endl;
}
}
</code>
int Cipher::Decrypt(const string &cipherText,
const string &passPhrase,
string &plainText)
{
const int cKeySizeBytes = 32;
const int cSaltSizeBytes = 32;
const int cIvSizeBytes = 32;
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int actual_size = 0;
int final_size = 0;
string key(cKeySizeBytes, 0);
string salt = cipherText.substr(0, cKeySizeBytes);
string iv = cipherText.substr(cKeySizeBytes, cIvSizeBytes);
try
{
rfc2898DeriveBytes(passPhrase, salt, cIterations, key);
EVP_CIPHER_CTX_init(ctx);
// Enc is 1 to encrypt, 0 to decrypt, or -1 (see documentation).
if(cOk != EVP_DecryptInit_ex(ctx,
EVP_aes_256_cbc(),
NULL,
reinterpret_cast<unsigned char *>(&key[0]),
reinterpret_cast<unsigned char *>(&iv[0])))
{
handleErrors();
}
if(cOk != EVP_DecryptUpdate(ctx,
reinterpret_cast<unsigned char *>(&plainText[0]),
&actual_size,
reinterpret_cast<const unsigned char *>(&cipherText[cKeySizeBytes + cIvSizeBytes]),
cipherText.size()- (cKeySizeBytes + cIvSizeBytes)))
{
handleErrors();
}
if(cOk != EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(&plainText[actual_size]), &final_size))
{
handleErrors();
}
actual_size += final_size;
plainText.resize(actual_size);
EVP_CIPHER_CTX_cleanup(ctx);
}
catch (std::runtime_error &ex)
{
throw ex;
}
catch (std::exception &ex)
{
std::cerr << ex.what() << std::endl;
}
return plainText.length();
}
void Cipher::rfc2898DeriveBytes(const string &passphrase,
const string &salt,
unsigned int iterations,
string &data)
{
try
{
PKCS5_PBKDF2_HMAC(passphrase.c_str(),
passphrase.size(),
reinterpret_cast<const unsigned char *>(&salt[0]),
salt.size(),
iterations,
EVP_sha1(),
data.size(),
reinterpret_cast<unsigned char *>(&data[0]));
}
catch (std::runtime_error &ex)
{
throw ex;
}
catch (std::exception &ex)
{
std::cerr << ex.what() << std::endl;
}
catch (...)
{
std::cerr << "Cipher::rfc2898DeriveBytes" << std::endl;
}
}
During encryption, when I call EVP_EncryptFinal_ex, the final_size value is 16, and when I call EVP_DecryptFinal_ex, the final_size value is 10. This doesn’t cause a problem in my C++ program but I have problems sometimes when trying to decrypt files not encrypted by my C++ program. I thought that this would be a good place to start investigating.
Can anyone help please?