【问题标题】:How to generate a random number based on a byte array?如何根据字节数组生成随机数?
【发布时间】:2015-03-18 12:29:53
【问题描述】:

假设我有一个来自安全 PRNG 的字节数组,并且我需要使用该数据生成一个介于 1 和 10 之间的数字,我该如何正确地做到这一点?

【问题讨论】:

  • 别误会,答案是by writing a piece of code. :-)
  • 顺便说一句,你在找modulus operator吗?
  • @SouravGhosh 是的,可以使用模数来完成,如果您将评论作为我接受的答案。
  • 如果您选择%,那么您可能还想了解modulo bias
  • 我个人会查找一段代码(通常称为getInt(int max))并将其整合。否则很容易产生偏见。相关JavaCard code。注意备注。

标签: c random cryptography prng


【解决方案1】:

把数组想象成一个大的无符号整数。那么答案很简单:

(Big_Number % 10) + 1

因此,所需要的只是一种找到大整数模数 10 的方法。使用modular exponentiation

#include <limits.h>
#include <stdlib.h>

int ArrayMod10(const unsigned char *a, size_t n) {
  int mod10 = 0;
  int base = (UCHAR_MAX + 1) % 10;
  for (size_t i = n; i-- > 0;  ) {
    mod10 = (base*mod10 + a[i]) % 10;
    base = (base * base) % 10;
  }
  return mod10;
}

void test10(size_t n) {
  unsigned char a[n];

  // fill array with your secure PRNG
  for (size_t i = 0; i<n; i++) a[i] = rand();

  return ArrayMod10(a, n) + 1;
}

会有轻微的偏差,因为256^n 不是10 的幂。对于较大的n,其重要性会迅速下降。

未经测试的代码:检测是否出现有偏差的结果。调用代码可以使用新的a 数组值重复调用此函数,以在发生偏差的极少数情况下获得无偏差的结果。

int ArrayMod10BiasDetect(const unsigned char *a, size_t n, bool *biasptr) {
  bool bias = true;
  int mod10 = 0;
  int base = (UCHAR_MAX + 1) % 10;  // Note base is usually 6: 256%10, 65536%10, etc.
  for (size_t i = n; i-- > 0;  ) {
    mod10 = (base*mod10 + a[i]) % 10;
    if (n > 0) {
      if (a[i] < UCHAR_MAX) bias = false;
    } else {
      if (a[i] < UCHAR_MAX + 1 - base) bias = false;
    }
    base = (base * base) % 10;
  }
  *biaseptr = bias;
  return mod10;
}

【讨论】:

  • @Maarten Bodewes 鉴于 OP 使用的是“安全 PRNG”,当然长度合理,所以同意,实际上不需要考虑偏见。
  • 我想使用这个代码,但实际上我不需要在 1 到 10 之间选择,它需要是可变的。我可以安全地将代码中出现的 1 和 10 替换为上限/下限变量吗?
  • @Muis 差不多。让ArrayModX() 返回范围 [0...X-1],然后用X = upper-lower+1 添加下限。如果数字超出范围 255,请使用比 unsigned char *a 更大的对象,例如 uint32_t *a,然后可以替换“10”。如果您的“10”大于INT_MAX,则会出现一些额外的问题。 IOW 说“具有上限/下限变量的代码”太含糊了。最好说出数字的类型或范围,例如:0 &lt;= lower_bound &lt;= upper_bound &lt;= INT_MAX 或类似的东西。
  • 这个答案仍然有偏见,实际上不需要偏见。
  • @Maarten Bodewes ArrayMod10BiasDetect() 检测并报告在二进制数上模 10 时发生的偏差。你指的是其他偏见吗?
【解决方案2】:

根据 cmets 的跟进,看来您需要的是 modulus operator [%]。

您可能还需要查看相关的wiki

注意:每次我们对随机数使用模运算符时,都有可能遇到modulo bias,这最终会破坏随机数的公平分布。你必须照顾好它。

