我已经编写了一个简单的媒体客户端/服务器,我希望生成一个非显而易见的时间值,我将每个命令从客户端发送到服务器。时间戳将包含相当多的数据(纳秒级分辨率,即使由于现代操作系统中定时器采样的限制而不能真正准确),等等。
我正在尝试(在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的唯一性仍将得到保证。
然而,我不知道:
我意识到我可以使用来自libssl
或类似内容的128位随机哈希,但我希望使用相同salt值的远程服务器能够将哈希时间戳转换回其真实值。
谢谢。
答案 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的无符号溢出行为(取任意精度结果的低位)。