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

Posted

技术标签:

【中文标题】双向加密:我需要存储可以检索的密码【英文标题】:Two-way encryption: I need to store passwords that can be retrieved 【发布时间】:2011-07-02 16:03:17 【问题描述】:

我正在创建一个应用程序来存储用户可以检索和查看的密码。密码是针对硬件设备的,所以检查哈希是不可能的。

我需要知道的是:

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

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

    我在哪里存储私钥?

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

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

【问题讨论】:

注意:Libsodium 现在已编译到 PHP 核心中,适用于 >= 7.2。这将是现在的“首选”解决方案,因为它充满了现代方法,与 mcrypt 不同,mcrypt 被认为已弃用并已被删除。 【参考方案1】:

如何在 PHP 中加密和解密密码? 通过实现许多加密算法之一。 (或使用许多库之一)

最安全的密码加密算法是什么? 有很多不同的算法,没有一个是 100% 安全的。但它们中的许多对于商业甚至军事目的来说都足够安全

我在哪里存储私钥? 如果您决定实施公钥 - 加密算法(例如 RSA),则不存储私钥。用户有私钥。您的系统有公钥,可以存储在您希望的任何地方。

不要存储私钥,而是要求用户在需要解密密码时输入私钥是个好主意吗? (可以信任此应用程序的用户) 好吧,如果您的用户可以记住长得离谱的素数,那么-是的,为什么不呢。但通常你需要想出一个允许用户将他们的密钥存储在某个地方的系统。

密码可以通过哪些方式被窃取和解密?我需要注意什么? 这取决于所使用的算法。但是,请始终确保您不会向用户发送或从用户发送未加密的密码。要么在客户端加密/解密它,要么使用 https(或用户其他加密方式来保护服务器和客户端之间的连接)。

但是,如果您只需要以加密方式存储密码,我建议您使用简单的 XOR Cipher。这个算法的主要问题是它很容易被频率分析破解。但是,通常密码不是由长段英文文本组成的,我认为您不必担心。 XOR Cipher 的第二个问题是,如果您有加密和解密形式的消息,您可以轻松找到加密它的密码。同样,在您的情况下这不是一个大问题,因为它只会影响已经被其他方式入侵的用户。

【讨论】:

在回答 3 上,当您说用户拥有私钥时,我不明白这是什么意思。您不建议用户手动将私钥传递给应用程序,那么私钥如何传递给应用程序? 这有点问题。私钥可以存储在文本文件中,然后复制粘贴到应用程序。密钥也可以存储在服务器上,但在这种情况下,它仍应使用其他加密算法(如 XOR)进行加密。在这种情况下使用 XOR 是足够安全的,因为只有一对密码-消息,而且消息是非常随机的,所以不使用冷频率分析。 我当然不建议自己实现加密算法,有太多潜在的陷阱,而且现有的库已经过很多人的测试和分析。 XOR 的主要问题是,如果有人窃取了您的应用程序数据并且只知道用户的一个密码,他们可以解密该用户的所有其他密码。 @Ivan:是的,但这是我认为 DIY 真的真的糟糕的情况之一,除非你真的了解密码学。存在强密码,为什么不使用它们?【参考方案2】:
    您使用的 PHP 函数是 Mcrypt (http://www.php.net/manual/en/intro.mcrypt.php)。

手册中的示例对此示例进行了略微编辑):

<?php
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "This is a very secret key";
$pass = "PasswordHere";
echo strlen($pass) . "\n";

$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $pass, MCRYPT_MODE_ECB, $iv);
echo strlen($crypttext) . "\n";
?>

您将使用mcrypt_decrypt 来解密您的密码。

    最好的 algorithm 是相当主观的 - 问 5 个人,得到 5 个答案。就个人而言,如果默认(Blowfish)对您来说不够好,您可能会遇到更大的问题!

    鉴于 PHP 需要它来加密 - 不确定你是否可以将它隐藏在任何地方 - 欢迎 cmets 在此。标准的 PHP 最佳编码实践当然适用!

    鉴于加密密钥无论如何都会在您的代码中,不确定您将获得什么,前提是您的应用程序的其余部分是安全的。

    显然,如果加密密码和加密密钥被盗,那么游戏就结束了。

我会在我的答案上加上一个骑手 - 我不是 PHP 加密专家,但是,我认为我的回答是标准做法 - 我欢迎其他人可能有的 cmets。

【讨论】:

