我目前是一名学生,我正在学习PHP,我正在尝试在PHP中对数据进行简单的加密/解密。我做了一些在线研究,其中一些很混乱(至少对我而言)。
这是我正在尝试做的事情:
我有一个由以下字段组成的表(UserID,Fname,Lname,Email,Password)
我想要的是将所有字段加密然后解密(是否可以使用sha256
进行加密/解密,如果没有加密算法的话)
我想要学习的另一件事是如何创建单向hash(sha256)
与良好的“盐”相结合。
(基本上我只想简单实现加密/解密,hash(sha256)+salt)
先生/女士,你的答案会有很大的帮助,非常感谢。谢谢++
答案 0 :(得分:282)
从表定义开始:
- UserID
- Fname
- Lname
- Email
- Password
- IV
以下是更改:
Fname
,Lname
和Email
将使用OpenSSL提供的对称密码进行加密,IV
字段将存储用于加密的initialisation vector。存储要求取决于使用的密码和模式;稍后会详细介绍。Password
字段将使用单向密码哈希进行哈希处理,密码和模式
选择最佳加密密码和模式超出了本答案的范围,但最终选择会影响加密密钥和初始化向量的大小;对于这篇文章,我们将使用AES-256-CBC,其固定块大小为16字节,密钥大小为16,24或32字节。
加密密钥
一个好的加密密钥是一个二进制blob,它是从一个可靠的随机数生成器生成的。建议使用以下示例(> = 5.3):
$key_size = 32; // 256 bits
$encryption_key = openssl_random_pseudo_bytes($key_size, $strong);
// $strong will be true if the key is crypto safe
这可以进行一次或多次(如果您希望创建一系列加密密钥)。保持这些尽可能私密。
<强> IV 强>
初始化向量为加密增加了随机性,并为CBC模式提供了所需。理想情况下,这些值应该只使用一次(技术上每个加密密钥一次),因此对行的任何部分的更新应该重新生成它。
提供了一个功能来帮助您生成IV:
$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
让我们使用之前的$encryption_key
和$iv
加密名称字段;要做到这一点,我们必须将数据填充到块大小:
function pkcs7_pad($data, $size)
{
$length = $size - strlen($data) % $size;
return $data . str_repeat(chr($length), $length);
}
$name = 'Jack';
$enc_name = openssl_encrypt(
pkcs7_pad($name, 16), // padded data
'AES-256-CBC', // cipher and mode
$encryption_key, // secret key
0, // options (not used)
$iv // initialisation vector
);
加密输出,如IV,是二进制;将这些值存储在数据库中可以使用指定的列类型来完成,例如BINARY
或VARBINARY
。
输出值与IV类似,是二进制;要在MySQL中存储这些值,请考虑使用BINARY
or VARBINARY
列。如果这不是一个选项,您还可以使用base64_encode()
或bin2hex()
将二进制数据转换为文本表示,这样做需要的存储空间增加33%到100%。
存储值的解密类似:
function pkcs7_unpad($data)
{
return substr($data, 0, -ord($data[strlen($data) - 1]));
}
$row = $result->fetch(PDO::FETCH_ASSOC); // read from database result
// $enc_name = base64_decode($row['Name']);
// $enc_name = hex2bin($row['Name']);
$enc_name = $row['Name'];
// $iv = base64_decode($row['IV']);
// $iv = hex2bin($row['IV']);
$iv = $row['IV'];
$name = pkcs7_unpad(openssl_decrypt(
$enc_name,
'AES-256-CBC',
$encryption_key,
0,
$iv
));
您可以通过附加从密钥(不同于加密密钥)和密文生成的签名来进一步提高生成的密文的完整性。在解密密文之前,首先验证签名(最好使用恒定时间比较方法)。
// generate once, keep safe
$auth_key = openssl_random_pseudo_bytes(32, $strong);
// authentication
$auth = hash_hmac('sha256', $enc_name, $auth_key, true);
$auth_enc_name = $auth . $enc_name;
// verification
$auth = substr($auth_enc_name, 0, 32);
$enc_name = substr($auth_enc_name, 32);
$actual_auth = hash_hmac('sha256', $enc_name, $auth_key, true);
if (hash_equals($auth, $actual_auth)) {
// perform decryption
}
另请参阅:hash_equals()
必须尽可能避免在数据库中存储可逆密码;您只想验证密码而不是知道其内容。如果用户丢失了密码,最好允许他们重置密码而不是发送原密码(确保密码重置只能在有限的时间内完成)。
应用哈希函数是单向操作;之后,它可以安全地用于验证而不会泄露原始数据;对于密码,蛮力方法是一种可行的方法来揭示它,因为它的长度相对较短,而且许多人的密码选择不佳。
哈希算法(如MD5或SHA1)用于根据已知哈希值验证文件内容。它们经过了极大的优化,可以在保持准确的同时尽快进行验证。鉴于它们相对有限的输出空间,很容易构建一个具有已知密码及其各自的哈希输出的数据库,彩虹表。
在散列之前向密码添加一个盐会使彩虹表变得无用,但最近的硬件改进使得强力查找成为可行的方法。这就是为什么你需要一个故意放慢并且根本无法优化的散列算法的原因。它还应该能够增加更快硬件的负载,而不会影响验证现有密码哈希的能力,以便将来证明。
目前有两种流行的选择:
这个答案将使用bcrypt的一个例子。
<强>代强>
可以像这样生成密码哈希:
$password = 'my password';
$random = openssl_random_pseudo_bytes(18);
$salt = sprintf('$2y$%02d$%s',
13, // 2^n cost factor
substr(strtr(base64_encode($random), '+', '.'), 0, 22)
);
$hash = crypt($password, $salt);
使用openssl_random_pseudo_bytes()
生成salt以形成随机数据块,然后通过base64_encode()
和strtr()
运行以匹配[A-Za-z0-9/.]
所需的字母。
crypt()
函数根据算法(Blowfish的$2y$
)执行散列,成本因子(在3GHz机器上13倍大约需要0.40秒)和22个字符的盐
<强>验证强>
获取包含用户信息的行后,您将以这种方式验证密码:
$given_password = $_POST['password']; // the submitted password
$db_hash = $row['Password']; // field with the password hash
$given_hash = crypt($given_password, $db_hash);
if (isEqual($given_hash, $db_hash)) {
// user password verified
}
// constant time string compare
function isEqual($str1, $str2)
{
$n1 = strlen($str1);
if (strlen($str2) != $n1) {
return false;
}
for ($i = 0, $diff = 0; $i != $n1; ++$i) {
$diff |= ord($str1[$i]) ^ ord($str2[$i]);
}
return !$diff;
}
要验证密码,请再次调用crypt()
,但将先前计算的哈希值作为salt值传递。如果给定的密码与哈希匹配,则返回值产生相同的哈希值。为了验证散列,通常建议使用恒定时间比较功能来避免定时攻击。
使用PHP 5.5进行密码散列
PHP 5.5引入了password hashing functions,您可以使用它来简化上述散列方法:
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);
验证:
if (password_verify($given_password, $db_hash)) {
// password valid
}
答案 1 :(得分:21)
我认为之前已经回答了......但无论如何,如果你想加密/解密数据,就不能使用SHA256
//Key
$key = 'SuperSecretKey';
//To Encrypt:
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, 'I want to encrypt this', MCRYPT_MODE_ECB);
//To Decrypt:
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_ECB);
答案 2 :(得分:14)
要理解这个问题,首先必须了解SHA256是什么。 SHA256是加密哈希函数。加密哈希函数是一种单向函数,其输出是加密安全的。这意味着很容易计算哈希(相当于加密数据),但很难使用哈希获得原始输入(相当于解密数据)。由于使用加密哈希函数意味着解密在计算上是不可行的,因此您无法使用SHA256执行解密。
您想要使用的是双向功能,但更具体地说,是分组密码。一种允许加密和解密数据的功能。默认情况下,函数mcrypt_encrypt
和mcrypt_decrypt
使用Blowfish算法。 PHP可以在manual中找到PHP的使用。还存在用于选择密码mcrypt的cipher definitions列表。可以在Wikipedia找到关于Blowfish的维基。分组密码使用已知密钥以已知大小和位置的块加密输入,以便稍后可以使用密钥对数据进行解密。这就是SHA256无法为您提供的。
$key = 'ThisIsTheCipherKey';
$ciphertext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, 'This is plaintext.', MCRYPT_MODE_CFB);
$plaintext = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $encrypted, MCRYPT_MODE_CFB);
答案 3 :(得分:8)
以下是使用openssl_encrypt
的示例//Encryption:
$textToEncrypt = "My Text to Encrypt";
$encryptionMethod = "AES-256-CBC";
$secretHash = "encryptionhash";
$iv = mcrypt_create_iv(16, MCRYPT_RAND);
$encryptedText = openssl_encrypt($textToEncrypt,$encryptionMethod,$secretHash, 0, $iv);
//Decryption:
$decryptedText = openssl_decrypt($encryptedText, $encryptionMethod, $secretHash, 0, $iv);
print "My Decrypted Text: ". $decryptedText;
答案 4 :(得分:1)
function my_simple_crypt( $string, $action = 'e' ) {
// you may change these values to your own
$secret_key = 'my_simple_secret_key';
$secret_iv = 'my_simple_secret_iv';
$output = false;
$encrypt_method = "AES-256-CBC";
$key = hash( 'sha256', $secret_key );
$iv = substr( hash( 'sha256', $secret_iv ), 0, 16 );
if( $action == 'e' ) {
$output = base64_encode( openssl_encrypt( $string, $encrypt_method, $key, 0, $iv ) );
}
else if( $action == 'd' ){
$output = openssl_decrypt( base64_decode( $string ), $encrypt_method, $key, 0, $iv );
}
return $output;
}
答案 5 :(得分:0)
我花了很长时间才弄清楚如何在使用false
时获得openssl_decrypt()
并获得加密和解密工作。
// cryptographic key of a binary string 16 bytes long (because AES-128 has a key size of 16 bytes)
$encryption_key = '58adf8c78efef9570c447295008e2e6e'; // example
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
$encrypted = openssl_encrypt($plaintext, 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, $iv);
$encrypted = $encrypted . ':' . base64_encode($iv);
// decrypt to get again $plaintext
$parts = explode(':', $encrypted);
$decrypted = openssl_decrypt($parts[0], 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, base64_decode($parts[1]));
如果要通过URL传递加密字符串,则需要对字符串进行urlencode:
$encrypted = urlencode($encrypted);
为了更好地了解正在发生的事情,请阅读:
要生成16字节长的密钥,您可以使用:
$bytes = openssl_random_pseudo_bytes(16);
$hex = bin2hex($bytes);
要查看openssl的错误消息,您可以使用:echo openssl_error_string();
希望有所帮助。