为什么采用散列的 mod 的加盐散列会导致非常不均匀的分布?

Why does taking the salted hash of the mod of a hash result in a very non-uniform distribution?

我有一百万个随机生成的唯一 ID。

如果我这样做:

result = int(hash(id + 'some_salt')) % 1000

然后这似乎导致 ID 均匀分布到 0 到 999 之间的某个整数,每个整数都有大约 1000 个 ID 映射到它。

如果我现在对此添加一些盐并再次进行散列:

x = int(hash(id)) % 1000
result = int(hash(str(x) + 'some_salt') % 1000)

那么最终的分布是完全不均匀的。对于每个 ID,结果当然在 [0,999] 范围内,但此范围内的一些整数映射到它们的 ID 为零,而另一些则有数千个。

为什么这会导致值的分布非常不均匀?

对于我的百万个 ID 和任何给定的 salt,我如何调整它以使整数均匀分布在 [0,999] 范围内?我想保留将可能非常大的输入 space 减少到更小的 space (例如大小 1000)的中间步骤。

我正在使用 SHA-256 哈希。

下面是一些 Python 代码,它演示了非常不均匀的结果:

import numpy as np
import hashlib

OUTPUT_RANGE_SIZE = 1000

unique_ids = xrange(1000000) # sequential here, but could be any kind of unique ids
frequencies = np.zeros(OUTPUT_RANGE_SIZE, dtype='int')

for idx in xrange(len(unique_ids)):
    id = unique_ids[idx]
    hash_mod = int(hashlib.sha256(str(id)).hexdigest(), 16) % 1000
    result = int(hashlib.sha256(str(hash_mod) + 'some_salt').hexdigest(), 16) % OUTPUT_RANGE_SIZE
    frequencies[result] = frequencies[result] + 1

print frequencies

通过在您的第一个哈希运算中应用模运算符,您已确保该阶段只有 1000 个唯一输出,无论您有多少个唯一数字作为输入。当您再次对它进行散列和取模时,其中一些散列可能会映射到相同的存储桶;因此,存储桶中的值数量将大约是散列到该存储桶 ID 的值数量的 1000 倍。您可以通过将频率数组中的值除以 1000 来看到这一点:

[1, 0, 2, 1, 0, 0, 0, ...]

如果您从第一步中删除模运算符,您在第二步中的输出值将按预期均匀分布。

强制附言:不要发明自己的密码系统。如果这是安全关键,请了解最佳实践并加以实施。