2

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.

10
  • please also paste the code where you retrieve the data from db and pass it to the decrypt function. Commented Jun 20, 2023 at 15:54
  • I edited the question to add the db retrieval and how it's passed to the decrypt function. Let me know if I missed any logic, it's taken from several files. Commented Jun 20, 2023 at 16:25
  • 1
    "a MySQL upgrade from 5.7 to 8 and migration to a new server" - if a MySQL upgrade caused a problem, that would probably be a data corruption issue, not something you can fix in code. How did you get the data from the old server to the new server? Can you inspect the data manually, maybe pull an encrypted field and compare the sha256 on both old and new? Don't both decrypting until you know the data isn't corrupt. If the data is valid, then I'm betting it is more related to a config or library issue, possibly openssl related Commented Jun 20, 2023 at 19:59
  • Do you have access to the old server still? Are you able to just decrypt, transfer, and then re-encrypt? Commented Jun 21, 2023 at 0:15
  • The server was decommissioned as soon as the update happened (not my call.) I requested a restore from a backup and we'll see how that goes. Commented Jun 21, 2023 at 14:36

2 Answers 2

0

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks Xecrets for your comment, it did help me eliminate a few things that I thought could be the issue.
0

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!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.