首页 文章

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

提问于
浏览
166

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

我需要知道的是:

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

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

  • 我在哪里存储私钥?

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

  • 密码以什么方式被盗和解密?我需要注意什么?

8 回答

  • 12

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

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

    请参阅下面的强大课程,为您解决一切:

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

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

    • 我在哪里存储私钥?

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

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

    这样做的好处是,任何2个密钥都可以在没有数据泄露的情况下受到攻击 . 如果有SQL注入攻击,他们可以获得 $userKey ,但不能获得其他2.如果有本地服务器利用,他们可以获得 $userKey$serverKey ,但不能获得第三个 $userSuppliedKey . 如果他们用扳手击败用户,他们可以获得 $userSuppliedKey ,但不能获得另外2个(但是如果用户被扳手殴打,那么你还是太迟了) .

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

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

    • 密码以什么方式被盗和解密?我需要注意什么?

    任何形式的系统妥协都会让他们查看加密数据 . 如果他们可以注入代码或访问您的文件系统,他们可以查看解密数据(因为他们可以编辑解密数据的文件) . 任何形式的重播或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->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推导拉伸 .

    • 用于加密的密钥对于每个加密的文本块都是唯一的 . 因此,提供的密钥成为“主密钥” . 因此,该类为密码和授权密钥提供密钥轮换 .

    • IMPORTANT NOTE$rounds 参数配置为具有足够强度的真随机密钥(128位)密码学安全随机至少) . 如果您要使用密码或非随机密钥(或随机的128位随机随机密钥),则 must 会增加此参数 . 我建议密码最少10000(你能负担的越多越好,但它会增加运行时间)......

    • 数据完整性

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

    • 加密:

    • 它使用mcrypt实际执行加密 . 我建议使用 MCRYPT_BLOWFISHMCRYPT_RIJNDAEL_128 cyphers和 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个字节 . 然后为salt添加16个字节,为hmac添加64个字节,总存储大小为96个字节 . 所以最多只有80个字符的开销,最差的是87个字符的开销......

    我希望有帮助......

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

  • 2

    How do I encrypt and decrypt a password in PHP? 通过实施众多加密算法之一 . (或使用众多库中的一个)

    What is the safest algorithm to encrypt the passwords with? 有许多不同的算法,其中没有一个是100%安全的 . 但其中许多都足够安全,可用于商业甚至军事目的

    Where do I store the private key? 如果您决定实施公钥 - 加密算法(例如RSA),则不存储私钥 . 用户有私钥 . 您的系统有公钥,可以存储在您希望的任何地方 .

    Instead of storing the private key, is it a good idea to require users to enter the private key any time they need a password decrypted? (Users of this application can be trusted) 好吧,如果你的用户能够记住那些可疑的长素数 - 是的,为什么不呢 . 但通常你需要提出一个允许用户在某处存储密钥的系统 .

    In what ways can the password be stolen and decrypted? What do I need to be aware of? 这取决于使用的算法 . 但是,请始终确保不要将未加密的密码发送给用户或从用户发送密码 . 在客户端加密/解密,或使用https(或用户其他加密方法来保护服务器和客户端之间的连接) .

    但是,如果您只需要以加密方式存储密码,我建议您使用简单的XOR密码 . 该算法的主要问题是它可以通过频率分析轻松破解 . 但是,由于密码不是通过长段英文文本制作的,我认为你不应该担心它 . XOR Cipher的第二个问题是,如果您有加密和解密形式的消息,您可以轻松找到加密的密码 . 同样,在您的情况下不是一个大问题,因为它只会影响已经被其他方式泄露的用户 .

  • 206

    本例中略微编辑了手册中的示例):

    <?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需要加密 - 不确定你可以在任何地方隐藏它 - 欢迎对此发表评论 . 标准PHP最佳编码实践当然适用!

    • 鉴于加密密钥无论如何都在您的代码中,不确定您将获得什么,提供其他应用程序是安全的 .

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

    我把一个骑手放在我的答案上 - 我不是PHP加密专家,但是,我认为我所回答的是标准做法 - 我欢迎其他人的评论 .

  • 2

    许多用户建议使用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);
    
  • 14

    如果您希望能够在没有交互的情况下设置用户密码,那么我只建议使用公钥加密(这对于重置和共享密码非常方便) .

    公钥

    • OpenSSL扩展名,具体为 openssl_public_encryptopenssl_private_decrypt

    • 这将是直接RSA,假设您的密码适合密钥大小 - 填充,否则您需要一个对称层

    • 为每个用户存储两个密钥,私钥的密码是他们的应用程序密码

    对称

    • Mcrypt扩展名

    • AES-256可能是一个安全的赌注,但这可能是一个SO问题本身

    • 你没有 - 这将是他们的申请密码

    两者

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

    5 .

    • 如果有人窃取了应用程序数据,那么's as secure as the symmetric cipher (for the public key scheme, it'用于保护带密码的私钥 . )

    • 您的应用程序绝对只能通过SSL访问,最好使用客户端证书 .

    • 考虑添加第二个身份验证因素,每个会话只使用一次,例如通过SMS发送的令牌 .

  • 5

    我试过这样的事情,但请注意我不是密码学家,也不是我对 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."
    "; echo "Text : ".$text . "
    "; echo "Md5 : ".md5($text). "
    "; echo "Sha1 : ".sha1($text). "
    "; $crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH , $key, $text, MCRYPT_MODE_ECB, $iv); echo "Crypted Data : ".$crypttext."<br>"; $base64 = base64_encode($crypttext); echo "Encoded Data : ".$base64."
    "; $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.

    请注意,这只是一个概念 . 对此代码的任何改进都是非常值得注意的 .

  • 1

    密码用于硬件设备,因此检查哈希是不可能的

    嗯?我不明白 . 你只是说密码必须是可以恢复的吗?

    正如其他人所说,mcrypt扩展提供了对大量加密功能的访问 - 但是你邀请你的用户把所有的鸡蛋放在一个篮子里 - 一个可能是攻击者的目标 - 如果你甚至不知道如何开始解决问题然后你正在为你的用户造成伤害 . 您无法理解如何保护数据 .

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

    话虽如此, Build 一个相当安全的系统是正确的 .

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

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

  • 6

    使用password_hashpassword_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.';
    }
    ?>
    

相关问题