【问题标题】:How to create a Firestore safe Document ID based on a string如何基于字符串创建 Firestore 安全文档 ID
【发布时间】:2019-05-24 08:55:40
【问题描述】:

在讨论了我关于 base64 对 Firestore ID 不安全的问题here 之后,我想知道如何将字符串编码为 Firestore“安全”文档 ID。

问题来了:

  • 我通过自定义身份验证服务登录用户。
  • 该服务提供的用户名可以包含/,而该用户名对于 Firestore 文档 ID 不安全,如声明的 here

我在另一个问题中询问了 base64,这是不安全的,因为它包含 /

那么,在不丢失外部服务提供的用户名熵的情况下,有什么方法可以安全地对该字符串进行编码。这意味着可能有一个用户名,如dimi/test1,另一个为dimitest1,因此仅删除字符不是一种选择。

此外,由于该服务具有可用的开放 API,并且我的服务通过 URL 公开文档 ID,因此我不希望通过我的应用程序 URL 公开其他服务用户名。

有什么建议吗?

【问题讨论】:

  • 请问您为什么要(作为用户)生成 ID? Firestore 已经有一个唯一的安全 ID 生成器和 Firebase 身份验证,其中任何一个都应该为您提供唯一的 ID 以满足您的需求。
  • @IgnacioBustos 我在队列中添加了我需要访问而无需查询的项目。因此,如果我总是可以构建相同的 id,我可以更快地访问它并且读/写更少。想象一下,这是一个 API 服务/函数,每天大约有 10 万次调用。这有帮助吗?
  • @IgnacioBustos 另外,这样我不会每次都插入一个重复的队列项。我从 API 调用中得到的输入是:USERNAME/WORKOUTID。
  • @IgnacioBustos 您也可以将其视为每个服务用户锻炼的映射。因此,即使该服务上的用户更新了信息,也会更新相同的队列项并在消费时发出通知
  • 哦,我明白了,为此调用 Firebase 非常昂贵。所以让我假设更多的事情,如果不是,请纠正我。所以'userID' (A) & 'workoutID' (B) 是已知的,不是吗?在这种情况下,您的新关系数据库可以是${userID}${workoutID},其中没有任何斜线/。如果您事先不知道 ID,我有另一种方法可以快速可靠地自己生成 ID,但出于安全原因,必须使用 Node.js。让我知道,我会把方法写给你。

标签: javascript firebase google-cloud-firestore


【解决方案1】:

使用 encodeURI() 后跟 SHA256。这会将文档 ID 限制为

Must be valid UTF-8 characters
Must be no longer than 1,500 bytes
Cannot contain a forward slash (/)
Cannot solely consist of a single period (.) or double periods (..)
Cannot match the regular expression __.*__

encodeURI 用于有效的 UTF-8 字符。

SHA256 是 256 位(或 32 字节)的固定长度,因此不超过 1,500 字节的限制。

根据https://stackoverflow.com/a/12618366/3073280,SHA256 字符为 [a-fA-F0-9]。

最后,您提到它需要熵。 SHA256 扩散良好。

【讨论】:

  • 每次调用的 1m 条记录。不会 SHa256 需要更多 CPU,为什么不需要 Base58?
  • 关于 URF-8 的 `new TextEncoder('utf-8').encode(message);` 怎么样?
  • 为什么不简单地使用 encodeURIComponent('dimi/test1')?
  • @harsh989 一些字符在 encodeURIComponent 中没有转义。
【解决方案2】:

编辑

为了快速将字符串转换为唯一ID,请改用crypto.createHash()。对于给定的字符串输入,结果将是相同的。

您可以使用 MD5 或 SHA256,因为两者都需要相同的时间,计算 100 万个唯一 ID 平均需要 2.2 秒。

代码如下:

const crypto = require('crypto');

function uniqueId(string, algorithm = 'md5') {
  return crypto.createHash(algorithm).update(string).digest('hex');
}

console.log('started');
console.time('generateIDsMD5')
for (let i = 0; i < 1000000; i++) {
  uniqueId('a string ' + i);
}
console.timeEnd('generateIDsMD5');

console.time('generateIDsSHA256')
for (let i = 0; i < 1000000; i++) {
  uniqueId('a string ' + i, 'sha256');
}
console.timeEnd('generateIDsSHA256');

