如何在php中加密/解密数据? [英] How to encrypt/decrypt data in php?

查看:40
本文介绍了如何在php中加密/解密数据?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前是一名学生,正在学习 PHP,我正在尝试使用 PHP 对数据进行简单的加密/解密.我做了一些在线研究,其中一些令人困惑(至少对我而言).

I'm currently a student and I'm studying PHP, I'm trying to make a simple encrypt/decrypt of data in PHP. I made some online research and some of them were quite confusing(at least for me).

这是我想要做的:

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

I have a table consisting of these fields (UserID,Fname,Lname,Email,Password)

我想要的是将所有字段加密然后解密(是否可以使用sha256进行加密/解密,如果没有任何加密算法)

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) 结合一个好的盐".(基本上我只想有一个简单的加密/解密实现,hash(sha256)+salt)先生/女士,您的回答将有很大帮助,我们将不胜感激.谢谢++

Another thing I want to learn is how to create a one way hash(sha256) combined with a good "salt". (Basically I just want to have a simple implementation of encryption/decryption, hash(sha256)+salt) Sir/Ma'am, your answers would be of great help and be very much appreciated. Thank you++

推荐答案

前言

从您的表定义开始:

Foreword

Starting with your table definition:

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

变化如下:

  1. 字段FnameLnameEmail 将使用由OpenSSL,
  2. IV 字段将存储用于加密的初始化向量.存储要求取决于使用的密码和模式;稍后再详细介绍.
  3. Password 字段将使用 one-way 密码散列进行散列,
  1. The fields Fname, Lname and Email will be encrypted using a symmetric cipher, provided by OpenSSL,
  2. The IV field will store the initialisation vector used for encryption. The storage requirements depend on the cipher and mode used; more about this later.
  3. The Password field will be hashed using a one-way password hash,

加密

密码和模式

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

Choosing the best encryption cipher and mode is beyond the scope of this answer, but the final choice affects the size of both the encryption key and initialisation vector; for this post we will be using AES-256-CBC which has a fixed block size of 16 bytes and a key size of either 16, 24 or 32 bytes.

加密密钥

好的加密密钥是由可靠的随机数生成器生成的二进制 blob.建议使用以下示例 (>= 5.3):

A good encryption key is a binary blob that's generated from a reliable random number generator. The following example would be recommended (>= 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 模式所必需的.理想情况下,这些值应该只使用一次(技术上每个加密密钥一次),因此对行的任何部分的更新都应该重新生成它.

The initialisation vector adds randomness to the encryption and required for CBC mode. These values should be ideally be used only once (technically once per encryption key), so an update to any part of a row should regenerate it.

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

A function is provided to help you generate the IV:

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

示例

让我们使用之前的 $encryption_key$iv 来加密 name 字段;为此,我们必须将数据填充到块大小:

Example

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)来将这些值存储在数据库中.

Storage requirements

The encrypted output, like the IV, is binary; storing these values in a database can be accomplished by using designated column types such as BINARY or VARBINARY.

输出值和IV一样,是二进制的;要将这些值存储在 MySQL 中,请考虑使用 BINARYVARBINARY 列.如果这不是一个选项,您还可以使用 base64_encode()bin2hex(),这样做需要多出 33% 到 100% 的存储空间.

The output value, like the IV, is binary; to store those values in MySQL, consider using BINARY or VARBINARY columns. If this is not an option, you can also convert the binary data into a textual representation using base64_encode() or bin2hex(), doing so requires between 33% to 100% more storage space.

存储值的解密类似:

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

经过身份验证的加密

您可以通过附加由密钥(不同于加密密钥)和密文生成的签名,进一步提高生成的密文的完整性.在密文解密之前,首先要验证签名(最好采用恒定时间比较的方法).

Authenticated encryption

You can further improve the integrity of the generated cipher text by appending a signature that's generated from a secret key (different from the encryption key) and the cipher text. Before the cipher text is decrypted, the signature is first verified (preferably with a constant-time comparison method).