$pass = $text。我认为他改变了它以迎合这个问题,并没有注意到第二次出现。 有两点需要注意。首先,MCRYPT_MODE_ECB 不使用 IV。其次,如果是这样,您需要存储 IV,因为没有它您无法解密数据... “最好的算法是相当主观的 - 问 5 个人,得到 5 个答案。就个人而言,如果默认 (Blowfish) 对你来说不够好,你可能会遇到更大的问题!”这是完全错误的。任何加密专家都会或多或少同意gist.github.com/tqbf/be58d2d39690c3b366ad,其中明确排除了河豚【参考方案3】:

如果您希望能够在不与用户交互的情况下设置用户密码(这对于重置和共享密码很方便),我只建议使用公钥加密。

公钥

    OpenSSL 扩展,特别是 openssl_public_encryptopenssl_private_decrypt 这将是直接 RSA 假设您的密码适合密钥大小 - 填充,否则您需要一个对称层 存储每个用户的两个密钥,私钥的密码是他们的应用程序密码

对称

    Mcrypt 扩展 AES-256 可能是一个安全的选择,但这本身可能是一个 SO 问题 你不知道 - 这将是他们的应用程序密码

两者

4。是的 - 用户每次都必须输入他们的应用程序密码,但将其存储在会话中会引发其他问题

5.

如果有人窃取了应用程序数据,它与对称密码一样安全(对于公钥方案,它用于通过密码保护私钥。) 您的应用程序绝对应该只能通过 SSL 访问,最好使用客户端证书。 考虑添加第二个身份验证因素,每个会话仅使用一次,例如通过 SMS 发送的令牌。

【讨论】:

避免使用mcrypt,小心openssl_private_decrypt()【参考方案4】:

就个人而言,我会像其他人发布的那样使用mcrypt。但是还有很多需要注意的......

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

    请参阅下面的强大类,可以为您处理所有事情:

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

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

    我在哪里存储私钥?

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

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

    这样做的好处是,任何 2 个密钥都可以在不泄露数据的情况下被泄露。如果有SQL注入攻击,他们可以获得$userKey,但不能获得其他2。如果有本地服务器漏洞,他们可以获得$userKey$serverKey,但不能获得第三个$userSuppliedKey。如果他们用扳手殴打用户,他们可以获得$userSuppliedKey,但不能获得其他 2 个(但话又说回来,如果用户被扳手殴打,无论如何你都为时已晚)。

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

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

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

    对您的系统进行任何形式的入侵都会让他们查看加密数据。如果他们可以注入代码或访问您的文件系统,他们就可以查看解密的数据(因为他们可以编辑解密数据的文件)。任何形式的重放或 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,你可以使用这个替代函数,它使用 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 向您展示了不同的实例仍然可以正确解密数据。

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

    不直接使用密钥。相反,密钥由标准 PBKDF2 派生扩展。

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

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

    数据完整性

    更新版本使用 ENCRYPT-THEN-MAC,这是一种更好的方法来确保加密数据的真实性。

    加密:

    它使用 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() 函数中拉伸它,但由于它稍后会被拉伸,所以这样做并没有太大意义。

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

盐分 16 个字节 hmac 为 64 字节 数据长度 填充以便数据长度 % 8 == 0

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

希望对你有帮助……

注意: 2012 年 12 月 11 日:我刚刚用更好的加密方法更新了这个类,使用更好的派生密钥,并修复了 MAC 生成...

【讨论】:

有人不明白“中断”是什么意思。 @IRC 在课堂上做得很好,这真是该死的好代码。 以下返回false。知道为什么吗? $x = 新加密(MCRYPT_BlOWFISH,MCRYPT_MODE_CBC); $test = $x->encrypt("test", "a"); echo var_dump($x->decrypt($test, "a")); 哦,再一次在解密函数中将两个-64s 更改为-128 帮助(所以你得到$enc = substr($data, 128, -128)$mac = substr($data, -128); @ircmaxell 自从上次修改代码以来已经有一段时间了,所以我想知道它是否是最新的。我需要在财务应用程序中使用类似的东西,如果你同意这个课程会很好:-) 警告! mcrypt 扩展已被废弃近十年,而且使用起来也相当复杂。因此,它已被弃用,取而代之的是 OpenSSL,它将在 PHP 7.2 中从核心中删除并进入 PECL。 th1.php.net/manual/en/migration71.deprecated.php【参考方案5】:

很多用户建议使用 mcrypt...这是正确的,但我想更进一步,使其易于存储和传输(因为有时加密值会使它们难以使用 curl 等其他技术发送,或 json)。

