首页 > 解决方案 > 使用 PHP 安全加密 MySQL 数据库中的静态客户详细信息

问题描述

我正在使用 PHP 开发一个应用程序,它将一些客户详细信息存储在 MySQL 数据库中。(姓名、电子邮件地址、电话号码、邮寄地址)我不存储任何“敏感”信息,如银行/信用卡详细信息、SSN/SIN、出生日期等。只是基本的客户详细信息。

但是,虽然显然会采取安全预防措施,但如果黑客获得数据库的副本,我想要一种体面且相对简单的方法来使这些数据很难有用(通过对其进行加密)。

我读过在 MySQL 查询中执行加密不太安全,因为加密密钥将缓存在数据库的查询日志中。

所以不推荐这样的事情:

UPDATE customers SET email = AES_ENCRYPT('me@gmail.com', SHA2('encryption key here', 512));

我查看了有关 Stack Overflow 的许多问题以及其他资源。但是,许多建议至少有 5 年的历史,现在 PHP 7.2(及更高版本)可能有更简单的最佳实践。

我看过Defuse,但我倾向于避免使用我绝对不需要它们的第三方库。(我更喜欢理解代码并将其最小化以满足我的需要。)

查看 PHP 文档(https://www.php.net/manual/en/function.openssl-encrypt.php)我发现这个用户提供了建议,它看起来相当简单且易于实现:

--- Create Two Random Keys And Save Them In Your Configuration File ---
<?php
    // Create The First Key
    echo base64_encode(openssl_random_pseudo_bytes(32));

    // Create The Second Key
    echo base64_encode(openssl_random_pseudo_bytes(64));
?>
--------------------------------------------------------
<?php
    // Save The Keys In Your Configuration File
    define('FIRSTKEY', 'Lk5Uz3slx3BrAghS1aaW5AYgWZRV0tIX5eI0yPchFz4=');
    define('SECONDKEY', 'EZ44mFi3TlAey1b2w4Y7lVDuqO+SRxGXsa7nctnr/JmMrA2vN6EJhrvdVZbxaQs5jpSe34X3ejFK/o9+Y5c83w==');
?>
--------------------------------------------------------
<?php
    function secured_encrypt($data)
    {
        $first_key = base64_decode(FIRSTKEY);
        $second_key = base64_decode(SECONDKEY);

        $method = "aes-256-cbc";
        $iv_length = openssl_cipher_iv_length($method);
        $iv = openssl_random_pseudo_bytes($iv_length);

        $first_encrypted = openssl_encrypt($data, $method, $first_key, OPENSSL_RAW_DATA, $iv);
        $second_encrypted = hash_hmac('sha3-512', $first_encrypted, $second_key, TRUE);

        $output = base64_encode($iv.$second_encrypted.$first_encrypted);
        return $output;
    }
?>
--------------------------------------------------------
<?php
    function secured_decrypt($input)
    {
        $first_key = base64_decode(FIRSTKEY);
        $second_key = base64_decode(SECONDKEY);
        $mix = base64_decode($input);

        $method = "aes-256-cbc";
        $iv_length = openssl_cipher_iv_length($method);

        $iv = substr($mix, 0, $iv_length);
        $second_encrypted = substr($mix, $iv_length, 64);
        $first_encrypted = substr($mix, $iv_length+64);

        $data = openssl_decrypt($first_encrypted, $method, $first_key, OPENSSL_RAW_DATA, $iv);
        $second_encrypted_new = hash_hmac('sha3-512', $first_encrypted, $second_key, TRUE);

        if (hash_equals($second_encrypted, $second_encrypted_new))
            return $data;

        return false;
    }
?>

你认为这是相当安全的吗?我在想我可能会指定 AES-256-GCM,因为我认为 GCM 比 CBC 更好。

这种加密对我的需求来说是不是有点过头了?(同样,没有存储高度敏感的客户详细信息,并且“希望”这种级别的安全性(静态加密的数据库字段)无论如何都是多余的。)

如果我跳过 hash_hmac sha3-512 部分而只使用 openssl_encrypt 函数怎么办?

标签: phpencryption

解决方案


我看过Defuse,但我倾向于避免使用我绝对不需要它们的第三方库。(我更喜欢理解代码并将其最小化以满足我的需要。)

由于您的问题标题是Securely encrypting customer details at rest in MySQL database using PHP,我将不得不将我的答案一分为二。

在保护客户数据的情况下,您(和其他任何人)将收到的建议是使用可信赖的库

密码学很难做到正确,即使对于专家来说也是如此。就在这个月,许多低级密码库遭到攻击。但是,我多年来在 PHP 社区中推荐的库(即 libsodium)仍然不受这些攻击的影响(主要是设计使然)。

我和其他专家推荐的库旨在最大限度地提高安全性,最大限度地减少误用的可能性,并且易于审计。因为不想使用第三方库而回避这些建议是一个危险的立场,尤其是对密码学而言。

如果您希望“避免使用第三方库”恰好比保护客户更重要,那么您可能应该告诉您的客户您在做什么,为什么,以及安全行业的传统智慧是什么;这样他们就可以决定是否仍想成为您的客户。

相反,如果你说“这是为了我自己的自学,没有现实世界的生产系统”,那么这完全是另外一回事。毕竟,编写加密货币来学习是一件好事。

在 PHP + MySQL 中安全地加密静态客户详细信息

建议:使用CipherSweet

composer require paragonie/ciphersweet:^2

请记住,这部分答案是“我需要保护客户数据”。您不想安装第三方库的愿望需要解决这个问题;保护您的客户是重中之重。

CipherSweet 比Defuse更进一步:CipherSweet 不仅解决了“对称静态数据加密”问题,还解决了“我想加密数据但仍对其进行搜索”的问题。

CipherSweet 是可插拔的;我们提供了两个后端(FIPSCryptoModernCrypto),但提供了一个接口,如果有人需要编写自己的,可以使用该接口。(例如,如果您出于某种原因需要 SHA3 和 AES-GCM,您可以编写自己的FIPSCrypto变体。)

CipherSweet 文档可在线获取

它的目的不仅是解释幕后发生的事情,而且指导开发人员安全地使用它。如果您遇到任何问题,请随时寻求帮助。

如果您决定不需要任何“可搜索”位,您仍然可以使用 CipherSweet,而无需为简单的 AEAD 接口创建盲索引。

或者,Defuse 和Halite是不错的选择。

但重要的是,除非您是一位密码学工程师,能够在不意外引入漏洞的情况下重新发明这些第三方库之一(例如:定时攻击,或 AES-CBC + HMAC 但忘记在身份验证中包含 IV标签计算),那么您几乎可以肯定使用第三方库

没有客户参与,我只是想学习

我发现这个用户提供了建议,它看起来相当简单且易于实现:

您提供的代码不安全。具体来说,这部分代码没有涵盖 HMAC 标签计算中的 IV:

$first_encrypted = openssl_encrypt($data, $method, $first_key, OPENSSL_RAW_DATA, $iv);
$second_encrypted = hash_hmac('sha3-512', $first_encrypted, $second_key, TRUE);

注意如何$first_encrypted传递给hash_hmac()$iv不是?这是一个可利用的漏洞。

这是一个概念证明漏洞利用:

// Encrypt a message    
$ciphertext = secured_encrypt('{"is_admin":0,"user_id":12345}');

$decoded = base64_decode($ciphertext);
$extractedIv = mb_substr($decoded, 0, 16, '8bit');
$flip = "\x00\x00\x00\x00\x00\x00\x00\x00" . 
        "\x00\x00\x00\x00\x01\x00\x00\x00";
$extractedIv = $extractedIv ^ $flip;

// Put alternative IV in place of existing IV.
$spliced = $extractedIv . mb_substr($decoded, 16, null, '8bit');
$reEncoded = base64_encode($spliced);

// Decrypt message
$decrypted = secured_decrypt($reEncoded);

var_dump($decrypted, json_decode($decrypted, true));

您应该看到以下内容:

string(30) "{"is_admin":1,"user_id":12345}"
array(2) {
  ["is_admin"]=>
  int(1)
  ["user_id"]=>
  int(12345)
}

因此,CBC+HMAC 对开发者来说是危险的。

你最好使用 libsodium并学习如何在它提供的原语之上编写自己的高级协议,而不是试图让 CBC+HMAC 变得安全。

如果您想修复他们的代码,则需要进行这些更改。

secure_encrypt() 的补丁

- $second_encrypted = hash_hmac('sha3-512', $first_encrypted, $second_key, TRUE);
+ $second_encrypted = hash_hmac('sha3-512', $iv . $first_encrypted, $second_key, TRUE);

secure_decrypt() 的补丁

- $second_encrypted_new = hash_hmac('sha3-512', $first_encrypted, $second_key, TRUE);
+ $second_encrypted_new = hash_hmac('sha3-512', $iv . $first_encrypted, $second_key, TRUE);

回答您的问题

我在想我可能会指定 AES-256-GCM,因为我认为 GCM 比 CBC 更好。

AES-GCM 优于 CBC,因为 GCM 已通过身份验证,而 CBC 未通过。

但是,您只能在 AES-GCM 的给定密钥下加密大约 2^36 条消息。如果这不足以实现您的目标,那么 AES-GCM 也不适合。

这种加密对我的需求来说是不是有点过头了?(同样,没有存储高度敏感的客户详细信息,并且“希望”这种级别的安全性(静态加密的数据库字段)无论如何都是多余的。)

如果不是因为我上面披露的漏洞,它可能是。

如果我跳过 hash_hmac sha3-512 部分而只使用 openssl_encrypt 函数怎么办?

如果您切换到 AES-GCM 或 libsodium,您只能安全地摆脱它。永远不应信任未经身份验证的加密。


推荐阅读