// 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()

必须尽可能避免在数据库中存储可逆密码;您只想验证密码而不是知道其内容.如果用户丢失了密码,最好让他们重置密码,而不是将原始密码发送给他们(确保密码重置只能在有限的时间内完成).

Storing a reversible password in your database must be avoided as much as possible; you only wish to verify the password rather than knowing its contents. If a user loses their password, it's better to allow them to reset it rather than sending them their original one (make sure that password reset can only be done for a limited time).

应用散列函数是一种单向操作;之后可以安全地用于验证,而不会泄露原始数据;对于密码,由于密码长度相对较短,而且很多人选择的密码都很差,因此暴力破解方法是一种可行的方法来破解它.

Applying a hash function is a one-way operation; afterwards it can be safely used for verification without revealing the original data; for passwords, a brute force method is a feasible approach to uncover it due to its relatively short length and poor password choices of many people.

MD5 或 SHA1 等哈希算法用于根据已知哈希值验证文件内容.它们经过了极大的优化,可以在保持准确的同时尽可能快地进行验证.鉴于它们的输出空间相对有限,因此很容易构建一个具有已知密码及其各自哈希输出的数据库,即彩虹表.

Hashing algorithms such as MD5 or SHA1 were made to verify file contents against a known hash value. They're greatly optimized to make this verification as fast as possible while still being accurate. Given their relatively limited output space it was easy to build a database with known passwords and their respective hash outputs, the rainbow tables.

在对密码进行散列之前向密码中添加盐会使彩虹表变得无用,但最近的硬件进步使蛮力查找成为一种可行的方法.这就是为什么您需要一个故意变慢且根本无法优化的散列算法.它还应该能够为更快的硬件增加负载,而不会影响验证现有密码哈希以使其面向未来的能力.

Adding a salt to the password before hashing it would render a rainbow table useless, but recent hardware advancements made brute force lookups a viable approach. That's why you need a hashing algorithm that's deliberately slow and simply impossible to optimize. It should also be able to increase the load for faster hardware without affecting the ability to verify existing password hashes to make it future proof.

目前有两种流行的选择:

Currently there are two popular choices available:

  1. PBKDF2(基于密码的密钥派生函数 v2)
  2. bcrypt(又名河豚)

此答案将使用 bcrypt 的示例.

This answer will use an example with bcrypt.

一代

可以像这样生成密码哈希:

A password hash can be generated like this:

$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() 生成的,形成一个随机的数据块,然后运行base64_encode()strtr() 匹配[A-Za-z0-9/.] 所需的字母表.

The salt is generated with openssl_random_pseudo_bytes() to form a random blob of data which is then run through base64_encode() and strtr() to match the required alphabet of [A-Za-z0-9/.].

crypt() 函数根据算法 ($2y$ for Blowfish)、成本因素(13 的因素在 3GHz 机器上大约需要 0.40 秒)和 22 个字符的盐.

The crypt() function performs the hashing based on the algorithm ($2y$ for Blowfish), the cost factor (a factor of 13 takes roughly 0.40s on a 3GHz machine) and the salt of 22 characters.

验证

获取包含用户信息的行后,您可以通过以下方式验证密码:

Once you have fetched the row containing the user information, you validate the password in this manner:

$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() 但您将先前计算的哈希值作为盐值传递.如果给定的密码与散列匹配,则返回值产生相同的散列.为了验证散列,通常建议使用恒定时间比较函数以避免时序攻击.

To verify a password, you call crypt() again but you pass the previously calculated hash as the salt value. The return value yields the same hash if the given password matches the hash. To verify the hash, it's often recommended to use a constant-time comparison function to avoid timing attacks.

使用 PHP 5.5 进行密码散列

PHP 5.5 引入了密码散列函数,您可以使用它来简化上述方法散列:

PHP 5.5 introduced the password hashing functions that you can use to simplify the above method of hashing:

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

并验证:

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

另见:password_hash()password_verify()

这篇关于如何在php中加密/解密数据?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