生成加密安全 ID,而不是顺序标识符/自动增量。

8

我有一个难题已经困扰了我一段时间,尽管看起来应该已经有人做过了。我的问题是需要用密码学安全的(即非连续的!)id替换顺序AUTO_INCREMENT(或等效的)主键,但同时我想保持顺序主键的性能优势:保证未使用的下一个ID、可聚簇性等。

一种简单的方法似乎是实现一个加密伪随机置换生成器,将2^N空间唯一映射到2^N而无碰撞,并具有初始化向量(IV)。

虽然这可以在外部实现,但是这需要存储和原子访问状态(排列位置或上一个ID),这意味着在外部实现会极其低效(它相当于对每个INSERT运行后续 UPDATE table SET crypto_id = FN_CRYPTO(autoincrement_id) WHERE autoincrement_id=LAST_INSERT_ID() )。

您知道是否有任何商业用途中的数据库中实现了上述描述的内容吗?


性能:https://www.mssqltips.com/sqlservertip/5105/sql-server-performance-comparison-int-versus-guid/ - DaFi4
碰撞: https://dev59.com/jHVC5IYBdhLWcg3wykUt - DaFi4
@DaFi4 谢谢,但我找不到任何论文描述和承诺这些GUID的目的和实现,或者底层的随机生成器等(请参见Behrooz在碰撞下的评论)...所以当涉及到安全问题时,我发现依赖传闻有点冒险。其他数据库实现的GUID也同样不一致。 - Dinu
NEWID似乎是CoCreateGuid的一个包装器,至少从Yukon开始是这样的:https://blogs.msdn.microsoft.com/sqlprogrammability/2006/03/23/newsequentialid-histrorybenefits-and-implementation/ - DaFi4
实际上,624/4=158,因为它的输出是32位,所以要运行4次迭代才能得到128位。 - Dinu
显示剩余12条评论
2个回答

1

While this could be implemented externally, this does need to store and atomically access state (the permutation position or last id), which means implementing externally would be grossly inefficient (it's the equivalent of running a subsequent

 UPDATE table SET crypto_id = FN_CRYPTO(autoincrement_id) 
 WHERE autoincrement_id=LAST_INSERT_ID()
您可以使用生成/虚拟列来避免为每个插入运行建议的UPDATE操作:
-- pseudocode
CREATE TABLE tab(
   autoincrement_id INT AUTO_INCREMENT,
   crypto_id <type> GENERATED ALWAYS AS (FN_CRYPTO(autoincrement_id)) STORED
);

-- SQL Server example, SHA function is an example and should be replaced
CREATE TABLE tab(
 autoincrement_id INT IDENTITY(1,1),
 crypto_id AS (HASHBYTES('SHA2_256',CAST(autoincrement_id AS NVARCHAR(MAX))))     PERSISTED
);

db<>fiddle演示


更多信息:


如果您使用SHA,请不要忘记将秘密盐连接到autoincrement_id;或者,您可以使用例如AES128来使用秘密密码和IV加密autoincrement_id
另外值得注意的是:任何具有访问表DDL权限的DB用户都将访问您的秘密盐/密钥/iv。如果您担心这一点,您可以使用参数化存储过程,例如FN_CRYPTO(id,key,iv),并随每个插入发送它们。
为了在应用程序端检索crypto_id而无需进行后续查询,您需要复制加密函数到应用程序端以在返回的autoincrement_id上运行。注意:如果将autoincrement_id用作AES128的字节数组,请非常小心关于字节序,它可能与DB和应用程序端不同。唯一的替代方法是使用mssql的OUTPUT语法,但这仅适用于mssql,并且它需要运行ExecuteScalar API而不是ExecuteNonQuery

也许基于身份ID在应用端复制生成函数可以得到加密ID? 我会使用AES128而不是哈希,因为它可以保证不发生碰撞,并且我在其他地方看到了其实现,但大多数RDB都可以做到。 - Dinu
1
我认为我知道这个问题的答案:使用OUTPUT与SCHEMABINDING组合:https://stackoverflow.com/questions/6354894/get-sql-computed-column-inserted-value - DaFi4
(此外,请务必对此答案进行性能测试,如果我记得没错的话,更高级的加密功能在大规模情况下执行较慢。) - DaFi4
就像我说的那样,“OUTPUT”的问题在于它需要使用“ExecuteScalar”API而不是“ExecuteNonQuery”...我们确实在DB API之上使用ORM/DBAL,因此这基本上需要放弃或重写ORM/DBAL(使用存储过程也是如此)。因此,我们必须坚持使用常规的INSERT/UPDATE/SELECT操作。关于加密函数,SHAx / AESx应该对单个块运行非常快(除了一些失败的实现);使用CSPRNG(密码随机数)非常慢,使用常规PRNG通常是不安全的。 - Dinu
1
@Lukasz Szozda,我接受了这个答案,我在研究了很多后没有找到更好的实现方法。我还提出了一个编辑建议,增加有关SHA盐、AES替代方案以及如何在应用程序端检索加密ID的更多信息。 - Dinu
显示剩余5条评论

-1

只是一个想法...数据库本身安全吗?如果是的话,你可以考虑创建一个“密钥池”表格,其中包含一个伪随机密钥列表和每个密钥的“状态”列。然后在需要时分配下一个密钥。密钥池可以在空闲时间或者根据触发器自动填充,如果可用密钥数量低于设定阈值。

同样,这种方法依赖于能够保护密钥池表的安全性,但它确保分配的密钥是随机且唯一的。

此外,您需要确保不会出现并发问题,但这可以通过存储过程来完成,并且应该比按需生成安全ID更快。


1
不,搜索“密钥池”,然后更新该密钥池并不比即时生成加密ID更快。您正在建议一种比我已列为无法接受的低效(插入/更新)还要更低效的方法。 - Dinu
我同意整体而言,密钥池的效率较低。但是我认为在空闲时间可以重新填充密钥池,这样开销就可以分配到不那么关键的时间段。当然,我不知道你的应用程序的具体情况,所以我理解这可能对你来说不是一个实际的选择。 - daShier
在您拉取每个ID后,关键池仍需要更新以标记其已消失。没有任何理由创建关键池,关键生成不慢。使用相应的互斥锁进行重复搜索/插入/更新的数据库操作是缓慢的。 - Dinu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接