有关此问题的详细讨论,请参阅此question 和相关答案。

【讨论】:

  • 请添加一些关于偏见的信息以使这个答案完整。
  • @MaartenBodewes 已更新。请查看。
  • 好多了。不幸的是,链接中的答案很差。也许改用this。您当然可以在 0..10 范围内使用 8 位数字 - 取决于您是否要更改为更大的范围以及要从随机池中提取多少数据(只需 4 位就可以了,但是您在 16 次中会有 6 次被拒绝,这是相当糟糕的)。
【解决方案3】:

这取决于很多事情。安全 PRNG 有时会生成长字节数组而不是整数,假设它是 16 字节长数组,然后像这样提取 32 位整数:buf[0]*0x1000000+buf[1]*0x10000+buf[2]*0x100+buf[3] 或使用移位运算符。这是随机的,所以大端/小端无关紧要。

char randbytes[16];
//...

const char *p = randbytes;

//assumes size of int is 4
unsigned int rand1 = p[0] << 24 + p[1] << 16 + p[2] << 8 + p[3]; p += 4;
unsigned int rand2 = p[0] << 24 + p[1] << 16 + p[2] << 8 + p[3]; p += 4;
unsigned int rand3 = p[0] << 24 + p[1] << 16 + p[2] << 8 + p[3]; p += 4;
unsigned int rand4 = p[0] << 24 + p[1] << 16 + p[2] << 8 + p[3];

然后在整数上使用%

ps,我认为这是一个很长的答案。如果您想要 1 到 10 之间的数字,那么只需在第一个字节上使用 %

【讨论】:

  • 这会给你留下一个很小但可检测的偏差 为什么你需要 16 个字节来生成一个 32 位的整数???。
  • 它是从 16 个字节中提取的 4 个不同的整数,它是安全 PRNG 的典型输出。输入后我改变了主意,我认为这个问题与安全 PRNG 无关
【解决方案4】:

好的,所以在我使用 Eclipse C/C++ IDE 之前,这个答案是用 Java 编写的:

public final static int simpleBound(Random rbg, int n) {

    final int BYTE_VALUES = 256;

    // sanity check, only return positive numbers
    if (n <= 0) {
        throw new IllegalArgumentException("Oops");
    }

    // sanity check: choice of value 0 or 0...
    if (n == 1) {
        return 0;
    }

    // sanity check: does not fit in byte
    if (n > BYTE_VALUES) {
        throw new IllegalArgumentException("Oops");
    }

    // optimization for n = 2^y
    if (Integer.bitCount(n) == 1) {
        final int mask = n - 1;
        return retrieveRandomByte(rbg) & mask;
    }

    // you can skip to this if you are sure n = 10

    // z is upper bound, and contains floor(z / n) blocks of n values
    final int z = (BYTE_VALUES / n) * n;
    int x;
    do {
        x = retrieveRandomByte(rbg);
    } while (x >= z);
    return x % n;
}

所以 n 是 [0..n) 范围内的最大值,即 n 是互斥的。对于范围 [1..10],只需将结果增加 1。

【讨论】:

  • 请注意,您最多需要 24 个字节来生成 1 到 10 之间的数字(之后不生成 1 到 10 之间的数字的机会变得小于 1 / 2 ^ 128 :P )
  • 请注意,如果您想减少所需的最大字节数(在本例中为 20 字节),建议使用 16 位数字。
  • 我不知道如何将 (Integer.bitCount(n) == 1) 翻译成 C,所以我不能使用这个函数。
  • 您可以简单地省略它,这只是优化所必需的。但是,this is stackoverflow。如果您需要有关如何将 unsigned char 转换为 32 位值的链接,请尖叫 :)
猜你喜欢
  • 1970-01-01
  • 2020-01-04
  • 1970-01-01
  • 2011-05-20
  • 2023-03-16
  • 1970-01-01
  • 2011-03-31
  • 2018-01-27
  • 2020-06-29
相关资源
最近更新 更多