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

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

我需要知道的是:

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

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

  3. 我在哪里存储私钥?

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

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

    就个人而言,我会像其他人一样使用mcrypt 。 但还有很多需要注意的地方

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

      请参阅下面的一个强大的班级,为您照顾一切:

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

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

    3. 我在哪里存储私钥?

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

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

      这样做的好处是,任何两个密钥都可以被妥协,而不会泄露数据。 如果有一个SQL注入攻击,他们可以得到$userKey ,但不是其他2.如果有本地服务器漏洞,他们可以得到$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); } } 

    请注意,我正在使用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); } 

    用法:

     $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来显示不同的实例仍将正确解密数据。

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

    1. 按键

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

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

      • 重要提示$rounds参数配置为具有足够强度的真随机密钥(128位密码安全随机)。 如果要使用密码或非随机密钥(或随机选择较少的CS随机128位),则必须增加此参数。 我建议至少10000密码(越多,你可以承受,越好,但它会增加到运行时)…

    2. 数据的完整性

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

      • 它使用mcrypt来实际执行加密。 我建议使用MCRYPT_BLOWFISHMCRYPT_RIJNDAEL_128密码和MCRYPT_MODE_CBC模式。 它足够强大,而且还相当快(我的机器上的加密和解密周期大约需要1/2秒)。

    现在,就第一个列表中的第三点而言,会给你一个这样的函数:

     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个字节和hmac的64个字节,总共存储的大小是96个字节。 所以最多只有80个字符的开销,最坏的是87个字符的开销。

    我希望这有助于…

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

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

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

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

    而不是存储私钥,是否需要用户在需要解密的密码时输入私钥? (这个应用程序的用户可以信任)那么,如果你的用户可以记得可笑的长素数然后 – 是的,为什么不。 但是一般来说,你需要想出一个让用户把密钥存储在某个地方的系统。

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

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

    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"; ?> 

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

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

    2. 鉴于PHP需要加密 – 不知道你可以把它隐藏在任何地方 – 欢迎对此发表评论。 标准的PHP最佳编码习惯当然适用!

    3. 鉴于加密密钥将在您的代码无论如何,不​​知道你会得到什么,提供其他应用程序是安全的。

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

    我把一个骑士放在我的答案上 – 我不是一个PHP加密专家,但是,我想我所回答的是标准的做法 – 我欢迎其他人的评论。

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

    我只是建议公共密钥加密,如果你想能够设置一个用户的密码没有他们的互动(这可以很方便地重置和共享密码)。

    公钥

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

    对称

    1. Mcrypt扩展
    2. AES-256可能是一个安全的赌注,但这本身可能是个问题
    3. 你不要 – 这将是他们的应用程序密码

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

    5

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

    我尝试了这样的事情,但请注意,我不是密码学家,也不掌握有关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. 

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

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

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

    正如其他人所说的那样,mcrypt扩展提供了许多加密函数的访问权限 – 不过,您正在邀请您的用户将所有的鸡蛋放在一个篮子里 – 一个可能成为攻击者的目标 – 如果您甚至不知道如何开始解决问题,那么你正在做你的用户伤害。 您无法了解如何保护数据。

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

    话虽如此,建立一个合理安全的系统是可能的

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

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

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