如何在 php 中加密/解密数据?

我目前是一名学生,我正在学习 PHP,我试图使一个简单的加密/解密数据在 PHP。我做了一些在线研究,其中一些相当令人困惑(至少对我来说)。

这就是我要做的:

我有一个由这些字段 (UserID,Fname,Lname,Email,Password)组成的表

What I want to have is have the all fields encrypted and then be decrypted(Is it possible to use sha256 for encryption/decryption, if not any encryption algorithm)

我想学习的另一件事是如何创建一个单向 hash(sha256)结合良好的“盐”。 (基本上我只想有一个简单的加密/解密实现,< code > hash (sha256) + salt) 先生/女士,您的回答将是非常有帮助的,非常感谢。谢谢 + +

224182 次浏览

I'm think this has been answered before...but anyway, if you want to encrypt/decrypt data, you can't use 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);

答案背景和说明

To understand this question, you must first understand what SHA256 is. SHA256 is a 密码杂凑函数. A Cryptographic Hash Function is a one-way function, whose output is cryptographically secure. This means it is easy to compute a hash (equivalent to encrypting data), but hard to get the original input using the hash (equivalent to decrypting the data). Since using a Cryptographic hash function means decrypting is computationally infeasible, so therefore you cannot perform decryption with SHA256.

您想要使用的是一个双向函数,但更具体地说,是一个 块密码。允许数据加密和解密的函数。默认情况下,函数 mcrypt_encryptmcrypt_decrypt使用 Blowfish 算法。PHP 对 mcrypt 的使用可以在这个 手动操作中找到。还存在用于选择密码 mcrypt 使用的 密码定义列表。在 维基百科上可以找到一个关于河豚的维基。块密码使用已知的密钥对已知大小和位置的块中的输入进行加密,以便以后可以使用该密钥对数据进行解密。这是 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);

前言

从表定义开始:

- UserID
- Fname
- Lname
- Email
- Password
- IV

Here are the changes:

  1. 字段 FnameLnameEmail将使用由 OpenSSL提供的对称密码进行加密,
  2. IV字段将存储用于加密的 初始化向量。存储需求取决于所使用的密码和模式; 稍后详细介绍。
  3. The Password field will be hashed using a 单程票 password hash,

加密

Cipher and mode

选择最佳的加密密码和模式超出了这个答案的范围,但是最终的选择会影响加密密钥和初始化向量的大小; 对于这篇文章,我们将使用 AES-256-CBC,它有一个固定的块大小为16字节,密钥大小为16,24或32字节。

加密密钥

一个好的加密密钥是由可靠的随机数生成器生成的二进制块。建议使用以下示例(> = 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

This can be done once or multiple times (if you wish to create a chain of encryption keys). Keep these as private as possible.

IV

初始化向量为加密增加了随机性,并且是 CBC 模式所必需的。理想情况下,这些值应该只使用一次(从技术上讲,每个加密密钥只使用一次) ,因此对行的任何部分的更新都应该重新生成它。

提供了一个函数来帮助您生成 IV:

$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);

例子

Let's encrypt the name field, using the earlier $encryption_key and $iv; to do this, we have to pad our data to the block size:

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 类似,加密的输出是二进制的; 可以通过使用指定的列类型(如 BINARYVARBINARY)在数据库中存储这些值。

输出值与 IV 一样是二进制的; 要在 MySQL 中存储这些值,可以考虑使用 ABC0或 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等散列算法根据已知的散列值验证文件内容。他们极大的优化,使这个验证尽可能快,同时仍然是准确的。考虑到它们相对有限的输出空间,使用已知的密码和它们各自的哈希输出——彩虹表——很容易建立一个数据库。

在散列之前在密码中添加一个 salt 会使彩虹表失效,但是最近的硬件进步使得强力查找成为一种可行的方法。这就是为什么你需要一个哈希算法,故意慢,简单地不可能优化。它还应该能够在不影响验证现有密码哈希的能力的情况下,为更快的硬件增加负载,使其成为未来的证据。

目前有两种流行的选择:

  1. PBkdf2(基于密码的密钥导出函数 v2)
  2. bcrypt (aka Blowfish)

This answer will use an example with 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引入了 密码哈希函数密码哈希函数,您可以使用它来简化上述散列方法:

$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);

并验证:

if (password_verify($given_password, $db_hash)) {
// password valid
}

参见: password_hash()password_verify()

下面是使用 openssl _ crypt 的示例

//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;

我花了很长时间才弄明白,如何不得到一个 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();

希望能帮上忙。

     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;
}