【问题标题】:One time pad encryption一次性加密
【发布时间】:2019-03-08 22:29:18
【问题描述】:

我知道这是个老话题,但我玩了一点我的小型 OTP 库,我想问你一个建议。一切都很完美,但我确信没有管理员希望看到类似“php_value memory_limit 500000M”的东西:D。

我不打算重新发明轮子,但我真的试图找到一些用于加密数据的库,我不会对 AES、mcrypt 等感到满意,因为如果加密数据的大小小于大小,则没有 100% 的安全性的关键。如果有人能告诉我正确的方向,我会非常高兴。

我的库运行良好,但看起来对于 1 GB 的文件,我至少需要一个服务器机房;)而且因为我正在研究商业解决方案,具有“一点”更高的安全级别,我不会满足于其他图书馆。

非常感谢所有答案。

就是这样:

    <?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);

*/

【问题讨论】:

  • php_value memory_limit 500000M 我在一个地方有ini_set('memory_limit', '3G')。但它需要它> 120,000,000 行数据库数据才能做到这一点(这是一个在晚上运行的 cron 作业 :-p )....另外,这只是服务器 RAM 的4%
  • 尝试加密 14GB Matroska (*.mkv) 电影以及每周 10 000 个用户。例如:原始图像文件 2.0MB 加密文件大小:2.8MB 加密密钥大小:8.0MB 您对此有何看法? ;)
  • "我不打算重新发明轮子,但我确实试图找到一些用于加密数据的库,但我不会对 AES、mcrypt 等感到满意,因为如果大小为加密的数据小于密钥的大小。如果有人能告诉我正确的方向,我会很高兴。我向您保证,由整个专业密码学家团队创建的经过同行评审的加密可以提出比您更好的方法。 Do not roll your own.
  • 为了加密大文件,您可以分块加密/解密它们,而不是整体加密。这取决于您的用例。而且我至少会使用 AES,比如 PHPSecLib
  • ceejayoz:我当然知道,这就是我写轮子的原因。但我不会为那个解决方案花费 1 MIO 我的朋友 ;)

标签: php encryption base64 one-time-password


【解决方案1】:

这是一次性便笺簿的一个非常糟糕的实现。它受到许多严重问题的影响,包括密码学和实际问题。

  1. rand() 不是加密安全随机数的来源。根据您运行 PHP 的操作系统,它的状态可能低于 32 位,因此可以很容易地对其状态进行逆向工程并在没有密钥的情况下解密文件。

  2. 您想出的过程,其中加密数据在与随机数据组合之前转换为 Base64,在密码学上是不必要的,并且只会大大增加加密文件的大小。 (如果我的数学是正确的,“加密的数据密钥”可能比原始数据大 2.6 倍到 8 倍。)

  3. 您对“加密数据”和“加密数据密钥”的术语实际上是向后。 “加密数据”文件实际上包含随机数据,“加密数据密钥”包含从原始文件派生的数据。

  4. 您的“加密”过程中存在密码偏差。由于原始数据和“随机密钥”都是正整数,但它们的差值存储为有符号整数,因此可以从输出中推导出有关原始数据高位的一些信息(在“加密数据密钥”文件中)。

  5. 密钥文件的极端大小使得安全存储密钥变得不切实际。这是任何一次性加密方案的根本缺陷,也是您应该改用标准分组密码的主要原因。

一次性密码对于大多数应用程序来说不是一个合适的加密方案。但是,如果您一心想要实施一个,这里是更合适实施的基础:

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

在此过程中的任何时候都无需将您的数据转换为 Base64。

【讨论】:

    猜你喜欢
    • 2019-03-24
    • 1970-01-01
    • 2013-10-15
    • 2018-05-20
    • 2013-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-29
    相关资源
    最近更新 更多