从36个字符的字母表密码安全的string?

我正在尝试生成一些令牌。 他们必须是字母表中的26个字符[a-z0-9]

我find的最接近的解决scheme是从这个答案的第2部分,但string将不会均匀分布。

如果我的字母是一个2的长度的权力,这不会是那么难,但现在,我不知道如何正确地做到这一点。


具体来说,这是我到目前为止:

 export function createSessionId() { const len = 26; let bytes = new Crypto.randomBytes(len); let result = new Array(len); const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'; for(let i=0; i<len; ++i) { result[i] = alphabet[bytes[i]%alphabet.length]; } return result.join(''); } 

但是我敢肯定,由于模数的原因,不能正确分配。

让我们看看你在这里:

一个由36个字符组成的36个字符的string,其中每个字符都是用模36一次随机select的,实际上并不是均匀分布的。

你有几个select:

1)select一系列字节来表示总string。 这意味着,您需要select足够的字节来表示0 – 36 ^ 26范围内的数字。 这样,你的价值将会平均分配(只要encryption提供者允许)。

2)如果你坚持每次select一个数字,你想确保它的价值将被均匀分配,使用模36不做你的工作,正如你正确的假设。 在这种情况下,你也可以

  • a)将8个字节解释为浮点数并将结果乘以36
  • b)用大于26的幂来模数。然后,search比2的幂低的36的最大倍数,丢弃该值空间以外的任何值并以有效值为模36。

对于2a)来说,分布并不是完全平坦的,但是非常接近偶然,假定encryption提供者是公平的。

对于2b),分配是均匀的。 但是,当抛弃不需要的结果时,这将由更高的(特别是不可预知的)运行时支付。 当然,你可以统计计算运行时间,但最坏的情况是无限的,如果你的RNG永远产生无效的结果(这是非常非常不可能,但在理论上是可能的)。

我的build议是2a)。 采取一系列的字节,将它们解释为浮点值并将结果乘以36。

这是我执行Psi的答案,2b。

 export function createSessionId() { const len = 26; let bytes = Crypto.randomBytes(30); // a few extra in case we're unlucky let result = new Array(len); const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'; let i =0; let j = 0; for(;;) { if(i >= bytes.length) { bytes = Crypto.randomBytes(((len-j)*1.2)|0); // we got unlucky, gather up some more entropy i = 0; } let value = bytes[i++]; if(value >= 252) { // we need a multiple of 36 for an even distribution continue; } result[j++] = alphabet[value % alphabet.length]; if(j >= len) { break; } } return result.join(''); } 

有不到2%的机会需要重新注册(4/255),所以我认为这应该是有效的。

很难unit testing这样的事情,但这通过:

 test(createSessionId.name, () => { let ids = new Set(); let dist = Array.apply(null,new Array(26)).map(() => ({})); for(let i=0; i<1500; ++i) { let id = createSessionId(); if(ids.has(id)) { throw new Error(`Not unique`); } ids.add(id); for(let j=0; j<id.length; ++j) { dist[j][id[j]] = true; } } for(let i=0; i<26; ++i) { expect(Object.keys(dist[i]).length).toEqual(36); } });