使用Mcrypt加密/解密文件

24

我正在尝试编写一对函数用于加密或解密文件,并尝试使用此处找到的类来完成此操作:

http://www.itnewb.com/v/PHP-Encryption-Decryption-Using-the-MCrypt-Library-libmcrypt

下面的加密函数似乎可以正常工作,因为它似乎已经加密了文件并将其放置在了预期目录中。现在我正在尝试解密文件,但是它只会输出“加密失败”的消息(它被编码在里面)。PHP错误日志中没有任何内容,所以我不确定为什么会失败,但是由于mcrypt对我来说是全新的,我更倾向于相信我在这里做错了什么…

以下是这些函数:

//ENCRYPT FILE
    function encryptFile() {
        global $cryptastic;
        $pass = PGPPASS;
        $salt = PGPSALT;
        $key = $cryptastic->pbkdf2($pass, $salt, 1000, 32) or die("Failed to generate secret key.");

        if ($handle = opendir(PATH.'/ftpd')) {
            while (false !== ($file = readdir($handle))) {
                if ($file != "." && $file != "..") {
                    $newfile = PATH.'/encrypted/'.$file.'.txt';
                    $msg = file_get_contents(PATH.'/ftpd/'.$file);
                    $encrypted = $cryptastic->encrypt($msg, $key) or die("Failed to complete encryption.");
                    $nfile = fopen($newfile, 'w');
                    fwrite($nfile, $encrypted);
                    fclose($nfile);
                    unlink(PATH.'/ftpd/'.$file);

                }
            }
            closedir($handle);
        }       


//DECRYPT FILE
    function inFTP() {
        global $cryptastic;
        $pass = PGPPASS;
        $salt = PGPSALT;
        $key = $cryptastic->pbkdf2($pass, $salt, 1000, 32) or die("Failed to generate secret key.");

        if ($handle = opendir(PATH.'/encrypted')) {
            while (false !== ($file = readdir($handle))) {
                if ($file != "." && $file != "..") {
                    $newfile = PATH.'/decrypted/'.$file;
                    $msg = PATH.'/encrypted/'.$file;
                    $decrypted = $cryptastic->decrypt($msg, $key) or die("Failed to complete decryption.");
                    $nfile = fopen($newfile, 'w');
                    fwrite($nfile, $decrypted);
                    fclose($nfile);
                    //unlink(PATH.'/encrypted/'.$file);

                }
            }
            closedir($handle);
        }       
        //$crypt->decrypt($file);
    }

加密类没有你可以调用的某种错误报告方法吗? - user254875486
4个回答

57

由于mcrypt已经不再被推荐使用,因为它已经被遗弃,这里提供了一个使用openssl的示例。

class AES256Encryption
{
    public const BLOCK_SIZE = 8;
    public const IV_LENGTH = 16;
    public const CIPHER = 'AES256';

    public static function generateIv(bool $allowLessSecure = false): string
    {
        $success = false;
        $random = openssl_random_pseudo_bytes(openssl_cipher_iv_length(static::CIPHER));
        if (!$success) {
            if (function_exists('sodium_randombytes_random16')) {
                $random = sodium_randombytes_random16();
            } else {
                try {
                    $random = random_bytes(static::IV_LENGTH);
                }
                catch (Exception $e) {
                    if ($allowLessSecure) {
                        $permitted_chars = implode(
                            '',
                            array_merge(
                                range('A', 'z'),
                                range(0, 9),
                                str_split('~!@#$%&*()-=+{};:"<>,.?/\'')
                            )
                        );
                        $random = '';
                        for ($i = 0; $i < static::IV_LENGTH; $i++) {
                            $random .= $permitted_chars[mt_rand(0, (static::IV_LENGTH) - 1)];
                        }
                    }
                    else {
                        throw new RuntimeException('Unable to generate initialization vector (IV)');
                    }
                }
            }
        }
        return $random;
    }

    protected static function getPaddedText(string $plainText): string
    {
        $stringLength = strlen($plainText);
        if ($stringLength % static::BLOCK_SIZE) {
            $plainText = str_pad($plainText, $stringLength + static::BLOCK_SIZE - $stringLength % static::BLOCK_SIZE, "\0");
        }
        return $plainText;
    }

    public static function encrypt(string $plainText, string $key, string $iv): string
    {
        $plainText = static::getPaddedText($plainText);
        return base64_encode(openssl_encrypt($plainText, static::CIPHER, $key, OPENSSL_RAW_DATA, $iv));
    }

    public static function decrypt(string $encryptedText, string $key, string $iv): string
    {
        return openssl_decrypt(base64_decode($encryptedText), static::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
    }
}

$text = '8SViI0Gz4r-p7A15YxkwjOBFuW*@NTtbm{U]D&E=~6yLM+adX'P;h3$,KJ%/eo>}<Rs:2#gZ.9fqn"Cv_^[(H\c!)?`Ql';
$key = 'secretkey';
$iv = AES256Encryption::generateIv();
$encryptedText = AES256Encryption::encrypt($text, $key, $iv);
$decryptedText = AES256Encryption::decrypt($encryptedText, $key, $iv);

printf('Original Text: %s%s', $text, PHP_EOL);
printf('Encrypted: %s%s', $encryptedText, PHP_EOL);
printf('Decrypted: %s%s', $decryptedText, PHP_EOL);

输出:

// Long string with lots of different characters
Original Text: 8SViI0Gz4r-p7A15YxkwjOBFuW*@NTtbm{U]D&E=~6yLM+adX'P;h3$,KJ%/eo>}<Rs:2#gZ.9fqn"Cv_^[(H\c!)?`Ql
Encrypted    : rsiF4PMCMyvAp+CTuJrxJYGoV4BSy8Fy+q+FL8m64+Mt5V3o0HS0elRkWXsy+//hPjzNhjmVktxVvMY55Negt4DyLcf2QpH05wUX+adJDe634J/9fWd+nlEFoDutXuhY+/Kep9zUZFDmLmszJaBHWQ==
Decrypted    : 8SViI0Gz4r-p7A15YxkwjOBFuW*@NTtbm{U]D&E=~6yLM+adX'P;h3$,KJ%/eo>}<Rs:2#gZ.9fqn"Cv_^[(H\c!)?`Ql 

旧答案

尝试使用mcrypt库中的PHP5类进行加密,本例中使用AES加密算法。您需要为每个使用它的网站更改密钥。即使您不使用它,至少它可以指导您编写自己的版本。

<?php

class Encryption
{
    const CIPHER = MCRYPT_RIJNDAEL_128; // Rijndael-128 is AES
    const MODE   = MCRYPT_MODE_CBC;

    /* Cryptographic key of length 16, 24 or 32. NOT a password! */
    private $key;
    public function __construct($key) {
        $this->key = $key;
    }

    public function encrypt($plaintext) {
        $ivSize = mcrypt_get_iv_size(self::CIPHER, self::MODE);
        $iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
        $ciphertext = mcrypt_encrypt(self::CIPHER, $this->key, $plaintext, self::MODE, $iv);
        return base64_encode($iv.$ciphertext);
    }

    public function decrypt($ciphertext) {
        $ciphertext = base64_decode($ciphertext);
        $ivSize = mcrypt_get_iv_size(self::CIPHER, self::MODE);
        if (strlen($ciphertext) < $ivSize) {
            throw new Exception('Missing initialization vector');
        }

        $iv = substr($ciphertext, 0, $ivSize);
        $ciphertext = substr($ciphertext, $ivSize);
        $plaintext = mcrypt_decrypt(self::CIPHER, $this->key, $ciphertext, self::MODE, $iv);
        return rtrim($plaintext, "\0");
    }
}

用法:

$key = /* CRYPTOGRAPHIC!!! key */;
$crypt = new Encryption($key);
$encrypted_string = $crypt->encrypt('this is a test');
$decrypted_string = $crypt->decrypt($encrypted_string); // this is a test

注意:

  • 这个类不适用于二进制数据(可能以NUL字节结尾)。
  • 这个类不提供身份验证加密。

3
在处理二进制数据时,我认为您需要先对其进行base64编码,然后再进行加密。 - DaGhostman Dimitrov
4
没错,我在文本文件上运行了这个类,效果很好。对于二进制文件,在加密之前需要对信息进行编码。如果文件大小超过100MB左右,base64_encode会导致性能问题,因此您可能希望考虑将文件分成块进行加密。从安全角度来看,这不是一个理想的解决方案,因为它提供了更多恢复部分明文的机会。但是,这样做是可行的。 - John Poulin
6
我们能否添加一个强烈的免责声明?认证加密对于防御主动攻击者是绝对必要的。除非你将威胁模型降低到普通脚本小子的水平以下,否则真的没有谈判的余地,这样做对任何人都没有帮助。 - Scott Arciszewski
4
“Authenticated encryption is not necessary.”的说法是错误的,详见链接WrongSuper wrong。现在已经不能再忽略验证加密的重要性。 - Scott Arciszewski
3
认证加密应始终是必需的。否则,你的实现将容易受到选择明文攻击的影响。 - Aaron Toponce
显示剩余13条评论

2
您不应该使用Mcrypt来加密/解密数据。如您的问题所示,以及被接受的答案中所展示的那样,数据未经身份验证,这意味着它会成为所选密文攻击的受害者。
此外,已经做了大量的工作,确保开发人员正确地组合密码基元。因此,您应该在PHP项目中使用libsodium而不是Mcrypt。libsodium是NaCl的一个分支。NaCl/libsodium编写的目的是消除开发人员遇到的许多密码陷阱,例如计时攻击和MAC标记的验证。
在PHP 7.1中,Mcrypt已被弃用,libsodim是处理PHP中密码学的首选方法。
在您的PHP项目中使用libsodium非常简单且安全。Scott Arciszewski撰写了一本关于在PHP中使用libsodium的广泛电子书,网址为:https://paragonie.com/book/pecl-libsodium。对于任何进行PHP密码学的人都值得一读。

2
虽然John的回答很好,但仅仅为了解决二进制安全问题而使用base64编码是过度的,会使您的加密文件比原始文件大33%。以下是我的PHP实现AES Crypt文件格式,可以透明地解决所有上述问题。https://github.com/philios33/PHP-AES-File-Encryption它是二进制安全的,并包括认证加密。由于它使用开源aes crypt文件格式(.aes),因此与其他.aes软件完全兼容。https://www.aescrypt.com/无论您是加密还是解密,界面都非常简单。只需提供源文件和密码即可。

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接