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

Two-way encryption: I need to store passwords that can be retrieved

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

我需要知道的是:

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

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

  • 我在哪里存储私钥?

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

  • 密码可以通过什么方式被盗和解密?我需要知道什么?


  • 就我个人而言,我会像其他人一样使用mcrypt。但还有很多值得注意的…好的。

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

    下面是一个能为你处理一切事情的优秀班级:好的。

  • 用什么算法加密密码最安全?好的。

    最安全的?他们中的任何一个。如果要加密,最安全的方法是防止信息泄漏漏洞(XSS、远程包含等)。如果它退出,攻击者最终可以破解加密(没有密钥,任何加密都是100%不可逆的-正如@nulluserexception指出的,这不是完全正确的。有些加密方案是不可能破解的,比如OneTimePad)。好的。

  • 我在哪里存储私钥?好的。

    我会用三把钥匙。一个是用户提供的,一个是特定于应用程序的,另一个是特定于用户的(如salt)。特定于应用程序的密钥可以存储在任何地方(在Web根目录之外的配置文件中,在环境变量中,等等)。特定于用户的密码将存储在数据库中加密密码旁边的列中。不会存储用户提供的。然后,你会这样做:好的。

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

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

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

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

  • 密码可以通过什么方式被盗和解密?我需要知道什么?好的。

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

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

  • 编辑:下面是一个强加密方法的PHP类实现:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    /**
     * 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验证实现定时安全比较功能:好的。

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

    用途:好的。

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

    然后,要解密:好的。

    1
    2
    $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点,您将得到如下函数:好的。

    1
    2
    3
    4
    5
    function makeKey($userKey, $serverKey, $userSuppliedKey) {
        $key = hash_hmac('sha512', $userKey, $serverKey);
        $key = hash_hmac('sha512', $key, $userSuppliedKey);
        return $key;
    }

    您可以在makeKey()函数中拉伸它,但是由于稍后会拉伸它,所以这样做并没有什么意义。好的。

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

    • 盐16字节
    • HMAC为64字节
    • 数据长度
    • 填充以使数据长度%8==0

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

    我希望这有助于…好的。

    注意:12/11/12:我刚刚用更好的加密方法更新了这个类,使用了更好的派生密钥,并修复了mac生成…好的。好啊。


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

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

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

    不是存储私钥,而是要求用户在需要解密密码时随时输入私钥,这是一个好主意吗?(可以信任此应用程序的用户)好吧,如果你的用户能记住非常长的素数,那么-是的,为什么不呢。但一般来说,您需要设计一个允许用户将其密钥存储在某个地方的系统。

    密码可以通过什么方式被盗和解密?我需要知道什么?这取决于使用的算法。但是,一定要确保不向用户发送未加密的密码。在客户端对其进行加密/解密,或者使用HTTPS(或用户其他加密手段来保护服务器和客户端之间的连接)。

    但是,如果您只需要以加密的方式存储密码,我建议您使用一个简单的XOR密码。该算法的主要问题是容易被频率分析破坏。但是,一般来说,密码不是由英语文本的长段组成的,我认为你不应该担心它。XOR密码的第二个问题是,如果您有一条加密和解密格式的消息,您可以很容易地找到用来加密它的密码。再次说明,在您的案例中,这不是一个大问题,因为它只影响已经被其他方法破坏的用户。


  • 后面的php函数是mcrypt(http://www.php.net/manual/en/intro.mcrypt.php)。
  • 手册中的示例对此示例进行了轻微的编辑):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?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) ."
    "
    ;

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

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

  • 最好的算法是比较主观的-问5个人,得到5个答案。就个人而言,如果默认值(河豚)对您来说不够好,那么您可能会遇到更大的问题!

  • 考虑到PHP需要加密-不确定是否可以将其隐藏在任何地方-欢迎对此发表评论。当然可以应用标准的PHP最佳编码实践!

  • 考虑到加密密钥仍将在代码中,如果应用程序的其余部分是安全的,则不确定将获得什么。

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

  • 我会在我的答案上加上一个附加条款——我不是PHP加密专家,但是,我认为我所回答的是标准实践——我欢迎其他人的评论。


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

    使用mcrypt成功加密后,运行base64_编码,然后将其转换为十六进制代码。一旦使用十六进制代码,就很容易以各种方式进行传输。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $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));

    另一方面:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $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);


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

    公钥

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

  • mcrypt扩展
  • AES-256可能是一个安全的赌注,但这本身就是一个问题。
  • 你没有-这是他们的申请密码
  • 两个

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

    5

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


    The passwords are for a hardware device, so checking against hashes are out of the question

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

    正如其他人所说,mcrypt扩展提供了对许多密码功能的访问—但是您邀请您的用户将所有鸡蛋放在一个篮子里—一个篮子可能是攻击者的目标—如果您甚至不知道如何开始解决问题,那么您将对用户造成损害。您无法理解如何保护数据。

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

    尽管如此,建立一个合理安全的系统是可能的。

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

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH , MCRYPT_MODE_ECB);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    $key ="evenifyouaccessmydatabaseyouwillneverfindmyemail";
    $text ="[email protected]";
    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."";

    $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.

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


    使用密码哈希和密码验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?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)."
    "
    ;
    ?>

    并解密:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?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.';
    }
    ?>