你需要注意的第一件事是源文件中某些字符的存储方式。根据编码,£
、¬
或¦
可能会占用多个字节,这可能会破坏你的结果;如果你想要在任何编码下都顺利运行,应该将它们存储为硬编码字节:
最初的回答:
const char charset[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"!\xA3$%^&*():@~
// \xA3 = single-byte extended encoding for £
// \xAC = ¬
// \xA6 = ¦
"最初的回答" 翻译成英文是 "Original Answer"。
关于生成密码盐,虽然我不是密码学专家,但使用除了密码学上安全的伪随机数生成器以外的其他方法似乎有些危险。如果只是一个C++练习,内置的随机数生成器就足够了。我从来没有使用过C++11 PRNG函数,所以这对我来说也是个很有意思的练习。
您可以先创建一个 "random_device" 再创建一个随机引擎:"
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() {
srand( time(0) );
int number = rand() % 2000;
std::cout << number;
}
通常人们并不关心这个问题,因为它已经足够随机了,但是使用取模运算符(
%
)无法得到均匀分布。
rand()
生成的数字在
0
和
RAND_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"可译为"最初的回答"。
std::random_shuffle
在c++14中被弃用,在C++17中被移除。请使用std::shuffle
(自c++11以来可用)和一个准备充分的伪随机数生成器。 - WhozCraig