【问题标题】:How to generate a pseudorandom 32-byte string for use as a salt in a cryptographic hash function?如何生成一个伪随机 32 字节字符串以用作加密哈希函数中的盐?
【发布时间】:2026-02-16 19:55:02
【问题描述】:

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

#include <random>
#include <iostream>

using namespace std;


void genSalt() {

    const char charset[] = {

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

    };

}

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

【问题讨论】:

    标签: c++ hash cryptography salt


    【解决方案1】:

    如果您可以使用C++11,您可以使用random_shuffle 来打乱您的元素数组,然后始终返回第一个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;
    }
    

    请注意,这种方法不会返回重复的盐序列。这对于教育目的来说是可以的,因为它有效且易于实施。


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

    void genSalt() {
        static const char charset[] = {
            "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!£$%^&*():@~#'?/><.,|`¬¦" };
    
    
        for (int i = 0; i < 32; ++i) {
            cout<<charset[rand() % (sizeof(charset) - 1)];
        }
    }
    

    【讨论】:

    • 注意:std::random_shuffle 在 c++14 中已弃用,从 C++17 中删除。使用std::shuffle(c++11 起可用)和精心准备的 prng。
    • @WhozCraig 好点。但总体思路仍然有效。谢谢
    【解决方案2】:

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

    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());
    

    您可以选择不同的引擎来微调您的随机数,默认是实现定义的选择(我假设您也可以轻松插入加密安全生成器)。

    random_device 会自动处理播种,而如果您使用的是较旧的 C 风格 rand 函数,您可能希望使用种子(如系统时间)调用 srand 以在生成之前对其进行初始化任何东西。

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

    在这种情况下,您希望在一组盐字符中均匀分布:

    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 风格 rand() 函数的测试用例:

    int main() {
        // Seed randomizer
        srand( time(0) );
    
        // Print a random number between 0 and 1999
        int number = rand() % 2000;
        std::cout << number;
    }
    

    通常人们并不关心,因为它“足够随机”,但你不会得到模数的均匀分布 (%)。 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 函数没有统一覆盖所需的范围,您可能永远找不到结果。

    【讨论】: