I have a PHP class that handles encryption and decryption which stopped decrypting recently after a MySQL upgrade from 5.7 to 8 and migration to a new server. I need to decrypt the data in the database that was encrypted using the encrypt function and I can’t for the life of me figure out what may have happened or what I need to fix. After searching around I decided to post for feedback. Thanks!
Quick clarification: While my main issue is retrieving and accessing the encrypted information in the database, this may be a ‘red herring’ since the decrypt function in the encryption class doesn’t work at all for me. If I can solve this while preserving the encrypt function which works then it may result in a solution as to why I can’t decrypt the database data. The encrypt function will run error-free and return an encrypted string, while the decrypt function gives the error below.
These are error I have received:
Decryption error: error:0607A082:digital envelope routines:EVP_CIPHER_CTX_set_key_length:invalid key length
Decryption error: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
This is the class:
class Openssl_EncryptDecrypt
{
private $encryption_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
public function encrypt($pure_string)
{
$cipher = 'AES-256-CBC';
$options = OPENSSL_RAW_DATA;
$hash_algo = 'sha256';
$sha2len = 32;
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($pure_string, $cipher, $this->encryption_key, $options, $iv);
$hmac = hash_hmac($hash_algo, $ciphertext_raw, $this->encryption_key, true);
return $iv . $hmac . $ciphertext_raw;
// return bin2hex($iv . $hmac . $ciphertext_raw);
}
public function decrypt($encrypted_string)
{
// $encrypted_string = hex2bin($encrypted_string);
$cipher = 'AES-256-CBC';
$options = OPENSSL_RAW_DATA;
$hash_algo = 'sha256';
$sha2len = 32;
$ivlen = openssl_cipher_iv_length($cipher);
$iv = substr($encrypted_string, 0, $ivlen);
$hmac = substr($encrypted_string, $ivlen, $sha2len);
$ciphertext_raw = substr($encrypted_string, $ivlen + $sha2len);
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $this->encryption_key, $options, $iv);
$calcmac = hash_hmac($hash_algo, $ciphertext_raw, $this->encryption_key, true);
if ($original_plaintext === false) {
$errorMessage = 'Decryption error: ' . openssl_error_string() . ' for eid ' . $encrypted_string;
error_log($errorMessage);
return $errorMessage;
}
if (hash_equals($hmac, $calcmac)) {
return $original_plaintext;
}
}
}
Here is an example of retrieving data and passing it the decrypt function: (Aggregated from several files, trying to be succinct.)
// Grab employee data and decrypt the employee id:
$EmployeeData = getEmployeeDataBasedOnNetId($editNetId);
$EmployeeId = $OpensslEncryption->decrypt($EmployeeData['EmployeeId']);
// Get data from db as called above
function getEmployeeDataBasedOnNetId($NetId)
{
try {
$pdo = new pdoConnection('fasp');
$NetId = strtoupper(trim($NetId));
$sql = " SELECT NetId, EmployeeId, PrimaryRole, FirstName, MiddleName, LastName, Email, Phone, Mobile, Building, Room, MailStop, Picture, Gender, Ethnicity, CountryOfOrigin, UserName, Comment, OnWebsite
FROM Employee
WHERE NetId = :field1
";
$pdo->query($sql);
$row = $pdo->single(array(':field1' => $NetId));
return $row;
}
catch(Exception $e) {
echo $e;
}
finally {
$pdo->connectionClose();
$pdo = null;
}
}
// This is the query function from the pdo class called above:
public function query($query)
{
$this->stmt = $this->dbh->prepare($query);
}
// This is the single function from the pdo class called above:
public function single($arr = null)
{
$this->execute($arr);
return $this->stmt->fetch(PDO::FETCH_ASSOC);
}
// This is the execute function from the pdo class called above:
public function execute($arr = null)
{
if (is_array($arr) && count($arr)) {
return $this->stmt->execute($arr);
}
return $this->stmt->execute();
}
// This is the connectionClose funstion from the pdo class called above:
public function connectionClose()
{
$this->error = null;
$this->stmt = null;
$this->dbh = null;
}
Here is also the PDO settings I’m using for connecting to the DB, which may not be relevant but I’ll add it in case it prompts anything I may not be considering:
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::MYSQL_ATTR_SSL_CA => true,
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
);
$dsn = "mysql:host=" . $host . ";port=" . $port . ";dbname=" . $db . ";charset=utf8mb4";
$dbh = new PDO($dsn, $user, $pass, $options);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Note about encryption class: You’ll see a bin2hex
call in the encrypt function as well as a hex2bin
call in the decrypt function that are commented out, which was something I was testing and works! The issue is that it could be a solution moving forward, but it obviously doesn’t help decrypt the current values in the database. Which is what I’m trying to figure out.
2
Answers
Thank you for the comments. I'm not 100% sure what caused this but I believe one of the changes I made on local that enabled me to decrypt the data was specifying the character encoding in PHP. I thought I tried this initially, so it may be some combination of this as well as some PHP configuration I did on my local vm. After I decrypted the data, I wrote a few scripts to update the db records (and encryption functions) to be more friendly with retrieval and updating. Cheers!
The error message states that the key length is wrong,
EVP_CIPHER_CTX_set_key_length:invalid key length
.This should then refer to
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $this->encryption_key, $options, $iv);
. Where the key appears to beprivate $encryption_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
.As strings are byte arrays in PHP, i.e. the same thing internally, I would expect you to have the encryption_key set to a hexadecimal string value, and then converted to binary with
hex2bin
before being sent intoopenssl_decrypt()
. It doesn’t make sense that it could figure it out by itself, since a binary key could happenstance just look like a hexadecimal string and providing a binary non-text-string as a string constant in source will not work well.Just reasoning from what I see, at a quick glance. I could be barking up the wrong tree, I have not actually tried any code.