双向加密:我需要存储可以检索的密码

2022-08-30 06:33:04

我正在创建一个将存储密码的应用程序,用户可以检索和查看密码。密码适用于硬件设备,因此检查哈希是不可能的。

我需要知道的是:

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

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

  3. 我在哪里存储私钥?

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

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


答案 1

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

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

    请参阅下面的强大课程,为您处理所有事情:

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

    最安全?他们中的任何一个。如果要加密,最安全的方法是防止信息泄露漏洞(XSS,远程包含等)。如果它被取出,攻击者最终可以破解加密(没有密钥,加密是100%不可逆的 - 正如@NullUserException指出的那样,这并不完全正确。有一些加密方案是不可能破解的,例如一次性pad)。

  3. 我在哪里存储私钥?

    我会使用三个键。一个是用户提供的,一个是特定于应用程序的,另一个是特定于用户的(如盐)。特定于应用程序的密钥可以存储在任何位置(在 Web 根目录之外的配置文件中、环境变量中等)。用户特定的密码将存储在数据库中加密密码旁边的列中。用户提供的一个将不会被存储。然后,您将执行如下操作:

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

    这样做的好处是,任何两个密钥都可以在不损害数据的情况下受到损害。如果存在 SQL 注入攻击,他们可以获得 ,但不能获得其他两个。如果存在本地服务器漏洞,则他们可以获得 和 ,但不能获得第三个 。如果他们用扳手打败用户,他们可以得到,但不能得到其他两个(但话说回来,如果用户被扳手打败,你无论如何都太晚了)。$userKey$userKey$serverKey$userSuppliedKey$userSuppliedKey

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

    绝对。事实上,这是我唯一能做到的方法。否则,您需要以持久存储格式(共享内存,如 APCMemcached,或会话文件中)存储未加密的版本。这让自己面临额外的妥协。切勿将密码的未加密版本存储在除局部变量以外的任何内容中。

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

    任何形式的系统妥协都会让他们查看加密数据。如果他们可以注入代码或访问您的文件系统,他们可以查看解密的数据(因为他们可以编辑解密数据的文件)。任何形式的重放MITM攻击也将使他们能够完全访问所涉及的密钥。嗅探原始HTTP流量也会给他们密钥。

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

下面是一个强加密方法的 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->getKeys($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->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);
    }
}

请注意,我使用的是 PHP 5.6 中添加的函数:hash_equals。如果您使用的是低于 5.6,则可以使用以下替代函数,该函数使用双重 HMAC 验证实现时序安全比较函数:

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. 钥匙
  • 不直接使用密钥。相反,密钥由标准 PBKDF2 派生进行拉伸。

  • 用于加密的密钥对于每个加密的文本块都是唯一的。因此,提供的密钥将成为“主密钥”。因此,此类为密码和身份验证密钥提供密钥轮换。

  • 重要提示,该参数配置为具有足够强度的真正随机密钥(至少128位加密安全随机)。如果要使用密码或非随机密钥(或小于 128 位 CS random 的随机密钥),则必须增加此参数。我建议密码至少为10000(您能负担得起的越多越好,但它会增加运行时)...$rounds

  1. 数据完整性
  • 更新的版本使用ENCRYPT-THEN-MAC,这是确保加密数据真实性的更好方法。
  1. 加密:
  • 它使用mcrypt来实际执行加密。我建议使用密码或密码和模式。它足够强大,但仍然相当快(在我的机器上,加密和解密周期大约需要1/2秒)。MCRYPT_BLOWFISHMCRYPT_RIJNDAEL_128MCRYPT_MODE_CBC

现在,对于第一个列表中的第3点,这将给你一个这样的函数:

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

你可以在函数中拉伸它,但是由于它稍后会被拉伸,所以这样做并没有太大的意义。makeKey()

就存储大小而言,它取决于纯文本。Blowfish使用8字节的块大小,因此您将拥有:

  • 16 字节用于盐
  • 64 字节用于 hmac
  • 数据长度
  • 填充,使数据长度 % 8 == 0

因此,对于 16 个字符的数据源,将有 16 个字符的数据需要加密。因此,这意味着由于填充,实际的加密数据大小为16字节。然后为盐添加 16 个字节,为 hmac 添加 64 个字节,总存储大小为 96 个字节。因此,充其量是80个字符的开销,最坏的情况是87个字符的开销......


答案 2

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

通过实现许多加密算法之一(或使用许多库之一)

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

有大量不同的算法,没有一个是100%安全的。但其中许多都足够安全,可以用于商业甚至军事目的。

我在哪里存储私钥?

如果您决定实现公钥 - 加密算法(例如RSA),则不会存储私钥。用户有一个私钥。您的系统有一个公钥,可以存储在您想要的任何地方。

与其存储私钥,不如要求用户在需要解密密码时随时输入私钥是一个好主意?(此应用程序的用户可以信任)

好吧,如果你的用户能记住长得离谱的素数,那么 - 是的,为什么不呢。但一般来说,您需要提出允许用户将密钥存储在某个地方的系统。

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

这取决于所使用的算法。但是,请始终确保不要向用户发送或从用户发送未加密的密码。在客户端对其进行加密/解密,或使用HTTPS(或用户使用其他加密方法来保护服务器和客户端之间的连接)。

但是,如果您只需要以加密方式存储密码,我建议您使用简单的XOR密码。这种算法的主要问题是它很容易被频率分析打破。但是,由于密码通常不是由长段落的英语文本制成的,因此我认为您不必担心。XOR密码的第二个问题是,如果您有加密和解密形式的消息,则可以轻松找到加密的密码。同样,在您的案例中这不是一个大问题,因为它只影响已经通过其他方式受到损害的用户。


推荐