使用 mcrypt 成功加密后,通过 base64_encode 运行它,然后将其转换为十六进制代码。一旦使用十六进制代码,就可以很容易地以多种方式进行传输。

$td = mcrypt_module_open('tripledes', '', 'ecb', '');
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
$key = substr("SUPERSECRETKEY",0,mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$encrypted = mcrypt_generic($td, $unencrypted);
$encrypted = $ua."||||".$iv;
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$encrypted = base64_encode($encrypted);
$encrypted = array_shift(unpack('H*', $encrypted));

另一方面:

$encrypted = pack('H*', $encrypted);
$encrypted = base64_decode($encrypted);
list($encrypted,$iv) = explode("||||",$encrypted,2);
$td = mcrypt_module_open('tripledes', '', 'ecb', '');
$key = substr("SUPERSECRETKEY",0,mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$unencrypted = mdecrypt_generic($td, $encrypted);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);

【讨论】:

Mcrypt isn't a great option 嗯 - 那是在 2011 年:P【参考方案6】:

我尝试过类似的方法,但请注意,我不是密码学家,也不是对 php 或任何编程语言有深入了解。这只是一个想法。我的想法是将key存储在某个文件中或database(或手动输入)哪个(位置)不容易预测(当然总有一天会解密任何东西,这个概念是延长解密时间)并加密敏感信息。

$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH , MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "evenifyouaccessmydatabaseyouwillneverfindmyemail";
$text = "myemail@domain.com";
echo "Key : ".$key."<br/>";
echo "Text : ".$text . "<br/>";
echo "Md5 : ".md5($text). "<br/>";
echo "Sha1 : ".sha1($text). "<br/>";



$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH , $key, $text, MCRYPT_MODE_ECB, $iv);
echo "Crypted Data : ".$crypttext."<br>";

$base64 = base64_encode($crypttext);
echo "Encoded Data : ".$base64."<br/>";
$decode =  base64_decode($base64);


$decryptdata = mcrypt_decrypt(MCRYPT_BLOWFISH , $key, $crypttext, MCRYPT_MODE_ECB, $iv);

echo "Decoded Data : ".ereg_replace("?", null ,  $decryptdata); 
//event if i add '?' to the sting to the text it works, I don't know why.

请注意,这只是一个概念。对此代码的任何改进都将是非常可观的。

【讨论】:

【参考方案7】:

密码是针对硬件设备的,所以检查哈希是不可能的

诶?我不明白。你的意思是密码必须是可恢复的吗?

正如其他人所说,mcrypt 扩展提供了对许多加密功能的访问 - 但是您邀请您的用户将他们所有的鸡蛋放在一个篮子里 - 这可能会成为攻击者的目标 - 如果您不这样做'甚至不知道如何开始解决问题,那么您就是在伤害您的用户。您不了解如何保护数据。

大多数安全漏洞的出现并不是因为底层算法存在缺陷或不安全,而是因为算法在应用程序代码中的使用方式存在问题。

话虽如此,构建一个相当安全的系统是可能

仅当您要求用户创建可供其他(特定)用户读取的安全消息时,才应考虑非对称加密。原因是它的计算成本很高。如果你只是想为用户提供一个存储库来输入和检索他们自己的数据,对称加密就足够了。

但是,如果您将用于解密消息的密钥存储在与加密消息相同的位置(或存储加密消息的位置),则系统不安全。使用与解密密钥相同的令牌来验证用户(或者在非对称加密的情况下,使用令牌作为私钥密码)。由于您需要将令牌存储在至少临时进行解密的服务器上,您可能需要考虑使用不可搜索的会话存储基板,或者将令牌直接传递给与会话相关联的守护进程,该守护进程将存储内存中的令牌并按需执行消息解密。

【讨论】:

【参考方案8】:

使用password_hash 和password_verify

<?php
/**
 * In this case, we want to increase the default cost for BCRYPT to 12.
 * Note that we also switched to BCRYPT, which will always be 60 characters.
 */
$options = [
    'cost' => 12,
];
echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $options)."\n";
?>

并解密:

<?php
// See the password_hash() example to see where this came from.
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';

if (password_verify('rasmuslerdorf', $hash)) 
    echo 'Password is valid!';
 else 
    echo 'Invalid password.';

?>

【讨论】:

以上是关于双向加密:我需要存储可以检索的密码的主要内容,如果未能解决你的问题,请参考以下文章

加密解密

存储/检索 PGP 私钥和密码的安全方法?

单向加密和双向加密

python:双向部分信用卡存储加密

.NET 中的密码加密/解密代码

将私钥存储在数据库中