我正在创建一个应用程序,用于存储密码,用户可以检索并查看密码。 这些密码用于硬件设备,因此无法根据哈希值进行检查。
我需要知道以下几点:
如何在PHP中加密和解密密码?
使用哪种最安全的算法来加密密码?
私钥应该存储在哪里?
不存储私钥,要求用户在需要解密密码时输入私钥是否是好主意? (此应用程序的用户可信任)
密码可能会以什么方式被盗取和解密? 我需要注意什么?
我正在创建一个应用程序,用于存储密码,用户可以检索并查看密码。 这些密码用于硬件设备,因此无法根据哈希值进行检查。
我需要知道以下几点:
如何在PHP中加密和解密密码?
使用哪种最安全的算法来加密密码?
私钥应该存储在哪里?
不存储私钥,要求用户在需要解密密码时输入私钥是否是好主意? (此应用程序的用户可信任)
密码可能会以什么方式被盗取和解密? 我需要注意什么?
个人而言,我会像其他人一样使用mcrypt
。但是还有更多需要注意的地方...
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?
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 one-time pad).
Where do I store the private key?
I would use three 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 configuration 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;
The benefit there, is that any two 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 two. 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 two (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)
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?
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.
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.).
/**
* 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);
}
}
hash_equals
。如果您使用低于 5.6 的版本,则可以使用此替代函数,该函数使用 double HMAC verification 实现了一个 timing-safe comparison 函数: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
,以向您展示不同的实例仍然可以正确解密数据。$rounds
参数配置为具有足够强度(至少128位加密安全随机数)的真随机密钥。 如果您要使用密码或非随机密钥(或比128位CS随机更少的随机性),则必须增加此参数。 对于密码,建议最低10000(您能负担得起的越多越好,但会增加运行时间)...MCRYPT_BLOWFISH
或MCRYPT_RIJNDAEL_128
密码和MCRYPT_MODE_CBC
模式。它足够强大,而且仍然相当快(在我的机器上,加密和解密循环大约需要半秒钟)。现在,关于第一个列表中的第3点,这将给您一个像这样的函数:
function makeKey($userKey, $serverKey, $userSuppliedKey) {
$key = hash_hmac('sha512', $userKey, $serverKey);
$key = hash_hmac('sha512', $key, $userSuppliedKey);
return $key;
}
您可以在makeKey()
函数中进行拉伸,但由于稍后将进行拉伸,因此没有太大的意义。
至于存储大小,这取决于纯文本。 Blowfish使用8字节块大小,因此您将拥有:
因此,对于16个字符的数据源,将有16个字符的数据要加密。因此,实际加密数据的大小为16个字节,因为填充。然后添加16个字节的盐和64个字节的hmac,总存储大小为96个字节。因此,最多有80个字符的开销,最坏的情况下有87个字符的开销...
$enc = substr($data, 128, -128)
和$mac = substr($data, -128);
)。 - cosmorogers如何在PHP中加密和解密密码?
通过实现多种加密算法(或使用多个库)来完成。
最安全的加密密码算法是什么?
有很多不同的算法,其中没有一个是100%安全的。但其中许多对于商业甚至军事目的来说都足够安全。
我应该将私钥存储在哪里?
如果您决定实现公钥密码算法(例如RSA),则无需存储私钥。用户拥有私钥。您的系统具有公钥,可以将其存储在任何您希望的地方。
不将私钥存储在服务器上,要求用户每次需要解密密码时输入私钥是否是一个好主意?(该应用程序的用户可以信任)
如果您的用户能够记住极长的质数,那么可以。但通常您需要想出一种允许用户在某处存储其密钥的系统。
密码可以以何种方式被窃取和解密?我需要注意什么?
这取决于所使用的算法。但是,请始终确保不要将密码明文发送给用户或从用户那里接收明文密码。可以在客户端对其进行加密/解密,或使用HTTPS(或其他密码学手段)来保护服务器和客户端之间的连接。
但如果你只需要以加密方式存储密码,我建议你使用简单的XOR密码。这种算法的主要问题是它容易被频率分析攻破。然而,通常密码不是由长段的英文文本组成的,所以我认为你不用担心这个问题。XOR密码的第二个问题是,如果你有一条消息的加密和解密形式,你可以轻松地找到用于加密的密码。同样,在你的情况下,这不是一个大问题,因为它只影响已经被其他手段攻击的用户。
你需要的PHP模块是Mcrypt。
以下示例略有修改,仅供参考:
<?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 来解密您的密码。
最好的算法是相对主观的 - 询问五个人,得到五个答案。就我个人而言,如果默认的(Blowfish)无法满足您的要求,那么您可能有更大的问题!MCRYPT_MODE_ECB
不使用IV。其次,如果它使用了IV,你需要存储IV,因为没有IV你无法解密数据... - ircmaxell$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);
openssl_public_encrypt
和openssl_private_decrypt
4
。是的 - 用户每次都必须输入他们的应用程序密码,但将其存储在会话中会引起其他问题。
5
。
$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.
使用 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.';
}
?>