生成(非常)大的非重复整数序列而无需预先混洗

时间:2016-08-29 22:04:14

标签: c linux random hash uniqueidentifier

背景

我已经编写了一个简单的媒体客户端/服务器,我希望生成一个非显而易见的时间值,我将每个命令从客户端发送到服务器。时间戳将包含相当多的数据(纳秒级分辨率,即使由于现代操作系统中定时器采样的限制而不能真正准确),等等。

我正在尝试(在Linux上,在C中),生成一对一的n位值序列(假设现在数据存储在128位int-array-of-int元素中)没有重叠/碰撞的值。然后,我将伪随机的128位值/数字作为“盐”,将其应用于时间戳,然后开始向服务器发送命令,增加预先盐化/预先散列的值。

时间戳大小太大的原因是时间戳可能需要适应非常大的持续时间。

问题

我怎样才能用初始盐值完成这样的序列(非碰撞)? The best approach that sounds along the lines of my goal is from this post, which notes

  

如果选项1对您来说不够“随机”,请使用所述的CRC-32哈希值   全局(32位)计数器。两者之间存在一对一的映射(双射)   N位整数及其CRC-N的唯一性仍将得到保证。

然而,我不知道:

  • 如果可以(有效)扩展到128位数据。
  • 如果通过某种类型的加法/乘法 - 盐值来提供序列的初始种子会破坏它或引入冲突。

的后续

我意识到我可以使用来自libssl或类似内容的128位随机哈希,但我希望使用相同salt值的远程服务器能够将哈希时间戳转换回其真实值。

谢谢。

3 个答案:

答案 0 :(得分:3)

简短的回答是加密。使用一组128位值将它们输入AES并获得一组不同的128位值。由于加密是可逆的,因此对于具有固定密钥的唯一输入,输出保证是唯一的。

加密是输入值与输出值的可逆一对一映射,每组都是另一组的完全排列。

由于您可能不会重复输入,因此ECB模式可能已足够,除非您需要更高程度的安全性。如果重复使用相同的输入,ECB模式很容易受到攻击,这似乎不是这种情况。

对于短于128位的输入,则使用固定填充方法使其长度合适。只要输入的唯一性不受影响,填充就可以相当灵活。在任一端(或在内部字段的开头)零填充可能就足够了。

我不知道您的详细要求,所以请随时修改我的建议。

答案 1 :(得分:3)

您可以使用线性同余生成器。使用正确的参数,可以保证产生具有完整周期(即无冲突)的非重复序列[唯一]序列。

这是random(3)TYPE_0模式中使用的内容。我将其调整为完整unsigned int范围,种子可以是任意unsigned int(请参阅下面的示例代码)。

我相信它可以扩展到64位或128位。我要查看:https://en.wikipedia.org/wiki/Linear_congruential_generator,了解有关参数的约束,以防止碰撞和良好的随机性。

遵循维基页面指南,您可以生成一个可以将任何 128位值作为种子的指令,并且在生成所有可能的128位数之前不会重复。

您可能需要编写一个程序来生成合适的参数对,然后测试它们以获得最佳的"随机性。这将是一次性操作。

一旦获得它们,只需将这些参数插入实际应用中的等式中即可。

这是我在寻找类似内容时一直在玩的一些代码:

// _prngstd -- get random number
static inline u32
_prngstd(prng_p prng)
{
    long rhs;
    u32 lhs;

    // NOTE: random is faster and has a _long_ period, but it _only_ produces
    // positive integers but jrand48 produces positive _and_ negative
#if 0
    rhs = jrand48(btc->btc_seed);
    lhs = rhs;
#endif

    // this has collisions
#if 0
    rhs = rand();
    PRNG_FLIP;
#endif

    // this has collisions because it defaults to TYPE_3
#if 0
    rhs = random();
    PRNG_FLIP;
#endif

    // this is random in TYPE_0 (linear congruential) mode
#if 0
    prng->prng_state = ((prng->prng_state * 1103515245) + 12345) & 0x7fffffff;
    rhs = prng->prng_state;
    PRNG_FLIP;
#endif

    // this is random in TYPE_0 (linear congruential) mode with the mask
    // removed to get full range numbers
    // this does _not_ produce overlaps
#if 1
    prng->prng_state = ((prng->prng_state * 1103515245) + 12345);
    rhs = prng->prng_state;
    lhs = rhs;
#endif

    return lhs;
}

答案 2 :(得分:1)

在线性同余生成器和加密函数之间的某处,有些哈希可以将线性计数转换为可通过的伪随机数。

如果碰巧有128位整数类型(例如,在为64位目标构建时在GCC中为__int128),或者愿意手动实现这么长的乘法,那么你可以扩展关于SplitMix64中使用的构造。我做了一个相当肤浅的搜索,并提出了以下参数:

uint128_t mix(uint128_t x) {
    uint128_t m0 = (uint128_t)0xecfb1b9bc1f0564f << 64
                 | 0xc68dd22b9302d18d;
    uint128_t m1 = (uint128_t)0x4a4cf0348b717188 << 64
                 | 0xe2aead7d60f8a0df;
    x ^= x >> 59;
    x *= m0;
    x ^= x >> 60;
    x *= m1;
    x ^= x >> 84;
    return x;
}

及其逆:

uint128_t unmix(uint128_t x) {
    uint128_t im0 = (uint128_t)0x367ce11aef44b547 << 64
                  | 0x424b0c012b51d945;
    uint128_t im1 = (uint128_t)0xef0323293e8f059d << 64
                  | 0x351690f213b31b1f;
    x ^= x >> 84;
    x *= im1;
    x ^= x >> 60 ^ x >> (2 * 60);
    x *= im0;
    x ^= x >> 59 ^ x >> (2 * 59);
    return x;
}

我不确定你是否想要一个随机序列,或者一种混淆任意时间戳的方法(因为你说你想要解码它们必须比线性计数器更有趣的值),但是一个派生自另一个就够了:

uint128_t encode(uint128_t time, uint128_t salt) {
    return mix((time + 1) * salt);
}

uint128_t generate(uint128_t salt) {
    static uint128_t t = 0;
    return encode(t++, salt);
}

static uint128_t inv(uint128_t d) {
    uint128_t i = d;

    while (i * d != 1) {
        i *= 2 - i * d;
    }
    return i;
}

uint128_t decode(uint128_t etime, uint128_t salt) {
    return unmix(etime) * inv(salt) - 1;
}

注意salt选择2个 127 序列中的一个非重复128位值(我们丢失一位因为salt必须是奇数),但是(2 128 )!可能已经生成的序列。在其他地方,我正在寻求扩展参数化以便可以访问更多这些序列,但我开始使用上述方法来增加序列的随机性,以隐藏参数可能选择不那么随机的任何问题(但可证明是不同的序列。

显然uint128_t不是标准类型,所以我的答案不是C,但你可以使用bignumber库或编译器扩展来使算术工作。为清楚起见,我依赖于编译器扩展。所有操作都依赖于类似C的无符号溢出行为(取任意精度结果的低位)。