我正在创建一个存储密码的应用程序,用户可以检索和查看这些密码.密码是针对硬件设备的,因此无法判断哈希值.

我需要知道的是:

  1. 如何在PHP中加密和解密密码?

  2. 加密密码最安全的算法是什么?

  3. 我在哪里存储私钥?

  4. 与其存储私钥,还不如要求用户在需要解密密码时随时输入私钥?(可以信任此应用程序的用户)

  5. 密码可以通过哪些方式被窃取和解密?我需要注意什么?

推荐答案

就个人而言,我会像其他人一样使用mcrypt.但还有更多需要注意的...

  1. 如何在PHP中加密和解密密码?

    下面是一个能为你照顾一切的强大课程:

  2. 加密密码最安全的算法是什么?

    safest? 任何一个.如果要加密,最安全的方法是防止信息泄露漏洞(XSS、远程包含等).如果它泄露出go ,攻击者最终可以破解加密(没有密钥,任何加密都是OneTimePad%不可逆的——正如@NullUserException指出的,这并不完全正确.有些加密方案是不可能破解的,比如OneTimePad).

  3. 我将私钥存储在哪里?

    我会做的是用3个键.一种是用户提供的,一种是特定于应用程序的,另一种是特定于用户的(如盐).应用程序特定的密钥可以存储在任何地方(在web-root之外的配置文件中、在环境变量中等).用户特定的密码将被存储在数据库中紧挨着加密密码的列中.用户提供的密码将不会被存储.然后,您可以这样做:

    $key = $userKey . $serverKey . $userSuppliedKey;
    

    这样做的好处是,任何两个密钥都可以在不泄露数据的情况下泄露.如果发生SQL注入攻击,他们可以得到$userKey个,但不能得到另外2个.如果存在本地服务器漏洞,他们可以得到$userKey$serverKey,但不能得到第三个$userSuppliedKey.如果他们用扳手殴打用户,他们可以得到$userSuppliedKey,但不能得到另外两个(但是,如果用户被扳手殴打,你无论如何都太迟了).

  4. 与其存储私钥,还不如要求用户在需要解密密码时随时输入私钥?(可以信任此应用程序的用户)

    绝对一点儿没错.事实上,这是我唯一会做的事.否则,您需要以持久存储格式(共享内存,如APC或memcached,或会话文件)存储未加密版本.这让你自己expose 在额外的妥协之下.切勿将未加密版本的密码存储在除本地变量以外的任何内容中.

  5. 密码可以通过什么方式被窃取和解密?我需要知道什么?

    对您的系统进行任何形式的危害都会让他们查看加密的数据.如果他们可以注入代码或访问您的文件系统,他们就可以查看解密的数据(因为他们可以编辑解密数据的文件).任何形式的重播或MITM攻击也会让他们完全访问所涉及的密钥.嗅探原始HTTP流量也会给他们提供密钥.

    对所有流量使用SSL.并确保服务器上没有任何类型的漏洞(CSRF、XSS、SQL注入、权限提升、远程代码执行等).

Edit:下面是一个强加密方法的PHP类实现:

/**
 * 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->get keys ($salt, $key);

        if (!hash_equals(hash_hmac('sha512', $enc, $macKey, true), $mac)) {
             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->get keys ($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 get keys ($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);
    }
}

请注意,我使用的是PHP5.6:hash_equals中添加的函数.如果你的版本低于5.6,你可以使用这个替代函数,它使用double HMAC verification实现timing-safe comparison函数:

function hash_equals($a, $b) {
    $key = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
    return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
}

用法:

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

请注意,我第二次使用$e2向您展示了不同的实例仍然可以正确地解密数据.

现在,它是如何工作的/为什么要在另一个解决方案上使用它:

  1. keys

    • 这些 keys 不是直接使用的.相反,通过标准的PBKDF2派生来拉伸密钥.

    • 用于加密的密钥对于每个加密的挡路文本是唯一的.因此,所提供的密钥成为"主密钥".因此,该类为密码和身份验证密钥提供密钥轮换.

    • IMPORTANT NOTE$rounds参数被配置为具有足够强度的真实随机密钥(至少128位加密安全随机密钥).如果要使用密码或非随机密钥(或小于128位CS random的随机密钥),请增加此参数.我建议密码至少为$rounds00(你负担得起的越多越好,但这会增加运行时间)...

  2. 数据完整性

    • 更新后的版本使用ENCRYPT-THEN-MAC,这是确保加密数据真实性的更好方法.
  3. 加密:

    • 它使用mcrypt实际执行加密.我建议在模式中使用MCRYPT_BLOWFISHMCRYPT_RIJNDAEL_128个密码和MCRYPT_MODE_CBC个密码.它足够强大,而且仍然相当快(在我的机器上,一个加密和解密周期大约需要1/2秒).

现在,关于第一个列表中的第3点,它将给出一个如下的函数:

function makeKey($userKey, $serverKey, $userSuppliedKey) {
    $key = hash_hmac('sha512', $userKey, $serverKey);
    $key = hash_hmac('sha512', $key, $userSuppliedKey);
    return $key;
}

你可以在makeKey()函数中拉伸它,但因为它稍后会被拉伸,所以这样做并没有太大意义.

至于存储大小,它取决于纯文本.河豚鱼使用8字节的挡路大小,因此您将拥有:

  • 16个字节用于盐
  • 64字节用于HMAC
  • 数据长度
  • Padding so that 数据长度 % 8 == 0

因此,对于16个字符的数据源,将有16个字符的数据要加密.因此,这意味着由于填充,实际加密的数据大小为16字节.然后,将SALT的16字节和HMAC的64字节相加,总存储大小为96字节.所以顶多有80个字符的开销,最坏的是87个字符的开销…

我希望这能帮上忙...

Note: 12/11/12:我刚刚用一种更好的加密方法更新了这个类,使用了更好的派生密钥,并修复了MAC生成...

Php相关问答推荐

在Google云存储对象上从PHP设置缓存控制TTL会更改错误的元数据

如何更改数据表行背景 colored颜色

Laravel;Composer安装突然返回选项快捷方式不能为空.

基于不用于变体的产品属性隐藏多个WooCommerce发货方式

如何在php中生成包含第100秒的时间序列?

为什么本地主机(Xampp)上的laravel运行速度比普通html站点慢?

我必须对所有的SQL查询使用预准备语句吗?

PHP-准备用于TCP套接字的数据包

在WooCommerce中只允许高价产品的BACS付款

Laravel Http::get not working on controller

Laravel 10.x在到达控制器之前将查询参数强制转换为正确的类型

laravel Eloquent 模型的问题

如何在 Woocommerce 中自动删除旧的已完成订单

我不明白为什么我收到未定义的数组键异常错误 - Laravel 9 PHP 8

循环中未序列化数组显示的问题

如何将文件上传添加到 WooCommerce 结帐?

后退按钮不起作用 laravel ajax crud

Laravel路由中的符号在slug中会创建额外的斜杠

如何在供应商名称后将自定义徽章添加到商品详情

将样式文件或脚本引入WordPress模板