我正在尝试生成一些令牌。它们必须是字母[a-z0-9]
中的26个字符。
我找到的最接近的解决方案是this answer的第2部分,但字符串不会均匀分布。
如果我的字母表的长度为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('');
}
但我很确定由于模数而无法正确分发。
答案 0 :(得分:1)
让我们看看你在这里得到了什么:
36个字符的字母表,其中每个字符是使用模36一次随机选择的,实际上并不是均匀分布的。
您有几种选择:
1)选择一整系列字节来表示总字符串。这意味着,您需要选择足够的字节来表示0 - 36 ^ 26范围内的数字。这样,您的值将均匀分布(就加密提供程序所允许的而言)。
2)如果你坚持每次选择一个数字,你想确保它的值将被均匀分配,使用模36不能完成工作,正如你所正确的假设。在这种情况下,您可以
对于2a),分布并不完全均匀,但非常接近机会,假设加密提供者是公平的。
对于2b),分布是均匀的。但是,当丢弃不需要的结果时,这将由更高(尤其是不可预测的)运行时支付。当然,你可以统计计算运行时间,但最坏的情况是无限的,如果你的RNG永远产生无效结果(这非常非常不可能,但理论上可行)。
我的建议是2a)。取一系列字节,将它们解释为浮点值,并将结果乘以36.
答案 1 :(得分:0)
这是我对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),所以我认为这应该足够有效。
很难对这样的事情进行单元测试,但这传递了:
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);
}
});