php - 使用 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 函数怎么办?
解决方案
我看过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 是可插拔的;我们提供了两个后端(FIPSCrypto
和ModernCrypto
),但提供了一个接口,如果有人需要编写自己的,可以使用该接口。(例如,如果您出于某种原因需要 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,您只能安全地摆脱它。永远不应信任未经身份验证的加密。
推荐阅读
- python - 如何在 Python 字典中获取元素值
- python - 如何解决输入和字符串的名称错误
- java - 如何使 javax.mail 使用本地“sendmail”二进制文件?
- xcode - XCTest for a Mac command line application
- python - 如何通过在 Windows 上使用系统调用从 python 中正确获取 R sessionInfo()?
- angular - Angular mat-checkbox选中不等于表单组
- python - pynput按键函数调用事件函数问题
- sql - 通过更新旧表中的重复行来创建新的视图 psql 表
- spartacus-storefront - 更改产品列表页面中的默认排序选项
- android - 任何人都可以解释为什么这个 android 布局不工作并且在活动启动后立即使活动崩溃