【问题标题】:Converting this Encrypt - Decrypt PHP Class into a full static PHP class将此 Encrypt - Decrypt PHP 类转换为完整的静态 PHP 类
【发布时间】:2015-04-09 19:11:15
【问题描述】:

这是一个很棒的 php 类,可以在 php 中进行安全的 2 路加密。我在一个需要对其方法进行数千次调用的项目中使用它,

我想将它转换成一个包含所有方法的完整静态类,以提高性能并避免在每次新方法调用时出现新实例

/**
 * A class to handle secure encryption and decryption of arbitrary data
 *
 * Note that this is not just straight encryption.  It also has a few other
 *  features in it to make the encrypted data far more secure.  Note that any
 *  other implementations used to decrypt data will have to do the same exact
 *  operations.  
 *
 * Security Benefits:
 *
 * - Uses Key stretching
 * - Hides the Initialization Vector
 * - Does HMAC verification of source data
 *
 */
class Encryption {

    /**
     * @var string $cipher The mcrypt cipher to use for this instance
     */
    protected $cipher = '';

    /**
     * @var int $mode The mcrypt cipher mode to use
     */
    protected $mode = '';

    /**
     * @var int $rounds The number of rounds to feed into PBKDF2 for key generation
     */
    protected $rounds = 100;

    /**
     * Constructor!
     *
     * @param string $cipher The MCRYPT_* cypher to use for this instance
     * @param int    $mode   The MCRYPT_MODE_* mode to use for this instance
     * @param int    $rounds The number of PBKDF2 rounds to do on the key
     */
    public function __construct($cipher, $mode, $rounds = 100) {
        $this->cipher = $cipher;
        $this->mode = $mode;
        $this->rounds = (int) $rounds;
    }

    /**
     * Decrypt the data with the provided key
     *
     * @param string $data The encrypted datat to decrypt
     * @param string $key  The key to use for decryption
     * 
     * @returns string|false The returned string if decryption is successful
     *                           false if it is not
     */
    public function decrypt($data, $key) {
        $salt = substr($data, 0, 128);
        $enc = substr($data, 128, -64);
        $mac = substr($data, -64);

        list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);

        if ($mac !== hash_hmac('sha512', $enc, $macKey, true)) {
             return false;
        }

        $dec = mcrypt_decrypt($this->cipher, $cipherKey, $enc, $this->mode, $iv);

        $data = $this->unpad($dec);

        return $data;
    }

    /**
     * Encrypt the supplied data using the supplied key
     * 
     * @param string $data The data to encrypt
     * @param string $key  The key to encrypt with
     *
     * @returns string The encrypted data
     */
    public function encrypt($data, $key) {
        $salt = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
        list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);

        $data = $this->pad($data);

        $enc = mcrypt_encrypt($this->cipher, $cipherKey, $data, $this->mode, $iv);

        $mac = hash_hmac('sha512', $enc, $macKey, true);
        return $salt . $enc . $mac;
    }

    /**
     * Generates a set of keys given a random salt and a master key
     *
     * @param string $salt A random string to change the keys each encryption
     * @param string $key  The supplied key to encrypt with
     *
     * @returns array An array of keys (a cipher key, a mac key, and a IV)
     */
    protected function getKeys($salt, $key) {
        $ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
        $keySize = mcrypt_get_key_size($this->cipher, $this->mode);
        $length = 2 * $keySize + $ivSize;

        $key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length);

        $cipherKey = substr($key, 0, $keySize);
        $macKey = substr($key, $keySize, $keySize);
        $iv = substr($key, 2 * $keySize);
        return array($cipherKey, $macKey, $iv);
    }

    /**
     * Stretch the key using the PBKDF2 algorithm
     *
     * @see http://en.wikipedia.org/wiki/PBKDF2
     *
     * @param string $algo   The algorithm to use
     * @param string $key    The key to stretch
     * @param string $salt   A random salt
     * @param int    $rounds The number of rounds to derive
     * @param int    $length The length of the output key
     *
     * @returns string The derived key.
     */
    protected function pbkdf2($algo, $key, $salt, $rounds, $length) {
        $size   = strlen(hash($algo, '', true));
        $len    = ceil($length / $size);
        $result = '';
        for ($i = 1; $i <= $len; $i++) {
            $tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true);
            $res = $tmp;
            for ($j = 1; $j < $rounds; $j++) {
                 $tmp  = hash_hmac($algo, $tmp, $key, true);
                 $res ^= $tmp;
            }
            $result .= $res;
        }
        return substr($result, 0, $length);
    }

    protected function pad($data) {
        $length = mcrypt_get_block_size($this->cipher, $this->mode);
        $padAmount = $length - strlen($data) % $length;
        if ($padAmount == 0) {
            $padAmount = $length;
        }
        return $data . str_repeat(chr($padAmount), $padAmount);
    }

    protected function unpad($data) {
        $length = mcrypt_get_block_size($this->cipher, $this->mode);
        $last = ord($data[strlen($data) - 1]);
        if ($last > $length) return false;
        if (substr($data, -1 * $last) !== str_repeat(chr($last), $last)) {
            return false;
        }
        return substr($data, 0, -1 * $last);
    }
}

