如何encryption/解密数据在PHP?
我目前是一名学生,我正在学习PHP,我试图在PHP中对数据进行简单的encryption/解密。 我做了一些在线调查,其中一些令人困惑(至less对我而言)。
这是我想要做的:
我有一个由这些字段(UserID,Fname,Lname,电子邮件,密码)组成的表
我想要的是所有的领域encryption,然后解密(是否有可能使用sha256
encryption/解密,如果没有任何encryptionalgorithm)
我想学习的另一件事是如何创build一个单向hash(sha256)
结合一个好的“盐”。 (基本上我只想简单的实现encryption/解密, hash(sha256)+salt)
Sir / Ma'am,你的回答会非常有帮助,非常感谢。 谢谢++
前言
从你的表格定义开始:
- UserID - Fname - Lname - Email - Password - IV
这里是变化:
-
Fname
,Lname
和Email
字段将使用OpenSSL提供的对称密码进行encryption, -
IV
字段将存储用于encryption的初始化向量 。 存储要求取决于使用的密码和模式; 更多关于这个。 -
Password
字段将使用单向密码哈希散列,
encryption
密码和模式
select最好的encryption密码和模式已经超出了这个答案的范围,但最终select会影响encryption密钥和初始化vector的大小; 对于这篇文章,我们将使用AES-256-CBC,其固定块大小为16字节,密钥大小为16,24或32字节。
encryption密钥
一个好的encryption密钥是由可靠的随机数生成器生成的二进制数据块。 下面的例子将被推荐(> = 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
这可以做一次或多次(如果你想创build一个encryption密钥链)。 尽可能保持私密性。
IV
初始化vector为encryption增加了随机性,并且为CBC模式所需。 这些值理想情况下应该只使用一次(技术上每个encryption密钥一次),因此对行的任何部分的更新应该重新生成它。
提供了一个函数来帮助您生成IV:
$iv_size = 16; // 128 bits $iv = openssl_random_pseudo_bytes($iv_size, $strong);
例
我们使用前面的$encryption_key
和$iv
来encryption名称字段。 要做到这一点,我们必须将我们的数据填充到块大小:
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这样的encryption输出是二进制的; 将这些值存储在数据库中可以通过使用指定的列types(如BINARY
或VARBINARY
。
输出值,如IV,是二元的; 要将这些值存储在MySQL中,请考虑使用BINARY
或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 ));
经过身份validation的encryption
通过添加从密钥(不同于encryption密钥)和密文生成的签名,可以进一步提高生成的密文的完整性。 在密码文本被解密之前,首先validation签名(最好用一个恒定时间的比较方法)。
例
// 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()
哈希
必须尽可能避免在数据库中存储可逆密码; 你只希望validation密码,而不是知道它的内容。 如果用户丢失密码,最好让他们重置密码,而不是发送原来的密码(确保密码重置只能在有限的时间内完成)。
应用散列函数是单向操作; 之后可以安全地用于validation而不显示原始数据; 对于密码来说,暴力破解是一种可行的方法,因为它的长度比较短,而且许多人的密码select不好。
散列algorithm(如MD5或SHA1)是为了根据已知的散列值validation文件内容。 他们进行了极大的优化,使得这个validation尽可能快,同时仍然准确。 由于输出空间相对有限,build立一个已知密码的数据库以及它们各自的散列输出(彩虹表)是很容易的。
在散列之前向密码添加一个盐会使彩虹表无用,但最近的硬件升级使得powershell查找成为可行的方法。 这就是为什么你需要散列algorithm,故意缓慢,不可能优化。 它也应该能够增加更快硬件的负载,而不会影响validation现有密码散列的能力,以使其未来certificate。
目前有两种stream行的select可用:
- PBKDF2(基于密码的密钥推导函数v2)
- bcrypt(又名Blowfish)
这个答案将使用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()
生成盐,形成一个随机的blob数据,然后通过base64_encode()
和strtr()
以匹配[A-Za-z0-9/.]
所需的字母表。
crypt()
函数根据algorithm执行散列( $2y$
为Blowfish),成本因子(13因子在3GHz机器上大约需要0.40s)和22个字符的盐。
validation
获取包含用户信息的行后,您将按照以下方式validation密码:
$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; }
要validation密码,您再次调用crypt()
,但是您将先前计算的哈希值作为盐值传递。 如果给定的密码匹配散列,则返回值会产生相同的散列值。 为了validation哈希值,通常build议使用恒定时间比较函数来避免计时攻击。
密码散列与PHP 5.5
PHP 5.5引入了密码散列函数 ,可以用来简化上面的散列方法:
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);
并validation:
if (password_verify($given_password, $db_hash)) { // password valid }
另请参阅: password_hash()
, password_verify()
我认为这之前已经得到了答复…但无论如何,如果你想encryption/解密数据,你不能使用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);
答案背景和解释
要理解这个问题,你必须先了解SHA256是什么。 SHA256是一个encryption哈希函数 。 encryption哈希函数是一个单向函数,其输出是密码安全的。 这意味着计算散列(等同于encryption数据)很容易,但很难使用散列获得原始input(相当于解密数据)。 由于使用encryption哈希函数意味着解密在计算上是不可行的,因此您不能使用SHA256来执行解密。
你想要使用的是双向函数,但更具体地说,是一个分组密码 。 一种允许encryption和解密数据的function。 函数mcrypt_encrypt
和mcrypt_decrypt
默认使用Blowfishalgorithm。 PHP的mcrypt的使用可以在本手册中find。 用于select密码mcrypt使用的密码定义列表也存在。 河豚维基可以在维基百科find。 分组密码使用已知密钥以已知大小和位置的块对input进行encryption,以便稍后可以使用密钥对数据进行解密。 这是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);
这里是一个使用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;
我花了很openssl_decrypt()
才弄清楚,在使用openssl_decrypt()
时候怎样才不会openssl_decrypt()
然后得到encryption和解密的工作。
// 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传递encryption的string,则需要对string进行urlencode编码:
$encrypted = urlencode($encrypted);
为了更好地理解正在发生的事情,请阅读:
- http://blog.turret.io/the-missing-php-aes-encryption-example/
- http://thefsb.tumblr.com/post/110749271235/using-opensslendecrypt-in-php-
要生成16个字节的长键,您可以使用:
$bytes = openssl_random_pseudo_bytes(16); $hex = bin2hex($bytes);
要查看openssl的错误消息,可以使用: echo openssl_error_string();
希望有所帮助。