skip to Main Content

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


  1. Chosen as BEST ANSWER

    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!


  2. 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 be private $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 into openssl_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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search