如何生成一个32字节的伪随机字符串,供密码哈希函数使用作为盐?

3

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

#include <random>
#include <iostream>

using namespace std;


void genSalt() {

    const char charset[] = {

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

    };

}

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

2个回答

2

你需要注意的第一件事是源文件中某些字符的存储方式。根据编码,£¬¦可能会占用多个字节,这可能会破坏你的结果;如果你想要在任何编码下都顺利运行,应该将它们存储为硬编码字节:

最初的回答:

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

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

"最初的回答" 翻译成英文是 "Original Answer"。
关于生成密码盐,虽然我不是密码学专家,但使用除了密码学上安全的伪随机数生成器以外的其他方法似乎有些危险。如果只是一个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)

记得选择适合您需求的随机数生成器函数,并且尤其在寻找极小概率事件时要谨慎。如果你需要的是100万分之一的概率,但是所使用的随机数生成器函数并未均匀地覆盖到所需范围,那么你可能永远也无法得到想要的结果。"Original Answer"可译为"最初的回答"。

0

如果你可以使用 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以来可用)和一个准备充分的伪随机数生成器。 - WhozCraig
@WhozCraig 说得好。但总体思路仍然有效。谢谢 - Davide Spataro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接