双向加密:我需要存储可以找回的密码 [英] Two-way encryption: I need to store passwords that can be retrieved

查看:37
本文介绍了双向加密:我需要存储可以找回的密码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am creating an application that will store passwords, which the user can retrieve and see. The passwords are for a hardware device, so checking against hashes are out of the question.

我需要知道的是:

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

  1. How do I encrypt and decrypt a password in PHP?

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

What is the safest algorithm to encrypt the passwords with?

我在哪里存储私钥?

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

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?

推荐答案

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

Personally, I would use mcrypt like others posted. But there is much more to note...

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

  1. How do I encrypt and decrypt a password in PHP?

请参阅下面的强大班级,为您处理一切:

See below for a strong class that takes care of everything for you:

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

What is the safest algorithm to encrypt the passwords with?

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

safest? any of them. The safest method if you're going to encrypt is to protect against information disclosure vulnerabilities (XSS, remote inclusion, etc). If it gets out, the attacker can eventually crack the encryption (no encryption is 100% un-reversible without the key - As @NullUserException points out this is not entirely true. There are some encryption schemes that are impossible to crack such as OneTimePad).

我在哪里存储私钥?

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

What I would do is use 3 keys. One is user supplied, one is application specific and the other is user specific (like a salt). The application specific key can be stored anywhere (in a config file outside of the web-root, in an environmental variable, etc). The user specific one would be stored in a column in the db next to the encrypted password. The user supplied one would not be stored. Then, you'd do something like this:

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

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

The benefit there, is that any 2 of the keys can be compromised without the data being compromised. If there's a SQL Injection attack, they can get the $userKey, but not the other 2. If there's a local server exploit, they can get $userKey and $serverKey, but not the third $userSuppliedKey. If they go beat the user with a wrench, they can get the $userSuppliedKey, but not the other 2 (but then again, if the user is beaten with a wrench, you're too late anyway).

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

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)

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

Absolutely. In fact, that's the only way I would do it. Otherwise you'd need to store an unencrypted version in a durable storage format (shared memory such as APC or memcached, or in a session file). That's exposing yourself to additional compromises. Never store the unencrypted version of the password in anything except a local variable.

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

In what ways can the password be stolen and decrypted? What do I need to be aware of?

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

Any form of compromise of your systems will let them view encrypted data. If they can inject code or get to your filesystem, they can view decrypted data (since they can edit the files that decrypt the data). Any form of Replay or MITM attack will also give them full access to the keys involved. Sniffing the raw HTTP traffic will also give them the keys.

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

Use SSL for all traffic. And make sure nothing on the server has any kind of vulnerabilities (CSRF, XSS, SQL Injection, Privilege Escalation, Remote Code Execution, etc).

这是一个强加密方法的 PHP 类实现:

Here's a PHP class implementation of a strong encryption method:

/**
 * 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验证:

Note that I'm using a function added in PHP 5.6: hash_equals. If you're on lower than 5.6, you can use this substitute function which implements a timing-safe comparison function using double HMAC verification:

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 来向您展示不同的实例仍然可以正确解密数据.

Note that I used $e2 the second time to show you different instances will still properly decrypt the data.

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

Now, how does it work/why use it over another solution:

  1. 按键

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

  • The keys are not directly used. Instead, the key is stretched by a standard PBKDF2 derivation.

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

The key used for encryption is unique for every encrypted block of text. The supplied key therefore becomes a "master key". This class therefore provides key rotation for cipher and auth keys.

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

IMPORTANT NOTE, the $rounds parameter is configured for true random keys of sufficient strength (128 bits of Cryptographically Secure random at a minimum). If you are going to use a password, or non-random key (or less random then 128 bits of CS random), you must increase this parameter. I would suggest a minimum of 10000 for passwords (the more you can afford, the better, but it will add to the runtime)...

数据完整性

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

加密:

  • 它使用 mcrypt 来实际执行加密.我建议使用 MCRYPT_BLOWFISHMCRYPT_RIJNDAEL_128 密码和 MCRYPT_MODE_CBC 作为模式.它足够强大,而且仍然相当快(在我的机器上加密和解密周期大约需要 1/2 秒).
  • It uses mcrypt to actually perform the encryption. I would suggest using either MCRYPT_BLOWFISH or MCRYPT_RIJNDAEL_128 cyphers and MCRYPT_MODE_CBC for the mode. It's strong enough, and still fairly fast (an encryption and decryption cycle takes about 1/2 second on my machine).

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

Now, as to point 3 from the first list, what that would give you is a function like this:

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

您可以在 makeKey() 函数中拉伸它,但由于它稍后会被拉伸,所以这样做并没有多大意义.

You could stretch it in the makeKey() function, but since it's going to be stretched later, there's not really a huge point to doing so.

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

As far as the storage size, it depends on the plain text. Blowfish uses a 8 byte block size, so you'll have:

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

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

So for a 16 character data source, there will be 16 characters of data to be encrypted. So that means the actual encrypted data size is 16 bytes due to padding. Then add the 16 bytes for the salt and 64 bytes for the hmac and the total stored size is 96 bytes. So there's at best a 80 character overhead, and at worst a 87 character overhead...

希望能帮到你...

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

Note: 12/11/12: I just updated this class with a MUCH better encryption method, using better derived keys, and fixing the MAC generation...

这篇关于双向加密:我需要存储可以找回的密码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