生成一个随机且唯一的字符串。

4

我需要一种有效的方法来获取既随机又唯一的字符串,并且有零(或可以忽略的)重复的几率。

我需要使用 [0-9A-z] 范围内的字符。

这是我目前拥有的:

substr(sha1(mt_rand().uniqid()),0,22);  
5个回答

8

PHP的最新更改

我知道这实际上是在谈论bcrypt和密码加盐,现在我可以向阅读本文的人们指出他们应该使用的函数,而不是手动编写自己的加盐系统。

使用password_hash($input, PASSWORD_DEFAULT); 生成一个适合插入数据库的哈希值。这将为您获取salt。

插入:

$hash = password_hash($_POST["password"], PASSWORD_DEFAULT, ["cost" => 16]);
DB::table("users")->insert(["username" => $user, "password" => $hash]);
// or whatever database method you use to insert data

验证:

$hash = DB::table("users")->fetchByName($username)->select("password");
$input = $_POST["password"];

$verified = password_verify($input, $hash); // true if the password matches

在 PHP 5.5 版本之前,可以使用 https://github.com/ircmaxell/password_compat 作为一种即插即用的方法。


随机生成盐时,发生冲突的概率为

1 / [number of possible letters/numbers] ** [length]

对于一个22个字符的字符串来说,这些可能性非常小(嗯,并不是完全不可能,但可以忽略

1 / (22 ** 60) = 1 / (3.51043 x 10**80)

看到了吗?微小的


数学谬误

如果您需要一个真正随机的字符串(注意:这些字符串只是映射为字母的数字串),那么您可能有点运气不佳。
你要找的是CSPRNG(加密安全伪随机数生成器)。 不需要唯一性。

正如@Guarav在他的回答中指出的那样,您可以使用时间戳作为种子,然后进行哈希处理。 如果这是128位时间戳,则称为UUID(唯一通用标识符),它是可预测的,并且由于以下原因可能是不好的:

  1. 您采取此时间戳的准确度将成为决定此salt可预测程度的因素。
  2. 如果将以秒为单位的时间作为整数哈希处理,则会得到非常明确定义且容易猜测的salt

尽管如此,通过足够的准确性,您仍然可以使用时间戳作为唯一salt。不是 随机(除非您将其用作随机种子并将其转换为基数10,但这仍然是一个坏主意)。如果您可以计算小于纳秒级的时间并想将其用作唯一ID,则考虑这一点。 PHP无法快速处理以给出两个相撞的亚纳秒ID1(但这并不意味着您不应该验证!)


1:它适用于composer!


让我感到困扰的是,当bcrypt将盐与哈希一起存储时,我必须单独存储盐,但我无法查询所有行,从哈希中提取盐并检查先前的使用情况!这是不生产的!如果成千上万的用户中有两个共享相同的盐,会有问题吗? - Sandro Antonucci
1
如果您在使用bcrypt,则实际上不需要盐才能进行密码哈希处理,并且相同的盐两次出现与具有不同盐从纯数学角度来看是同样随机的。此外,我链接的那个类被用于现实世界的登录系统中,并且比旧系统(它们使用未加盐的sha1,发抖)更好。 - Amelia
谢谢!我曾经绝望地想找到一种方法,在使用bcrypt时拥有唯一的盐值,而无需存储以相互检查! - Sandro Antonucci
1
如果有帮助未来的访问者,我会将此添加到答案中。 - Amelia

4

这是我做某事的方式...

// chars
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-+';

// convert to array
$arr = str_split($chars, 1);

// shuffle the array
shuffle($arr);

// array to chars with 22 chars
echo substr(implode('', $arr), 0, 22);

输出

xd*thKM$B#13^)9!QkD@gU

ixXYL0GEHRf+SNn#gcJIq-

$0LruRlgpjv1XS8xZq)hwY

$G-MKXf@rI3hFwT4l9)j0u

为确保其唯一性,您可以始终在数据库中进行检查。如果重复,请重新生成KEY。

1
有趣。我从未想过使用shuffle;我只是将字符串作为数组使用,并根据数字随机选择字符。+1。 - Amelia
@Madan Sapkota,你的方法与我使用的方法相似。但是,使用你的方法每个字符只会出现一次,因此随机性要小得多。可以尝试使用以下代码,这对我来说效果非常好:`$pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';return substr(str_shuffle(str_repeat($pool, 22)), 0, 22);` - Kevin

2

1
你无法保证这将始终返回唯一值,但它将显著减少风险。使用随机数生成算法时,永远存在重复的可能性。
为了确保仅生成唯一值,可以针对先前生成的值进行搜索并丢弃重复项。
以下是减少公式中重复机会的一些建议:
  • 不要使用sha1。这是一种返回任何给定输入的一致输出的方法。它不会影响重复的可能性。
  • 考虑将随机数转换为不同的基数(例如,36进制数字可以使用字符0-9和A-Z,您可以将其存储在数据库中或者用于处理随机字符串输出)。但我不会真的选择36,也许256+?
  • 由于您使用substr限制了字符串长度为22个字符,因此实际上稍微增加了重复的可能性。使用我在前一个项目中提到的内容,将随机数转换为少于22个字符的字符串,无需截断。
  • 此外,您可以通过使用sha1函数返回一个原始值来消除对substr的需要,该函数仅有20个字符。
  • 或者,只需不限制字符串长度为22个字符(除非您真的有必要)。

-1

为什么不直接使用time()函数呢?

我需要做类似的事情,一个保持唯一ID的解决方案,最终我采用了使用PHP函数time()的解决方案,像这样$reference_number = 'BFF-' . time();你可以将BFF更改为对你的业务逻辑更有意义的内容。这样,我就不必担心新生成的ID是否已被占用,因为时间总是唯一的。

希望这能帮到你。


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