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

标签 php security encryption cryptography encryption-symmetric

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

这是我想要做的:

我有一个由这些字段组成的表 (用户 ID、Fname、Lname、电子邮件、密码)

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

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

最佳答案

前言

从您的表定义开始:

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

以下是变化:
  • 字段 Fname , LnameEmail将使用对称密码进行加密,由 OpenSSL 提供,
  • IV字段将存储 initialisation vector用于加密。存储要求取决于使用的密码和模式;稍后会详细介绍这一点。
  • Password字段将使用单向密码散列进行散列,

  • 加密

    密码和模式

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

    加密 key

    一个好的加密 key 是由可靠的随机数生成器生成的二进制 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
    

    这可以执行一次或多次(如果您希望创建加密 key 链)。尽可能将这些保密。



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

    提供了一个函数来帮助您生成 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
    ));
    

    认证加密

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

    示例
    // 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 之类的散列算法来根据已知散列值验证文件内容。它们经过了极大的优化,可以在保持准确的同时尽可能快地进行验证。鉴于它们的输出空间相对有限,很容易构建一个具有已知密码和它们各自的哈希输出的数据库,彩虹表。

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

    目前有两种流行的选择:
  • PBKDF2(基于密码的 key 派生函数 v2)
  • bcrypt(又名河豚)

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

    crypt() 函数根据算法(Blowfish 的 $2y$)、成本因子(13 的因子在 3GHz 机器上大约需要 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()再次但您将先前计算的哈希值作为盐值传递。如果给定的密码与散列匹配,则返回值产生相同的散列。为了验证散列,通常建议使用恒定时间比较函数来避免时序攻击。

    使用 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
    }
    

    另见: password_hash() , password_verify()

    关于php - 如何在php中加密/解密数据?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10916284/

    相关文章:

    php - mysqli_fetch_assoc()需要参数/调用成员函数bind_param()错误。如何获取并修复实际的mysql错误?

    c# - 从 C# 代码中查找 PHP 中的相似代码(AES-256 解密)

    android - 如何在 Android 应用程序中保护服务帐户凭据?

    java - 如何存储密码?

    c# - 向使用 SocketAsyncEventArgs 构建的服务器添加加密

    php - 两种方式的数据库加密甚至对管理员都是安全的

    PHP/MySQL - 如果使用变量而不是硬编码值,查询将失败

    php - 当 session 未在 codeigniter 中设置时,不授予对 Controller 中某些方法的访问权限

    security - 拦截并分析网络中的所有电子邮件流量

    javascript - 在哪里安全地存储用于来自 Monzo 银行 api 的 api 请求的访问 token