【问题标题】:Constant time random number within range范围内的恒定时间随机数
【发布时间】:2017-03-17 17:29:56
【问题描述】:

我有一个以恒定时间运行的随机数生成器。

这个函数的原型如下:

uint8_t rand();

我想要做的是创建一个随机返回 uint8_t 的函数,使得输出介于 0 和 max 之间,其中 max 是要返回的最大数字。这种函数的原型是:

uint8_t randi(uint8_t max);

有在线算法可以做到这一点,并且在堆栈溢出时。例如,https://stackoverflow.com/a/6852396/1444313。但我的问题是我想在恒定时间内实现这一目标。我找不到任何可以在恒定时间内执行此操作的方法。

此外,我在没有硬件划分的 ARM Cortex-m0 上运行,因此无法使用 % 运算符。

有人对我如何在恒定时间内实现这一目标有任何建议或指示吗?

谢谢

【问题讨论】:

  • 我看不出问题,任何远程合理的定长整数软件除法实现都将具有恒定的时间复杂度。常数因子可能比替代因子高一点,其中最简单的解决方案是反转过程并乘以max,然后除以常数MAX(如果需要,以 uint64_t 精度)。优点是,在最坏的情况下,编译器可以轻松地将除以常数优化为直接的乘法/移位/加法序列,或者如果您不信任优化器,则可以手动优化。
  • 强制 xkcd 链接:xkcd.com/221
  • @doynax 我不确定我是否从你的解释中理解了你的方法,如果你认为这可行,你能写一个答案吗?谢谢。
  • “我在没有硬件划分的 ARM Cortex-m0 上运行,因此无法使用 % 运算符。”暗示/ 也不可用(由于非恒定时间)。是这样吗?
  • 我怀疑答案是不可能的,但可能会有非常小的时间差异。这变成了一个问题,您需要绝对意义上的恒定时间还是实际意义上的几乎恒定时间?只是一些奇怪的随机想法。

标签: c arm cortex-m thumb


【解决方案1】:

从最严格的意义上说,这些要求是不可能满足的。只要您的 RNG 以二进制形式生成均匀分布,就无法将其映射到非二次幂范围而不丢弃某些结果或具有不完美的分布。

您可以使缺陷变得微不足道,也可以使丢弃情况变得微不足道。由于一个罕见的丢弃表示它发生时的意外时间异常(并且通过使其更罕见只会让它更令人惊讶)考虑this solution,它是恒定时间并且可以通过这种方式适应您特定的rand()函数:

int n = max + 1;
int r = n / 2;
for (int i = 0; i < 6; ++i) {
    r = (rand() * n + r) >> 8;
}
return r;

这会提取 48 个随机位作为 0 和 1 之间的分数,并将其乘以 n,仅保留不大于 max 的整数部分。偏向于某些价值相对于其他价值的偏好不到万亿分之一。如果您对此不满意,则可以将 6 更改为更大的值。

这种偏差意味着,如果您每秒抽取 1000 个随机数,则需要 30 多年才能看到一个结果出现的次数多于应有的次数,而统计噪声仍然会淹没它。

您可以通过将 rand() 替换为在一次调用中产生更多随机位并且只迭代一次或两次的东西来加快速度。

【讨论】:

    【解决方案2】:

    为了帮助澄清 OP 的困境,以下是 2 个不符合 OP 目标的简单解决方案。

    有什么作用:

    非均匀分布,除非 max +1 是 2 的幂。

    uint8_t randi_fail1(uint8_t max) {
      return rand()%(max + 1);
    }
    

    均匀分布,但时间不均匀。

    uint8_t randi_fail2(uint8_t max) {
      unsigned n = (256/(max+1))*(max+1);
      unsigned r;
      do {
        r = rand();  // 0 - 255
      } while (r >= n);
      return r/(max+1);
    }
    

    【讨论】:

    • 我之前遇到过非均匀分布问题。很容易掉入陷阱,但要确保分布 100% 均匀,却出奇的棘手。
    【解决方案3】:

    这里有一个满足您要求的解决方案,但可能不喜欢!

    制作 255 个包含 256 或 65536 个随机值的数组,称之为 uint8_t randval[255][256];

    现在每个randval[N][M] &lt;= N;换句话说,子数组专门用于 randi max 参数。

    uint8_t randi(uint8_t max)
    {
        return (max == 0u) ? 0u : randval[max - 1][rand()];
    }
    

    请注意,randval 可以在编译时构建,也可以按照@Realtime Rik 的建议在后台更新。当然,如果在编译时构造,也可以是const,存储在flash中。

    【讨论】:

    • 您好,感谢您的回复。我在 ARM Cortex-m0 上,它有 4k 内存和 32k 闪存,我不认为我可以把它放在那里!
    【解决方案4】:

    输入和输出都是 8 位字节,那么为什么不获取随机值,乘以“最大值”,然后右移 8 以除以 256?我这样做是为了通过获取 0-25 的值(8 位随机,乘以 26,取最高字节)来选择一个随机字母。

    【讨论】:

      【解决方案5】:

      此处基于表格的答案确保了在理想硬件上的恒定时间执行,但在现实生活中,它被内存访问定时的及时性和访问数据依赖(randval[..][rand()],这将随时间变化由于 CPU 级缓存):https://stackoverflow.com/a/42867281

      这里的统一分发建议是要走的路:https://stackoverflow.com/a/42865274 通常你想要的东西是 constant time 在安全方面,只是你的代码没有 data-dependent 代码路径 - 内存访问和指令分支 - 而不是时间实际上是恒定 em>。

      为了保护边界不被暴露,您可以提供一个上限(例如 64 位)并使用类似于 FIPS 模块中的 constant_time_select_int 的技术(不知道为什么 @987654330 中的常规加密代码@ 不使用恒定时间实现)来获取想要的样本 - 但请注意,在大多数情况下,被认为是敏感的仅仅是随机数生成器返回的值,而不是范围:https://github.com/google/boringssl/blob/master/crypto/fipsmodule/bn/cmp.c#L77

      有关更多灵感,请参阅boringssl 中的随机数生成器/包装器函数: https://github.com/google/boringssl/blob/master/crypto/fipsmodule/bn/random.c 和来自 OCaml 库 eqaf(请注意,后者专门用于处理单个字节,因此比较函数 compare(a,b){ return (a - b) } 不是为处理 a=0, b=INT_MIN 的情况而设计的):https://github.com/mirage/eqaf/blob/master/lib/eqaf.ml#L111-L124

      【讨论】:

        猜你喜欢
        • 2017-07-13
        • 1970-01-01
        • 2011-12-16
        • 2015-05-25
        • 2020-07-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多