phpmysqlopensslaesphp-openssl

PHP openssl_decrypt error after MySQL upgrade


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.


Solution

  • 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!