// For instance, It will take around 2.2s average
// to generate 1Million Unique IDs with MD5 or SHA256 encryption

console.log('MD5 string ', uniqueId('a string ' + 1));
console.log('MD5 sameString ', uniqueId('a string ' + 2));
console.log('MD5 sameString ', uniqueId('a string ' + 2));
console.log('SHA256 string ', uniqueId('a string ' + 1, 'sha256'));
console.log('SHA256 sameString ', uniqueId('a string ' + 2, 'sha256'));
console.log('SHA256 sameString ', uniqueId('a string ' + 2, 'sha256'));
console.log('finished');

以前的答案

我改编了 Firebase 中的代码,并为您提供了一些自定义测试,直接在您的 node.js 上可用。 100 万个 ID 最多需要 3 秒,而 100.000 个 ID 只需 300 毫秒,这是您考虑的日常使用方法。

这使用crypto 如果在 node.js 环境中运行被认为是非常安全的。

这里是使用示例包装的函数:

const crypto = require('crypto');

function autoId(bytesLength) {
  const chars =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let autoId = '';
  while (autoId.length < bytesLength) {
    const bytes = crypto.randomBytes(40);
    bytes.forEach(b => {
      // Length of `chars` is 62. We only take bytes between 0 and 62*4-1
      // (both inclusive). The value is then evenly mapped to indices of `char`
      // via a modulo operation.
      const maxValue = 62 * 4 - 1;
      if (autoId.length < bytesLength && b <= maxValue) {
        autoId += chars.charAt(b % 62);
      }
    });
  }
  return autoId;
}

console.log('started');
console.time('generateIDs')
for (let i = 0; i < 1000000; i++) {
  autoId(20);
}
console.timeEnd('generateIDs');
// For instance, It will take around 3s average
// to generate 1 Million Unique IDs with 20 bytes length

console.log('example 20bytes ', autoId(20));
console.log('example 40bytes ', autoId(40));
console.log('example 60bytes ', autoId(60));
console.log('finished');

只需使用node thisfile.js,您就会看到结果。

由于firebase主要是开源的,我们可以在这里找到node.js中使用的官方uniqueId生成器来生成ID:https://github.com/googleapis/nodejs-firestore/blob/4f4574afaa8cf817d06b5965492791c2eff01ed5/dev/src/util.ts#L52

重要

如果您要加入 2 个 ID,请不要使用任何斜线 /,因为您知道这是不允许的,而是使用下划线 _ 或什么都不使用,因为您可以控制 ID 的长度,因此您应该知道如何相应地拆分 ID(例如 40 个字节包含 2 个 20 字节的 ID)。

文档 ID 中的 firestore 限制为 1500 字节,因此您有很多可玩的地方。

更多信息:https://firebase.google.com/docs/firestore/quotas#limits

【讨论】:

  • 我知道你花了一些时间来写这篇文章,为此我肯定会奖励你。但是(并且也回答了评论),我从其他人(API)那里得到了 ID。 ${userID}${workoutID} 将不起作用,因为稍后可能会暴露 URI。可以使用那些(来自共享 URI)。所以我想让那些人说:“Base58?”加密货币?所以 2 个输入(USERID、WorkoutID)应该提供一个唯一的 Firestore ID。我认为(据我了解)这个答案不正确吗?我感谢您花时间教我如何创建 IDS(并将使用它),但这些都是给定的:-/
  • 可能会更有帮助:github.com/jimmykane/quantified-self/blob/master/functions/src/… 看看如何插入队列项。
  • 好的,我明白了,所以您希望generateIDFromParts 在输入相同的情况下生成相同的字符串,无论您使用相同的输入再次调用该函数多少次?
  • 正确!现在我base58编码。根据我的研究应该没问题,但我害怕碰撞等。我自己的答案也被否决了,所以如果你明白我的意思,它就会开始闻起来。
  • 你可以传递两个像uniqueId(`${userID}${workoutID}`)这样连接的ID来得到相同的结果
【解决方案3】:

我使用了 Base58,这是我可以研究的最安全的

【讨论】:

  • 为什么不使用哈希,例如SHA256?它是固定长度的,其字符池为[a-fA-F0-9]stackoverflow.com/a/12618366,同时保留熵。
猜你喜欢
  • 2019-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-16
  • 2019-08-26
  • 1970-01-01
  • 2022-06-17
  • 2011-01-10
相关资源
最近更新 更多