-1

I know it is old topic but I played a bit my small OTP library and I would like to ask you an advice. Everything is perfect but, I am sure that no administrator want's to see something like "php_value memory_limit 500000M" :D.

I am not going to reinvent wheel but I really tried to find some library for encrypting data and I will be not satisfied with AES, mcrypt, etc. because there is no 100% safety if size of encrypted data is smaller than size of key. I will be really happy if someone will show me right direction.

My library working great but it looks that for 1 GB file I will need at least one server room ;) And because I am working on commercial solution, with "a bit" higher security level I will be not satisfied with just other library.

Many many thanks for all answers.

So here is it:

    <?php

/** OtpFile - One time pad base64 file encryption
* @author Tomas Stofik, https://www.tomasstofik.com/
* @copyright 2018 Tomas Stofik
*/

final class OtpFile
{

    private static $charSet = array(
        '+','/','0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G',
        'H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
        'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s',
        't','u','v','w','x','y','z'
    );

    public static function encryptFile(
        $originalFilePath, 
        $encryptedFilePath, 
        $keyFilePath)
    {

        if(!self::existsFile($keyFilePath) || !self::existsFile($encryptedFilePath)) {

            if($originalFileData = self::existsFile($originalFilePath)) {

                $originalFileBase64Data = base64_encode($originalFileData);

                $originalFileBase64DataLength = strlen($originalFileBase64Data) - 1;

                $originalFileBase64DataArray = str_split($originalFileBase64Data);

                $encryptedData = NULL;

                $encryptedDataKey = NULL;

                for ($i=0; $i <= $originalFileBase64DataLength; $i++) {

                    $randKey = rand(0, sizeOf(self::$charSet) - 1);

                    $arrayKey = array_search(
                        $originalFileBase64DataArray[$i], 
                        self::$charSet
                    );

                    if($randKey > $arrayKey) {
                        $str='-'.($randKey - $arrayKey);
                    } elseif($randKey < $arrayKey) {
                        $str = ($randKey + $arrayKey);
                    } else {
                        $str = $randKey;
                    }

                    $encryptedData .= self::$charSet[$randKey];

                    $encryptedDataKey .= $str.';';

                }

                $encryptedDataString = $encryptedData;

                $encryptedDataKeyString = $encryptedDataKey;

                if(!self::existsFile($keyFilePath)) {
                    file_put_contents($keyFilePath, $encryptedDataKeyString);
                }

                if(!self::existsFile($encryptedFilePath)) {
                    file_put_contents($encryptedFilePath, $encryptedDataString);
                }

                return 'OK';

            } else {

                return 'Source file not exists';

            }

        } else {

            return 'Encrypted data already exists';

        }

    }

    public static function decryptFile(
        $encryptedFilePath, 
        $keyFilePath, 
        $decryptedFilePath)
    {

        $keyFileData = self::existsFile($keyFilePath);

        $encryptedFileData = self::existsFile($encryptedFilePath);

        $encryptedFileDataLength = strlen($encryptedFileData) - 1;

        if($encryptedFileData && $keyFileData) {

            $encryptedFileDataArray = str_split($encryptedFileData);

            $keyFileDataArray = explode(';',$keyFileData);

            $decryptedData = NULL;

            for ($i=0; $i <= $encryptedFileDataLength; $i++) {

                $positionCurrent = array_search($encryptedFileDataArray[$i], self::$charSet);

                $positionEncrypted = $keyFileDataArray[$i];

                if ($positionEncrypted == $positionCurrent) {
                    $move = $positionEncrypted;
                } elseif($positionEncrypted < 0) {
                    $move=$positionEncrypted + $positionCurrent;
                } elseif($positionEncrypted > 0) {
                    $move=$positionEncrypted - $positionCurrent;
                } else {
                    $move='0';
                }

                $decryptedData .= self::$charSet[$move];

            }

            if(!self::existsFile($decryptedFilePath)) {

                file_put_contents(
                    $decryptedFilePath, 
                    base64_decode(
                        $decryptedData
                    )
                );

                return 'OK';

            } else {

                return 'Decrypted data already exists';

            }

        }

    }

    private static function existsFile($filePath)
    {

        $fileData = @file_get_contents($filePath);

        if($fileData) {

            return $fileData;

        }

        return FALSE;

    }

}

