Search code examples
phpencryptionopenssl

Facing Problems during decryption in my PHP code


I'm new to PHP and I'm working on a PHP and MYSQL code, which will store encrypted Patient Data in a mysql database, and then when required will retrieve it and display it to the user. Right now I have 2 files, first one is where I am encrypting the data

<?php

require_once "config.php";
require_once "session.php";

try {
    if ($_SERVER["REQUEST_METHOD"] == "POST") {
        if (isset($_SESSION["userid"])) {
            $Patient_Number = $_SESSION["userid"];
            $Date = $_POST["date"];
            
            $encryptionKey = 'random_64_character_hexadecimal_key'; 

            $Systolic_BP = bin2hex(openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc"))) . ':' . bin2hex(openssl_encrypt($_POST["sys"], "aes-256-cbc", hex2bin($encryptionKey), 0, $iv, $tag));
            $Diastolic_BP = bin2hex(openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc"))) . ':' . bin2hex(openssl_encrypt($_POST["dia"], "aes-256-cbc", hex2bin($encryptionKey), 0, $iv, $tag));
            $Respiratory_Rate = bin2hex(openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc"))) . ':' . bin2hex(openssl_encrypt($_POST["rr"], "aes-256-cbc", hex2bin($encryptionKey), 0, $iv, $tag));
            $Capillary_Refill = bin2hex(openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc"))) . ':' . bin2hex(openssl_encrypt($_POST["cr"], "aes-256-cbc", hex2bin($encryptionKey), 0, $iv, $tag));
            $Body_Temp = bin2hex(openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc"))) . ':' . bin2hex(openssl_encrypt($_POST["temp"], "aes-256-cbc", hex2bin($encryptionKey), 0, $iv, $tag));
            $Weight = bin2hex(openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc"))) . ':' . bin2hex(openssl_encrypt($_POST["weight"], "aes-256-cbc", hex2bin($encryptionKey), 0, $iv, $tag));
            $Pulse_Rate = bin2hex(openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc"))) . ':' . bin2hex(openssl_encrypt($_POST["pulse"], "aes-256-cbc", hex2bin($encryptionKey), 0, $iv, $tag));
            $Doctor_Name = bin2hex(openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc"))) . ':' . bin2hex(openssl_encrypt($_POST["dn"], "aes-256-cbc", hex2bin($encryptionKey), 0, $iv, $tag));
            $Symptoms = bin2hex(openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc"))) . ':' . bin2hex(openssl_encrypt($_POST["symptoms"], "aes-256-cbc", hex2bin($encryptionKey), 0, $iv, $tag));
            $Diagnosis = bin2hex(openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc"))) . ':' . bin2hex(openssl_encrypt($_POST["diagnosis"], "aes-256-cbc", hex2bin($encryptionKey), 0, $iv, $tag));
            $Medication = bin2hex(openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc"))) . ':' . bin2hex(openssl_encrypt($_POST["medication"], "aes-256-cbc", hex2bin($encryptionKey), 0, $iv, $tag));

            $error = "";

            $insertQuery = $db->prepare("INSERT INTO medical_records (Date, Symptoms, Systolic_BP, Diastolic_BP, Respiratory_Rate, Capillary_Refill, Body_Temp, Weight, Pulse_Rate, Diagnosis, Medication, Doctor_Name, Patient_Number) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); ");

            if ($insertQuery === false) {
                throw new Exception('Error preparing query: ' . $db->error);
            }

            $insertQuery->bind_param("ssssssssssssi", $Date, $Symptoms, $Systolic_BP, $Diastolic_BP, $Respiratory_Rate, $Capillary_Refill, $Body_Temp, $Weight, $Pulse_Rate, $Diagnosis, $Medication, $Doctor_Name, $Patient_Number);

            $result = $insertQuery->execute();

            if ($result) {
                echo "Query executed successfully.";
                $error .= '<p class="success">Your registration was successful!</p>';
                header("Location: welcome.php");
                exit;
            } else {
                $error .= '<p class="error">Something went wrong! ' . $insertQuery->error . '</p>';
            }

            $insertQuery->close();
            mysqli_close($db);
        } else {
            echo "Patient_Number is not set in the session";
        }
    }
} catch (Exception $e) {
    echo "Caught exception: " . $e->getMessage();
}
?>


The second file is where I am decrypting the data and printing it in a tabular format.

<?php
require_once "config.php";
require_once "session.php";

if (!isset($_SESSION["userid"]) || empty($_SESSION["userid"])) {
    header("location: login.php");
    exit;
}

$Patient_Number = $_SESSION["userid"];

$user = [];
if (isset($_SESSION["user"])) {
    $user = $_SESSION["user"];
}

$encryptionKey = 'same_random_64_character_hexadecimal_key_used_during_encryption';

if ($_SERVER["REQUEST_METHOD"] == "GET" && isset($_GET["Case_Number"])) {
    $caseNumber = $_GET["Case_Number"];

    $query = $db->prepare("SELECT * FROM medical_records WHERE Patient_Number = ? AND Case_Number = ?");
    $query->bind_param("ii", $Patient_Number, $caseNumber);
    $query->execute();

    $result = $query->get_result();

    if (!$result) {
        echo "Error: " . $query->error;
        exit;
    }

    if ($result->num_rows > 0) {
        // Fetch the row as an associative array
        $row = $result->fetch_array(MYSQLI_ASSOC);

        // Decrypt the encrypted values
        $decryptedRow = [];
        foreach ($row as $key => $value) {
            if (in_array($key, ['Case_Number', 'Date', 'Patient_Number'])) {
                $decryptedRow[$key] = $value;
                continue;
            }

            // Checking if the value is empty before decryption
            if (!empty($value)) {

                // Separating IV and encrypted value
                $parts = explode(':', $value, 2);
                
                // Checking if the array has at least two elements
                if (count($parts) >= 2) {

                    // Extracting IV and encrypted value
                    list($iv, $encryptedValue) = $parts;
        
                    // Checking IV length
                    $expectedIvLength = openssl_cipher_iv_length("aes-256-cbc");
        
                    if (strlen($iv) === $expectedIvLength * 2) {

                        // Converting hexadecimal IV to binary
                        $ivBinary = hex2bin($iv);
        
                        // Checking if IV conversion was successful
                        if ($ivBinary === false) {
                            echo "Invalid IV format for key: $key\n";
                        } else {

                            $decryptedValue = openssl_decrypt(hex2bin($encryptedValue), 'aes-256-cbc', hex2bin($encryptionKey), 0, $ivBinary);
                            var_dump($decryptedValue);
        
                            // Checking if decryption was successful
                            if ($decryptedValue === false) {
                                echo "Decryption failed for key: $key\n";
                                echo "Error: " . openssl_error_string() . "\n";
                            } else {
                                // Decryption successful, store the decrypted value
                                $decryptedRow[$key] = $decryptedValue;
                            }
                        }
                    } else {
                        echo "Invalid IV length for key: $key\n";
                    }
                } else {
                    // Handling the case where there are not enough parts in the array
                    echo "Invalid format for encrypted value: $value\n";
                }
            } else {
                $decryptedRow[$key] = $value;
            }
        }

        // Displaying decrypted values in a table
        echo "<table border='1'>";
        foreach ($decryptedRow as $key => $value) {
            echo "<tr>";
            echo "<td><strong>$key</strong>";
            echo "<td>$value</td>";
            echo "</tr>";
        }
        echo "</table>";
    } else {
        echo "Report not found.";
    }

    $query->close();
    mysqli_close($db);
} else {
    echo "Invalid request.";
}
?>

Now the problem I'm facing is, for some reason during decryption openssl_decrypt() function is returning false; and I am not sure what is the problem.

The error I am getting is:

Decryption failed for key: Symptoms Error: error:1C800064:Provider routines::bad decrypt bool(false) Decryption failed for key: Systolic_BP Error: error:1C800064:Provider routines::bad decrypt bool(false) Decryption failed for key: Diastolic_BP Error: error:1C800064:Provider routines::bad decrypt bool(false) Decryption failed for key: Respiratory_Rate Error: error:1C800064:Provider routines::bad decrypt bool(false) Decryption failed for key: Capillary_Refill Error: error:1C800064:Provider routines::bad decrypt bool(false) Decryption failed for key: Body_Temp Error: error:1C800064:Provider routines::bad decrypt bool(false) Decryption failed for key: Weight Error: error:1C800064:Provider routines::bad decrypt bool(false) Decryption failed for key: Pulse_Rate Error: error:1C800064:Provider routines::bad decrypt bool(false) Decryption failed for key: Diagnosis Error: error:1C800064:Provider routines::bad decrypt bool(false) Decryption failed for key: Medication Error: error:1C800064:Provider routines::bad decrypt bool(false) Decryption failed for key: Doctor_Name Error: error:1C800064:Provider routines::bad decrypt

For your reference, the encrypted data is stored in the following format

iv : encrypted_data

for Example:

86f8589c55d009da3f46e039a1ee1218:2f5066596e5648414b5150633173516c4448593136513d3d


Solution

  • You are using a lot of repeated code, which is a giant red flag that you should encapsulate that logic, usually as a function or method.

    As noted in the comment, you are generating a random IV but not using it, at least as far as I can tell.

    I've taken your provided code, mostly as-is, and converted it into encrypt and decrypt functions. I'm not saying these are perfect, but should get you going a bit. We generate an IV, use that in the encryption, and tag it onto the front of the string. I'm also not a big fan of how I did the cipher algorithm, but it shows how it can be abstracted, at least to a point. Probably better as a constant or similar.

    function encryptString(string $s, string $cipher_algo = "aes-256-cbc"): string
    {
        // Not a big fan, but I'm just trying to keep things simple
        global $encryptionKey;
    
        if (!$ivLength = openssl_cipher_iv_length($cipher_algo)) {
            throw new Exception('Could not determine IV length');
        }
    
        if (!$iv = openssl_random_pseudo_bytes($ivLength)) {
            throw new Exception('Could not generate IV');
        }
    
        if (!$encrypted = openssl_encrypt($s, $cipher_algo, hex2bin($encryptionKey), iv: $iv)) {
            throw new Exception('Could not encrypt');
        }
    
        return sprintf(
            "%s:%s",
            bin2hex($iv),
            bin2hex($encrypted),
        );
    }
    
    function decryptString(string $s, string $cipher_algo = "aes-256-cbc"): string
    {
        // Not a big fan, but I'm just trying to keep things simple
        global $encryptionKey;
    
        if (!str_contains($s, ':')) {
            throw new Exception('The provided string does not match the expected format');
        }
    
        [$iv, $encrypted] = explode(':', $s);
    
        if (!$decrypted = openssl_decrypt(hex2bin($encrypted), $cipher_algo, hex2bin($encryptionKey), iv: hex2bin($iv),)) {
            throw new Exception('Could not decrypt');
        }
    
        return $decrypted;
    }
    

    Then the following code shows using it. The key is obviously something that should be generated once and stored securely. Very specifically, storing it right in a code file is asking for potential trouble, so make sure to look into ways to store if securely.

    // This would obviously be generated once and stored somewhere safe
    $encryptionKey = bin2hex(openssl_random_pseudo_bytes(64));
    
    $encrypted = encryptString('something');
    $decrypted = decryptString($encrypted);
    
    echo $encrypted, PHP_EOL, $decrypted, PHP_EOL;
    
    OUTPUT:
    960e4b3057968c90c7eef79cdeaaa124:32457554366e325771657479572f7656374e706a58773d3d
    something