2

I'm looking for a way to decrypt a file since a few weeks. But it's impossible to recover the file intact in PHP, only with node. But I would like to do it without node. If someone can tell me where I could be wrong... ?

I tried with openssl_encrypt/decrypt then with OPENSSL_NO_PADDING & ZERO_PADDING options. Transform the result in base64 but impossible to have the good result...

I thank you in advance I do not know what to do...

Here is my NodeJs crypto code :

  decrypt(encryptedBuffer) {
        const PASSPHRASE = "";

        let decryptedBuffer = Buffer.alloc(encryptedBuffer.length);
        let chunkSize = 2048;
        let progress = 0;

        while (progress < encryptedBuffer.length) {
            if ((encryptedBuffer.length - progress) < 2048) {
                chunkSize = encryptedBuffer.length - progress;
            }

            let encryptedChunk = encryptedBuffer.slice(progress, progress + chunkSize);

            // Only decrypt every third chunk and only if not at the end
            if (progress % (chunkSize * 3) === 0 && chunkSize === 2048) {
                let cipher = crypto.createDecipheriv('bf-cbc', PASSPHRASE, Buffer.from([0, 1, 2, 3, 4, 5, 6, 7]));
                cipher.setAutoPadding(false);

                encryptedChunk = Buffer.concat([cipher.update(encryptedChunk), cipher.final()]);
            }

            decryptedBuffer.write(encryptedChunk.toString('binary'), progress, encryptedChunk.length, 'binary');

            progress += chunkSize;
        }

        return decryptedBuffer;
    }

Here in PHP

    public function decrypt($encryptedBuffer)
    {
        ini_set('memory_limit', '1G');

        $f = fopen('myfile', 'wb+');
        $chunkSize = 2048;
        $progress = 0;
        $passphrase = "h5ihb>p9`'yjmkhf";

        while ($progress > strlen($encryptedBuffer)) {
            // If the buffer is if the end calculate the valid chunksize
            if ((strlen($encryptedBuffer) - $progress) < 2048) {
                $chunkSize = strlen($encryptedBuffer) - $progress;
            }

            /** Getting the encrypted chunk part */
            $encryptedChunk = substr($encryptedBuffer, $progress, $progress + $chunkSize);

            // Only decrypt every third chunk and only if not at the end
            if ($progress % ($chunkSize * 3) === 0 && $chunkSize === 2048) {
                $encryptedChunk = openssl_decrypt($encryptedChunk, 'bf-cbc', $passphrase, OPENSSL_ZERO_PADDING, '01234567');
            }

            fwrite($f, $encryptedChunk);

            $progress += $chunkSize;
        }

    }
Topaco
  • 40,594
  • 4
  • 35
  • 62
  • Your NodeJS reference code does not work: When executing the NodeJS code, a _TypeError_ is raised (_Cannot read property 'length' of undefined_) because `chunkString` is not defined. Also, your test key is invalid. And to denote the key as IV only makes it more confusing. Furthermore, the logic of the if-else block is unclear, but that may be related to the `chunkString` issue. – Topaco Oct 21 '21 at 06:25
  • Since the logic of the NodeJS code is not that obvious, it would be helpful if you also post the code for the encryption. By the way, for decryption `openssl_decrypt()` must be used in the PHP code. – Topaco Oct 21 '21 at 06:36
  • Yes .length generates an error, because it is actually a function parameter. This is the encrypted buffer. Sorry for the lack of precision. I rewrote the code to make it more understandable. The if allows to decrypt every 3 chunk and if the chunk is 2048 in the contrary case we do nothing – Théo Dulieu Oct 21 '21 at 08:08

1 Answers1

1

There are several flaws in the PHP code:

  • The condition in the while loop is wrong and should be:
    $progress < strlen($encryptedBuffer).
  • $encryptedChunk is determined incorrectly because substr() expects the length in the third parameter. The correct way is:
    $encryptedChunk = substr($encryptedBuffer, $progress, $chunkSize);
  • In the openssl_decrypt() call, too few flags are set in the fourth parameter:
    Apart from disabling the padding, the default Base64 decoding has to be disabled with OPENSSL_RAW_DATA.
    For key sizes smaller than 16 bytes, the padding of the key with 0x00 values to a length of 16 bytes has to be disabled with OPENSSL_DONT_ZERO_PAD_KEY. This is a PHP bug (s. here). The fix, i.e. the flag is available as of version 7.1.8.
    Overall: OPENSSL_DONT_ZERO_PAD_KEY | OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING.
  • The wrong IV is used, correct would be: hex2bin('0001020304050607').

With these changes, decryption with the PHP code works.

Regarding security: The short block size makes Blowfish vulnerable to birthday attacks, see here. Using a static IV is generally insecure. Rather, a random IV should be generated for each encryption.

Topaco
  • 40,594
  • 4
  • 35
  • 62
  • You are the boss! It works perfectly! I didn't know you could combine the options, with and without the OPENSSL_DONT_ZERO_PAD_KEY option I have the same result. Thanks for the advice, I'll have another look! – Théo Dulieu Oct 21 '21 at 19:11
  • @ThéoDulieu - `OPENSSL_DONT_ZERO_PAD_KEY` is only necessary for keys smaller than 16 bytes. For larger keys the flag has no effect. – Topaco Oct 21 '21 at 19:41