密码安全字符串由36个字符的字母表?

时间:2017-06-15 21:11:58

标签: javascript node.js

我正在尝试生成一些令牌。它们必须是字母[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('');
}

但我很确定由于模数而无法正确分发。

2 个答案:

答案 0 :(得分:1)

让我们看看你在这里得到了什么:

36个字符的字母表,其中每个字符是使用模36一次随机选择的,实际上并不是均匀分布的。

您有几种选择:

1)选择一整系列字节来表示总字符串。这意味着,您需要选择足够的字节来表示0 - 36 ^ 26范围内的数字。这样,您的值将均匀分布(就加密提供程序所允许的而言)。

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

  • a)将8个字节解释为float并将结果乘以36
  • b)以大于26的幂2为模。然后,搜索低于2的幂的36的最大倍数,丢弃该值空间之外的任何值,并将有效值模数为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);
    }
});