如何生成伪随机 32 字节字符串以用作加密哈希函数中的盐?

How to generate a pseudorandom 32-byte string for use as a salt in a cryptographic hash function?

我正在尝试用 C++ 编写密码加密函数(注意:仅用于教育目的。我实际上不会用它存储我的密码。)但我不确定如何创建一个随机的 32 字节盐使用预定义的字符集。我该怎么做?

#include <random>
#include <iostream>

using namespace std;


void genSalt() {

    const char charset[] = {

        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz"
        "!£$%^&*():@~#'?/><.,|`¬¦"

    };

}

代码应根据定义的字符集 'charset' 生成一个随机的 32 字节字符串。我不确定如何实现。

如果可以使用 C++11,则可以使用 random_shuffle 来随机排列元素数组,然后总是 return 前 32 个元素。

类似下面的内容应该适用于您的情况:

void genSalt() {
   static char charset[] = {
        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!£$%^&*():@~#'?/><.,|`¬¦" };

    random_shuffle(begin(charset), end(charset));

    for(int i = 0 ; i < 32; i++)
       cout<<charset[i];
    cout<<endl;
}

请注意,此方法不会 return 盐序列重复。这在某种程度上可以用于教育目的,因为它有效并且易于实施。


另一种方法是从序列中一个一个地随机挑选元素,如下所示:

void genSalt() {
    static const char charset[] = {
        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!£$%^&*():@~#'?/><.,|`¬¦" };


    for (int i = 0; i < 32; ++i) {
        cout<<charset[rand() % (sizeof(charset) - 1)];
    }
}

首先要注意的是源文件中某些字符的存储。根据编码,£¬¦ 可能超过一个字节,这可能会搞砸您的结果;如果你想让事情顺利进行而不考虑编码,你应该将它们存储为硬编码字节:

const char charset[] =
    "0123456789"
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
    "!\xA3$%^&*():@~#'?/><.,|`\xAC\xA6";

// \xA3 = single-byte extended encoding for £
// \xAC = ¬
// \xA6 = ¦

(顺便说一句,我的意思是无论如何在你的 salt 中使用这些字符都是一个坏主意,因为它们可能与使用 salt 的其他编码冲突。)

至于生成密码盐,我不是密码学家,但使用加密安全伪随机数生成器以外的任何东西对我来说似乎有点不确定。如果它只是一个 C++ 练习,内置的随机生成器就可以了。我从未真正使用过 C++11 PRNG 函数,所以这对我来说也是一个很好的练习。

您首先制作一个 random_device,然后制作一个随机化器引擎:

#include <random>
std::random_device my_random_device;
std::default_random_engine my_random_engine(my_random_device());

你可以为 fine-tuning 你的随机数选择不同的引擎,默认是一个实现定义 selection(我假设你也可以很容易地插入一个加密安全的发电机)。

random_device 会自动处理播种,而如果您使用的是较旧的 C-style rand 函数,则需要使用种子调用 srand (像系统时间)在生成任何东西之前对其进行初始化。

要从您的 salt 源中挑选字符,您 select 一种分发方法,您可以在此处获得更多详细信息:https://en.cppreference.com/w/cpp/numeric/random

在这种情况下,您希望在 salt 字符集中均匀分布:

std::uniform_int_distribution<int> random_number(0, sizeof(charset) - 1);

您可以将其调用为 random_number(my_random_engine) 以获得介于 0 和最后一个字符索引之间的数字(不要忘记减 1 以跳过空终止符)。

然后很容易对字符进行采样并构建一个字符串:

std::string salt;
salt.reserve( 32 );
for( int i = 0; i < 32; i++ ) {
    salt.push_back(charset[random_number(my_random_engine)]);
}
std::cout << "salt result: " << salt << std::endl;

工作示例:https://wandbox.org/permlink/mGd8pYP9Y3injuuG


我想提及的另一件事是对随机数使用 % 的常见陷阱。例如,考虑这个使用旧 C-style rand() 函数的测试用例:

int main() {
    // Seed randomizer
    srand( time(0) );

    // Print a random number between 0 and 1999
    int number = rand() % 2000;
    std::cout << number;
}

通常人们不在乎,因为它是 "random enough",但您不会得到具有模数 (%) 的均匀分布。 rand() 生成一个介于 0RAND_MAX 之间的数字,您应该缩放返回的范围以适合您想要的范围。

// Sample random number between 0 and 1999. (add 1 to rand max to make 2000 not slightly possible)
int number = rand() * 2000 / (RAND_MAX+1)

记得选择合适的 PRNG 函数来满足您的需要,尤其是在寻找非常困难的赔率时。如果您要搜索百万分之一,如果 PRNG 函数没有统一覆盖所需的范围,您可能永远找不到结果。