安全分析与限制
  根据你的确切威胁模型,这个解决方案留下了两个必须回答的问题:
  是否真的可以安全使用,还是说可能仍然会泄漏数据,像滤网一样?
  它的用途有什么局限性?(这一个其实已经回答了。)
  以上我们的例子中,假设您的加密密钥和盲索引键是分开的,两个密钥都存储在Web服务器中,而数据库服务器没有任何方法来获取这些密钥,那么任何攻击者只会破坏数据库服务器但不是Web服务器)只能够了解若干行是否共享一个社会安全号码,而不是共享SSN是什么。为了使索引成为可能,这种重复的条目泄漏是必要的,这又允许从用户提供的值进行快速的SELECT查询。
  此外,如果攻击者能够在遵守存储在数据库中的盲目索引的同时观察/更改明文作为应用程序的普通用户,则可以将其利用为选择的明文攻击,在这些攻击中,它们以用户身份迭代每个可能的值然后与所得的盲目索引值相关联。这在HMAC方案中比在Argon2方案中更实际。而对于高熵或低灵敏度值(而不是SSN)来说,我只能说物理学真的在我们身边。
  对于现在这样的情况,犯罪分子要想进行更实际的攻击要将值从一行替换到另一行,然后正常访问应用程序,而这将会揭示明文,除非采用了独特的每行密钥(例如hash_hmac('sha256', $rowID, $masterKey, true)甚至可以在这里有效缓解)其他人会更喜欢)。这里好的防御是使用AEAD模式(将主键作为附加关联数据传递),以便将密文连接到特定的数据库行。(这样做并不会阻止攻击者删除数据,这是一个更大的挑战。)
  与其他解决方案泄露的信息量相比,大多数应用程序的威胁模型应该将其视为可接受的权衡。只要您使用身份验证加密进行加密,以及HMAC(用于盲目索引非敏感数据)或密码散列算法(盲目索引敏感数据),很容易理解应用程序的安全性。
  然而,它确实有一个非常严重的限制:它只适用于完全匹配。如果两个字符串以无意义的方式不同,但总是会产生不同的加密散列,则搜索一个不会产生另一个。如果您需要执行更多高级查询,但是仍然希望将解密密钥和明文值保留在数据库服务器的手中,我们将必须获得创造性。
  还值得注意的是,虽然HMAC / Argon2可以防止不具有密钥的攻击者学习数据库中存储的明文值,但它可能在现实世界会显示元数据(例如两个看似无关的人共享一个街道地址)。
  实现加密数据的模糊搜索
  可能的用途:加密人们的法定名称,并能够仅搜索部分匹配内容。
  我们在上一节的基础上构建一个盲目索引,让您可以查询数据库的精确匹配。
  这一次,我们将不会在现有表中添加列,而是将额外的索引值存储到连接表中。
CREATE TABLE humans (
humanid BIGSERIAL PRIMARY KEY,
first_name TEXT, /* encrypted */
last_name TEXT, /* encrypted */
ssn TEXT, /* encrypted */
);
CREATE TABLE humans_filters (
filterid BIGSERIAL PRIMARY KEY,
humanid BIGINT REFERENCES humans (humanid),
filter_label TEXT,
filter_value TEXT
);
/* Creates an index on the pair. If your SQL expert overrules this, feel free to omit it. */
CREATE INDEX ON humans_filters (filter_label, filter_value);
  这种变化的原因是规范化我们的数据结构。你可以通过添加列到现有的表,但它可能会变得混乱。
  接下来的改变是,我们将为每个不同类型的查询(每个都有自己的密钥)为每列存储一个单独的,不同的盲目索引。例如:
  需要忽略空格的不区分大小写的查找?
  存储盲目索引preg_replace('/[^a-z]/', '', strtolower($value))。
  需要查询他们姓氏的第一个字母?
  存储盲目索引strtolower(mb_substr($lastName, 0, 1, $locale))。
  需要匹配“这个字母,并在那个字母结束”?
  存储盲目索引strtolower($string[0] . $string[-1])。
  需要查询他们姓氏的前三个字母和他们的名字的第一个字母?
  你猜对了!基于部分数据来构建另一个索引。
  每个指标都需要有一个独特的关键,应该大力刺激明文隐含的盲目索引,将真正的明文值泄露给一个犯罪分子,用谜语填字游戏。只能为严重的业务需求创建索引,并且积极地记录对应用程序这些部分的访问。
  时间与存储的交易
  到目前为止,所有设计命题都赞成允许开发人员仔细阅读SELECT查询,同时小化解密子例程的次数。一般来说,这样已经能做到让大多数人满意了。
  但是,有些情况下,如果意味着节省大量的磁盘空间,搜索查询中的轻微性能也可以接受。
  这里的技巧很简单:将您的盲目索引截断为16,32或64位,并将其视为 Bloom过滤器:
  如果查询中涉及到的盲目索引与给定行匹配,则数据可能是一个匹配项。
  您的应用程序代码将需要为每个候选行执行解密,然后仅提供实际匹配。
  如果查询中涉及的盲指数与给定行不匹配,则数据不匹配。
  如果您的数据库服务器终会更有效地存储它,也可能会将这些值从字符串转换为整数。