/* Using

$originalFilePath = 'original.jpg';
$keyFilePath = 'Otp_Key_' . $originalFilePath;
$encryptedFilePath = 'Otp_Data_' . $originalFilePath;
$decryptedFilePath = 'Otp_Decrypted_' . $originalFilePath;

echo OtpFile::encryptFile($originalFilePath, $encryptedFilePath, $keyFilePath);
echo OtpFile::decryptFile($encryptedFilePath, $keyFilePath, $decryptedFilePath);

*/
Steve Vinoski
  • 19,847
  • 3
  • 31
  • 46
  • `php_value memory_limit 500000M` I have `ini_set('memory_limit', '3G')` in one place. But it needs it > 120,000,000 rows of DB data will do that (and it's a cron job that runs at night :-p ).... Plus that's only `4%` of the servers RAM. – ArtisticPhoenix Mar 08 '19 at 22:33
  • Try to encrypt 14GB Matroska (*.mkv) movie and also with 10 000 users per week. for example: original image file 2.0MB encrypted file size: 2.8MB encryption key size: 8.0MB what do you say about this? ;) – tomdawayhet Mar 08 '19 at 22:35
  • 2
    "I am not going to reinvent wheel but I really tried to find some library for encrypting data and I will be not satisfied with AES, mcrypt, etc. because there is no 100% safety if size of encrypted data is smaller than size of key. I will be really happy if someone will show me right direction." I **guarantee** you that peer-reviewed encryption created by entire teams of professional cryptographers can come up with a better approach than you can. **[Do not roll your own.](https://security.stackexchange.com/questions/18197/why-shouldnt-we-roll-our-own)** – ceejayoz Mar 08 '19 at 22:36
  • 1
    For encrypting large files, You may be able to encrypt/decrypt them in chunks rather then as a whole. This depends on your use case. And I would at least use AES, something like PHPSecLib – ArtisticPhoenix Mar 08 '19 at 22:36
  • ceejayoz: of course I know that, that's why I wrote that about wheel. but I am not going to spend 1 MIO for that solution my friend ;) – tomdawayhet Mar 08 '19 at 22:38
  • ArtisticPhoenix: what I see under the code of almost every such library. "we don't recomment this library for commercial solutions" or something similar. that's why I came with this small library. it is more concept than real solution. – tomdawayhet Mar 08 '19 at 22:40
  • For example https://stackoverflow.com/questions/49567436/php-encrypt-streamed-file-from-javascript/49567765#49567765 - this answer I posted a while ago breaks large files into chunks to better manage the memory, however you have to decode it in the same way. On average this takes longer, but it's speed vs resources. – ArtisticPhoenix Mar 08 '19 at 22:41
  • @tomdawayhet Your question is based on a misconception, as far as I can tell. [AES is fine with small strings](https://crypto.stackexchange.com/questions/62268/is-aes-easier-to-crack-when-the-input-is-small). – ceejayoz Mar 08 '19 at 22:42
  • @ceejayoz for me it's not importan in which conditions is possible to crack AES. important is that there is still posibility. What I sent is just concept of this https://en.wikipedia.org/wiki/One-time_pad and till now I was not able to find similar solution. and I will not use solution where for example enc. data / key = 100%/20% if you know what I mean. – tomdawayhet Mar 08 '19 at 22:49
  • Sigh. I tried. ¯\\_(ツ)_/¯ – ceejayoz Mar 08 '19 at 22:54
  • @ceejayoz thank you :) – tomdawayhet Mar 08 '19 at 23:02

1 Answers1

2

This is a very poor implementation of a one-time pad. It is subject to a number of serious issues, both cryptographic and practical.

  1. rand() is not a source of cryptographically secure random numbers. Depending on what operating system you are running PHP on, it may have under 32 bits of state, making it quite easy to reverse-engineer its state and decrypt a file without the key.

  2. The process you've come up with, in which the encrypted data is converted to Base64 before being combined with random data, is cryptologically unnecessary and serves only to massively bloat the size of the encrypted files. (If my math is correct, the "encrypted data key" may be anywhere between 2.6x and 8x larger than the original data.)

  3. Your terminology for "encrypted data" and "encrypted data key" is actually backwards. The "encrypted data" file actually contains random data, and the "encrypted data key" contains data derived from the original file.

  4. There is a cryptographic bias in your "encryption" process. Since the original data and "random key" are both positive integers, but their difference is stored as a signed integer, it is possible to derive some information about the high bits of the original data from the output (in the "encrypted data key" file).

  5. The extreme size of the key file makes it impractical to store the key securely. This is a fundamental flaw with any one-time-pad encryption scheme, and is the primary reason why you should use a standard block cipher instead.

One-time pads are not an appropriate encryption scheme for most applications. However, if you are dead-set on implementing one, here is the basis of a more appropriate implementation:

$key_data = random_bytes(strlen($cleartext_data));
$encrypted_data = $cleartext_data ^ $key_data;
…
$cleartext_data = $encrypted_data ^ $key_data;

There is no need to convert your data to Base64 at any point in the process.