Search code examples
phpopensslaeslarge-filesphp-openssl

create php code for large files openssl encrypt using AES-256-CTR compatible with openssl command line


I am trying to create a PHP method to replicate the OpenSSL command line function so I can encrypt using PHP and then decrypt it using command line .

i created PHP method for encrypting files taking into consideration memory performance and limit issue by encrypt the file on parts:

$salt= random_bytes(8);
$saltPrefix = "Salted__" . $salt;
$keyIV= EVP_BytesToKey($salt, file_get_contents('my.key'));
$key = substr($keyIV, 0, 32);
$iv= substr($keyIV, 32, openssl_cipher_iv_length('AES-256-CTR'));
$original = file_get_contents('confidential.txt');
$FILE_ENCRYPTION_BLOCKS = 10000;
if ($fpOut = fopen('php-encrypted.bin', 'w')) {
    fwrite($fpOut, $saltPrefix);
    if ($fpIn = fopen('confidential.txt', 'rb')) {
        while (!feof($fpIn)) {
            $plaintext = fread($fpIn, 16 * $FILE_ENCRYPTION_BLOCKS);
            $encrypted = openssl_encrypt($plaintext, 'AES-256-CTR', $key, (feof($fpIn) ? OPENSSL_RAW_DATA:OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING), $iv);
            fwrite($fpOut, $encrypted);
        }
        fclose($fpIn);
    }
    fclose($fpOut);
}

then after running this method, I can decrypt it using this command line:

openssl  enc -p -d -aes-256-ctr -in php-encrypted.bin -out decrypted.txt -pass file:my.key

but when i try to decrypt the generated file using the command line above it is not working. i need to update the above method to simulate command line encryption using CTR mode. i read that the $iv must contains nonce and counter but how this must be done and which function to generate the counter, or it is secure just to put $iv = $randomBytes . $counter; where $randomBytes is fixed and in the second call in the loop change $counter from 1 to 2 or how to be done in the above php code?


Solution

  • In CTR mode, the initial IV is incremented with each subsequent block, see the CTR flow diagram. This so-called counter is encrypted. As a result, a key stream is generated which is XORed with the plaintext.

    If encryption is performed in chunks consisting of $FILE_ENCRYPTION_BLOCKS blocks (16 bytes each), the counter for the subsequent chunk must be incremented accordingly by $FILE_ENCRYPTION_BLOCKS.

    $iv is a binary string that can be converted into a number using gmp_import() (and the default values for $word_size and $flags), incremented and converted back into a binary string using gmp_export(). One possible implementation is:

    $FILE_ENCRYPTION_BLOCKS = 10000;
    $salt= random_bytes(8);
    $saltPrefix = 'Salted__' . $salt;
    $keyIV = EVP_BytesToKey($salt, file_get_contents('my.key'));
    $key = substr($keyIv, 0, 32);
    $iv = substr($keyIv, 32, 16);
    if ($fpOut = fopen('php-encrypted.bin', 'wb')) {
        fwrite($fpOut, $saltPrefix);
        if ($fpIn = fopen('confidential.txt', 'rb')) {
            while (!feof($fpIn)) {
                $plaintext = fread($fpIn, 16 * $FILE_ENCRYPTION_BLOCKS);
                $encrypted = openssl_encrypt($plaintext, 'AES-256-CTR', $key, OPENSSL_RAW_DATA, $iv);   // padding implicitly disabled for CTR
                $iv = gmp_export(gmp_import($iv) + $FILE_ENCRYPTION_BLOCKS);                            // increment counter
                fwrite($fpOut, $encrypted);
            }
            fclose($fpIn);
        }
        fclose($fpOut);
    } 
    

    The ciphertext generated in this way can be decrypted with:

    openssl  enc -p -d -aes-256-ctr -in ciphertext.bin -out decrypted.txt -pass file:my.key
    

    For completeness: stream cipher modes such as CTR do not require padding. OpenSSL (and also the PHP wrapper) disable this automatically.