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

我正在创建一个应用程序,将存储密码,用户可以检索和看到。密码是针对硬件设备的,因此根据散列进行检查是不可能的。

我需要知道的是:

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

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

  3. 我把私钥存放在哪里?

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

  5. 密码可以通过什么方式被盗取和解密? 我需要注意什么?

69937 次浏览

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

通过实现多个加密算法中的一个(或使用多个库中的一个)

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

有很多不同的算法,没有一个是100% 安全的。但其中许多都足够安全,可用于商业甚至军事目的

我把私钥存放在哪里?

如果决定实现公钥加密算法(例如 RSA) ,则不存储私钥。用户有一个私钥。您的系统有一个公钥,可以存储在任何您希望的地方。

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

好吧,如果你的用户可以记住可笑的长素数,那么-是的,为什么不呢。但一般来说,你需要想出一个系统,让用户存储他们的密钥的地方。

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

这取决于所使用的算法。但是,请始终确保不要向用户发送未加密的密码或从用户发送未加密的密码。要么在客户端对其进行加密/解密,要么使用 HTTPS(或者使用其他加密手段来保护服务器和客户端之间的连接)。

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

  1. 您要查看的 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";
    ?>
    

    您将使用 < strong > mcrypt _ deccrypt 来解密密码。

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

  3. 考虑到 PHP 需要对它进行加密,我不确定是否可以将它隐藏在任何地方。标准 PHP 最佳编码实践当然适用!

  4. 考虑到加密密钥无论如何都会在您的代码中,如果您的应用程序的其余部分是安全的,那么我不确定您将获得什么。

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

我会在我的回答上加上附加条件。我不是一个 PHP 密码学专家,但是,我认为我所回答的是标准实践。

我只建议公开密钥加密,如果你想要在没有用户交互的情况下设置用户的密码(这对于重置和共享密码很方便)。

公开密钥

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

对称

  1. 密码分机
  2. AES-256可能是一个安全的赌注,但这可能是一个 SO 问题本身
  3. 你不知道,这是他们的申请密码

都有

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

5.

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

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

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

    下面是一个强大的班级,它会为你照顾好一切:

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

    最安全?他们中的任何一个。如果要进行加密,最安全的方法是防止信息泄露漏洞(XSS、远程包含等)。如果它泄露出去,攻击者最终会破解加密(没有密钥的加密是100% 不可逆的——正如@NullUserException 指出的那样,这并不完全正确。有一些加密方案是不可能破解的,如 一次性本子)。

  3. 我把私钥存放在哪里?

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

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

    这样做的好处是,任何两个密钥都可以被破坏,而数据不会被破坏。如果有 SQL 注入攻击,他们可以得到 $userKey,但不能得到另外两个。如果存在本地服务器漏洞,他们可以获得 $userKey$serverKey,但不能获得第三个 $userSuppliedKey。如果他们去用扳手打用户,他们可以得到 $userSuppliedKey,但不是其他两个(但话说回来,如果用户被用扳手打,你无论如何都太晚了)。

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

    当然。事实上,只有这样我才会这么做。否则,您将需要以持久存储格式(共享内存,如 装甲运兵车Memcached,或会话文件)存储未加密的版本。那会让你自己暴露在更多的妥协之中。除了局部变量之外,不要将未加密的密码版本存储在任何地方。

  5. 密码可以通过什么方式被盗取和解密? 我需要注意什么?

    任何形式的系统泄露都会让他们查看加密数据。如果他们可以注入代码或访问您的文件系统,他们可以查看解密的数据(因为他们可以编辑解密数据的文件)。任何形式的 重播麻省理工学院攻击也将使他们完全访问所涉及的关键。嗅探原始 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位加密安全随机密钥)。如果您要使用密码,或非随机密钥(或随机性小于128位的 CS 随机) ,则 必须的增加此参数。我建议至少设置10000个密码(你能负担得起的越多越好,但它会增加运行时) ..。

  1. 资料完整性
  • 更新的版本使用 ENCRYPT-THEN-MAC,这是确保加密数据真实性的更好方法。
  1. 加密:
  • 它使用 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;
}

你可以在 makeKey()函数中拉伸它,但是因为它将在稍后被拉伸,所以这样做并没有什么意义。

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

  • Salt 为16个字节
  • Hmac 是64字节
  • 数据长度
  • 填充,使数据长度% 8 = = 0

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

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

我尝试过类似的东西,但请注意,我不是密码学家,我也没有关于 PHP 或任何编程语言的深入知识。这只是个想法。

我的想法是在一些文件或 资料库(或手动输入)中存储一个 钥匙,它的(位置)不容易预测(当然有一天任何东西都会被解密)。这个概念是为了延长解密时间)和加密敏感信息。

密码

$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 "SHA-1: " . sha1($text) . "<br/>";


$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $text, MCRYPT_MODE_ECB, $iv);
echo "Encrypted 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);
// Even if I add '?' to the sting to the text it works. I don't know why.

请注意,这只是一个概念。任何改进此代码将高度赞赏。

密码是针对硬件设备的,因此根据散列进行检查是不可能的

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

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

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

尽管如此,构建一个合理安全的系统仍然是 有可能的任务。

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

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

使用 Password _ hashPassword _ 確認

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