它的用法是

$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);

$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);

我想要类似的东西

Encryption::encrypt($data, $key);
Encryption::decrypt($encryptedData, $key);

谢谢。

【问题讨论】:

  • 很好。祝你好运。你有问题吗?
  • 您知道每次加密/解密都不需要新实例。还要研究依赖注入为什么它是一个坏主意
  • 为什么?你从来没有说过你为什么要这样做......
  • @ircmaxell 这是出于性能目的,因为我将对这个类进行数千次调用。因此,我不想在每个新方法调用时创建新实例。当然,静态类不需要实例化,这应该在每次调用时为我节省一些微秒。
  • @WilliemBabalola 然后静态化是错误的解决方案。更好的方法是实例化一次,然后使用 dependency injection 传递该对象。

标签: php security encryption passwords


【解决方案1】:

我将介绍一些解决方案和想法,帮助您朝着正确的方向前进。但是,我对您的代码有严重的担忧,必须首先要求您不要在实际环境中使用此代码

与许多会告诉您“不要编写加密代码”并就此罢休的密码学研究人员不同,我选择write crypto code, don't publish or use it 路径。

您向 Stack Overflow 询问如何将您的类转换为使用静态方法这一事实告诉我,您可能还没有资格在 PHP 中编写密码学。即使您更熟悉该语言,我也会强烈提醒您远离您前进的方向。

请改用this PHP library。我已经审查过了。其他人也是如此。它尚未经过正式审核,但这更多是因为作者没有数千美元可用于正式审核,而不是缺乏对安全和质量代码的奉献精神。

以下是我一目了然地发现的一些危险对于密码学库的东西。这并不意味着攻击;我只是想向你展示这个领域的一些危险。

构造函数中没有完整性检查

你怎么知道$this-&gt;cipher$this-&gt;mode 是有效的mcrypt 常量?

浪费的熵

$salt = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);

128 字节的熵是 1024 位的熵;远远超出您对 AES 的需求。

使用substr() 被认为对加密有害

PHP 有一个名为mbstring.func_overload 的功能,它完全改变了substr()strlen(), 等函数的行为,以使用多字节(Unicode)逻辑而不是二进制字符串逻辑进行操作。此行为的典型后果是它会导致生成的子字符串中的字节数不正确。哪个不好。严重程度取决于攻击者的创造力。

在启用此功能的系统中,您必须显式调用 mb_substr($string, '8bit') 以获取原始二进制字符串中的字节数。但是,此功能并非在所有系统上都可用。

使用substr() 对原始二进制字符串进行粗放是危险的。

对MAC验证的定时攻击

if ($mac !== hash_hmac('sha512', $enc, $macKey, true))

Anthony Ferrara already covered this topic in detail.

总结

真诚地,我希望这篇文章能以建设性批评的预期语气收到,并且您对密码学工程中的大量疯狂细节有所了解。

此外,我希望这不会让您放弃更多地了解该领域。 请详细了解它!这是一个令人兴奋的领域,每个人都有更多的手在甲板上会更好!您甚至可能有一天会向我介绍一种我永远无法想象的复杂攻击。

但是今天,现在,在生产系统中使用业余密码来保护敏感数据是很危险的。您有责任自己(或您的客户/雇主,如果适用)来决定更重要的事情:实验,或保护他们的信息免受损害。我不会为你做那个选择。

【讨论】:

  • 感谢您抽出宝贵的时间给出答案,请认为您建议的 library 已准备好用于生产环境,它的性能如何以最少的系统资源处理数千个调用。
  • 加密库应该不是瓶颈。此外,它使用 openssl 而不是 libmcrypt,它可以利用 AES-NI 并执行更快的 AES 加密。开销应该是最小的。
  • 如果启用了mbstring.func_overload,将substr()转换为mb_substr(),那么分割二进制字符串的好方法是什么?
  • 这不是该功能所做的。该功能改变了substr() 的行为,并强制您使用mb_substr($string, $start, $length, '8bit') 代替它。我已经更新了答案以澄清这一点。
  • @Scott 生成的密钥似乎不是字符串,我如何存储它以供以后在解密以前加密的数据时使用。
【解决方案2】:

您正在寻找静态方法,只需在方法声明中的publicprotected 之前添加static 关键字。

但是,请注意,您不能从静态方法调用非静态方法,因此您必须将所有实用程序方法也声明为静态。

更多信息请参见http://php.net/manual/en/language.oop5.static.php

【讨论】:

    猜你喜欢
    • 2018-10-15
    • 2016-06-26
    • 2014-08-15
    • 1970-01-01
    • 2022-01-25
    • 1970-01-01
    • 1970-01-01
    • 2014-09-05
    • 1970-01-01
    相关资源
    最近更新 更多