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

Hyd*_*erA 172 php security encryption passwords

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

我需要知道的是:

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

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

  3. 我在哪里存储私钥?

  4. 除了存储私钥,最好是要求用户在需要密码解密时输入私钥吗?(可以信任此应用程序的用户)

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

irc*_*ell 210

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

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

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

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

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

  3. 我在哪里存储私钥?

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

    $key = $userKey . $serverKey . $userSuppliedKey;
    
    Run Code Online (Sandbox Code Playgroud)

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

  4. 除了存储私钥,最好是要求用户在需要密码解密时输入私钥吗?(可以信任此应用程序的用户)

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

  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);
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我正在使用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);
}
Run Code Online (Sandbox Code Playgroud)

用法:

$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);
Run Code Online (Sandbox Code Playgroud)

然后,要解密:

$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);
Run Code Online (Sandbox Code Playgroud)

请注意,我$e2第二次使用它来向您显示不同的实例仍将正确解密数据.

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

  1. 按键

    • 密钥不直接使用.相反,密钥由标准的PBKDF2推导拉伸.

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

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

  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;
}
Run Code Online (Sandbox Code Playgroud)

你可以在makeKey()函数中对它进行拉伸,但由于它将在以后进行拉伸,因此实际上并没有太大的意义.

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

  • 盐的16个字节
  • hmac为64字节
  • 数据长度
  • 填充使数据长度%8 == 0

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

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

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

  • @ircmaxell自上次修改代码以来已经有一段时间了,所以我想知道它是否是最新版本.我需要为财务应用程序使用类似的东西,如果你对这个类给了一个好的话会很好:-) (4认同)
  • 有人不明白这意味着什么"打破".@ IRC在课堂上做得很好,这是非常棒的代码. (3认同)
  • 哦,再一次在解密函数中将两个`-64`更改为`-128`很有帮助(因此,您会得到$ enc = substr($ data,128,-128)和$ mac = substr($ data, -128);` (2认同)
  • 警告!mcrypt扩展已经废弃了将近十年,并且使用起来也相当复杂。因此,不建议使用OpenSSL,而建议将OpenSSL从内核中删除并放入PHP 7.2中的PECL中。http://th1.php.net/manual/zh/migration71.deprecated.php (2认同)

Iva*_*van 14

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

使用加密密码最安全的算法是什么? 有许多不同的算法,其中没有一个是100%安全的.但其中许多都足够安全,可用于商业甚至军事目的

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

除了存储私钥,最好是要求用户在需要密码解密时输入私钥吗?(此应用程序的用户可以信任) 如果您的用户可以记住可笑的长素数然后 - 是的,为什么不.但通常你需要提出一个允许用户在某处存储密钥的系统.

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

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

  • 我当然不会建议您自己实施加密算法,存在太多潜在的陷阱,现有的库已经过许多人的测试和分析. (4认同)

Jon*_*des 12

  1. 您所使用的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";
?>
Run Code Online (Sandbox Code Playgroud)

您可以使用mcrypt_decrypt来解密您的密码.

  1. 最好的算法是相当主观的 - 问5个人,得到5个答案.就个人而言,如果默认(Blowfish)对你不够好,你可能会有更大的问题!

  2. 鉴于PHP需要加密 - 不确定您是否可以将其隐藏在任何地方 - 欢迎对此发表评论.标准PHP最佳编码实践当然适用!

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

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

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

  • 有两点需要注意.首先,`MCRYPT_MODE_ECB`不使用IV.其次,如果确实如此,你需要存储IV,因为如果没有它就无法解密数据...... (3认同)

Bra*_*ley 6

许多用户建议使用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));
Run Code Online (Sandbox Code Playgroud)

另一方面:

$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);
Run Code Online (Sandbox Code Playgroud)

  • 嗯 - 这是在2011年:P (2认同)

Lon*_*ars 5

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

公钥

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

对称

  1. 这个Mcrypt扩展
  2. AES-256可能是一个安全的赌注,但这本身就是一个问题
  3. 你没有 - 这将是他们的申请密码

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

5.

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