将一个事物的 HMAC 用作另一个 HMAC 的盐是否安全?

信息安全 匿名 hmac
2021-09-04 04:18:57

问题

我试图在接下来的几段中解释完整的场景。我认为这对于获得一些提出问题的背景很重要,所以请耐心等待,即使它是一堵文字墙。

我目前的任务是以安全的方式匿名数据。这个想法是用来HMAC(<string to anonymize>, key)匿名化数据,这样它就不能被逆转。例如,如果您有一个客户 ID ( CUST299128218),这将是 HMAC-ed,SECRET用作543a36dd07fe4a3fa4a2db202546eaaccaef71f871ebafe11de3b54784ba266e. 由于我们想要对匿名数据进行分析,因此相同的客户 ID 始终会产生相同的 HMAC 摘要是很重要的。所以我们不能丢弃密钥,因为我们需要用相同的密钥对未来的数据进行匿名化。

显然,钥匙需要存放在安全的地方,这样它就不会泄露出去。否则,知道客户 ID 的人可以轻松地在匿名数据中找到该特定客户。由于各种技术/组织原因,我们不能使用硬件安全模块来存储密钥。所以我看了一下 HashiCorp 的 Vault,它似乎很适合这个,因为它提供了一个REST API,你可以在其中给它一个明文,并使用以前存储的密钥返回这个明文的 HMAC。密钥永远不会离开 Vault,这比将密钥存储在匿名软件的某些配置属性中要好得多。

然而,我们谈论的是要匿名的大量数据集 (每天几十万到几百万),并且可以预见为每个数据集调用 Vault API(如果需要对多个项目进行匿名化,可能会多次调用)将导致大量开销,可能会使我们为此可用的基础设施不堪重负。

建议的解决方案

因此我有了这个想法:如果我使用一些固定的字符串(例如'customer_id_secret_bootstrap')并让 Vault 使用密钥在其上创建一个 HMAC 会怎样。然后我使用这个 HMAC 作为对数据进行匿名化的实际 HMAC 的密钥。在功能方面:

temp_key = CALL_VAULT('customer_id_secret_bootstrap')
anonymized_text = HMAC( <plaintext>, temp_key)

这样我只能调用一次 Vault 并将临时密钥保存在内存中。我应该始终从 Vault 中取回相同的临时密钥(因为它是 HMAC),但原始密钥(用于派生临时密钥)永远不会离开 Vault,并且当程序退出时,无法重新创建临时密钥无需访问 Vault。因此,通过这种方式,我可以确保密钥的安全性,同时不会对 Vault 进行一百万次调用。

问题

现在知道我到目前为止还不是安全专家,由于我不知道的原因,这可能是一个糟糕的主意。因此,我想在这里与您的专家一起运行此程序-您能告诉我这是一个好主意还是坏主意,如果这是一个坏主意,您能否提出一些替代方法来确保密钥的安全性并具有可扩展性?

更新/编辑

正如许多答案指出的那样,仅仅替换 ID 是不够的,因为还有其他字段可用于将信息关联到一小组人甚至一个人(例如时间戳非常适合此)。我们还通过删除或替换此类信息来解决此问题,以确保不会发生这种情况(我们有一个非常长的基于匿名标准的关于此类事情的清单)。我只是不想在这里介绍这些细节,因为这个问题已经很冗长了。

2个回答

正如Luc 指出的那样,您可能已经比大多数人做得更好。关心您的客户隐私,您值得享有盛誉!

所以我们这里有三个不同的系统,为了安全起见:

  1. 只需使用存储在某些配置中的密钥。
  2. 您的系统 - 将保险库与存储在配置中的密钥结合使用。
  3. 只是使用保险库。

#1 的问题很明显。任何有权访问您的系统的攻击者都可以窃取密钥,然后使用它在他们自己的计算机上暴力破解哈希值。那很糟。

使用#2,您可以获得更多安全性。必须有人闯入您的系统并窃取CALL_VAULT('customer_id_secret_bootstrap')这更难,因为他们必须从工作内存中而不是从磁盘中获取它。此外,它仅在系统运行时可用。所以它不会意外地结束备份等。但是获得秘密的攻击者可以在他们自己的系统上使用它来强制 HMAC 脱机。

这是#3更强的地方。获得您系统访问权限的攻击者无法窃取任何东西,因为密钥不会离开保险库。攻击者可以尝试通过调用保险库来破解您系统上客户 ID 的哈希值,但他们不能简单地窃取所有哈希值并尝试在自己家中的隐私中破解它们。

因此,虽然 #3 比 #2 更安全,但由您来判断额外的安全性是否值得付出代价(在降低性能等方面)。这取决于您的威胁模型以及此信息的安全性有多重要。

让我列出假设/情况:

  • 您有一个包含客户 ID 和每个客户的其他字段的大型数据库。
  • 您希望对此进行匿名化以运行分析。组织仍会知道原始客户 ID(您不会永久删除原始 ID),但进行分析的人不会。
  • 客户的其他字段也必须匿名。
  • 您在询问是否可以只使用匿名客户 ID 作为其他字段 HMAC 的键。

答案是否定的,这不安全。进行分析的人知道匿名客户 ID,并且可以在暴力破解其他字段时使用它。

另一种选择是为每个客户创建一个随机密钥,并将其与客户数据一起存储在数据库中。这意味着您不需要“保险库”或硬件安全模块:只需从中读取一些字节/dev/urandom并将其与客户数据一起存储。然后将其用作匿名其他字段的密钥。

我想数据库将如下所示:

+---------+------------+------------+-------------------+
| ID      | Name       | Money      | Anonymization key |
+---------+------------+------------+-------------------+
| CUST999 | Jon Jonson | 3.14159265 | b2aZSo2D9erqwanrf |
+---------+------------+------------+-------------------+

然后匿名:

customer = database.read();
anon = new Customer();
anon.ID = anonymize(customer.ID, customer.AnonymizationKey)
anon.Name = anonymize(customer.Name, customer.AnonymizationKey)
anon.Money = customer.Money //Assuming you don't want to anonymize every field.
print(anon)

anonymize(data, key)函数可以是您建议的 HMAC。但是,我认为Stephane 的评论非常好:他们提到使用慢速哈希来防止暴力破解。您可以使用密码存储算法(Bcrypt、Scrypt、Argon2 或 PBKDF2,无特定顺序)来使事情更安全。但是,由于您谈论了很多记录,我可以想象这是不可能的(或者只有低成本因素),但是您可以研究一下。

顺便说一句,很多人试图只对客户 ID(例如电话号码)进行哈希处理,以便营销部门可以直截了当地说它是匿名的,即使它是微不足道的暴力破解。这已经更好了,因为它涉及密钥。最重要的是,您正在考虑采取适当的措施来真正保密该密钥。为此+1!