Skip to content

Commit f350c55

Browse files
authored
Merge pull request #7344 from kenjis/fix-random_string-alnum
fix: random_string() alpha alnum nozero
2 parents 43cf748 + 80ea505 commit f350c55

3 files changed

Lines changed: 73 additions & 7 deletions

File tree

system/Helpers/text_helper.php

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ function random_string(string $type = 'alnum', int $len = 8): string
559559
break;
560560
}
561561

562-
return substr(str_shuffle(str_repeat($pool, (int) ceil($len / strlen($pool)))), 0, $len);
562+
return _from_random($len, $pool);
563563

564564
case 'numeric':
565565
$max = 10 ** $len - 1;
@@ -587,6 +587,69 @@ function random_string(string $type = 'alnum', int $len = 8): string
587587
}
588588
}
589589

590+
if (! function_exists('_from_random')) {
591+
/**
592+
* The following function was derived from code of Symfony (v6.2.7 - 2023-02-28)
593+
* https://github.com/symfony/symfony/blob/80cac46a31d4561804c17d101591a4f59e6db3a2/src/Symfony/Component/String/ByteString.php#L45
594+
* Code subject to the MIT license (https://github.com/symfony/symfony/blob/v6.2.7/LICENSE).
595+
* Copyright (c) 2004-present Fabien Potencier
596+
*
597+
* The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03)
598+
* https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16
599+
* Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE).
600+
* Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/)
601+
*
602+
* @internal Outside the framework this should not be used directly.
603+
*/
604+
function _from_random(int $length, string $pool): string
605+
{
606+
if ($length <= 0) {
607+
throw new InvalidArgumentException(
608+
sprintf('A strictly positive length is expected, "%d" given.', $length)
609+
);
610+
}
611+
612+
$poolSize = \strlen($pool);
613+
$bits = (int) ceil(log($poolSize, 2.0));
614+
if ($bits <= 0 || $bits > 56) {
615+
throw new InvalidArgumentException(
616+
'The length of the alphabet must in the [2^1, 2^56] range.'
617+
);
618+
}
619+
620+
$string = '';
621+
622+
while ($length > 0) {
623+
$urandomLength = (int) ceil(2 * $length * $bits / 8.0);
624+
$data = random_bytes($urandomLength);
625+
$unpackedData = 0;
626+
$unpackedBits = 0;
627+
628+
for ($i = 0; $i < $urandomLength && $length > 0; $i++) {
629+
// Unpack 8 bits
630+
$unpackedData = ($unpackedData << 8) | \ord($data[$i]);
631+
$unpackedBits += 8;
632+
633+
// While we have enough bits to select a character from the alphabet, keep
634+
// consuming the random data
635+
for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) {
636+
$index = ($unpackedData & ((1 << $bits) - 1));
637+
$unpackedData >>= $bits;
638+
// Unfortunately, the alphabet size is not necessarily a power of two.
639+
// Worst case, it is 2^k + 1, which means we need (k+1) bits and we
640+
// have around a 50% chance of missing as k gets larger
641+
if ($index < $poolSize) {
642+
$string .= $pool[$index];
643+
$length--;
644+
}
645+
}
646+
}
647+
}
648+
649+
return $string;
650+
}
651+
}
652+
590653
if (! function_exists('increment_string')) {
591654
/**
592655
* Add's _1 to a string or increment the ending number to allow _2, _3, etc

user_guide_src/source/changelogs/v4.3.3.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ SECURITY
1313
********
1414

1515
- **Email:** Added missing TLS 1.3 support.
16-
- **Text Helper:** The :php:func:`random_string()` type **numeric** is now cryptographically secure.
16+
- **Text Helper:** The :php:func:`random_string()` type **alpha**, **alnum**,
17+
**numeric** and **nozero** are now cryptographically secure.
1718

1819
BREAKING
1920
********

user_guide_src/source/helpers/text_helper.rst

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ The following functions are available:
3030
Generates a random string based on the type and length you specify.
3131
Useful for creating passwords or generating random hashes.
3232

33-
.. warning:: Except for type **numeric** and **crypto**, no cryptographically secure
34-
strings are generated. Therefore, it must not be used for cryptographic
35-
purposes or purposes that requires return values to be unguessable.
33+
.. warning:: For types: **basic**, **md5**, and **sha1**, generated strings
34+
are not cryptographically secure. Therefore, these types cannot be used
35+
for cryptographic purposes or purposes requiring unguessable return values.
3636

3737
The first parameter specifies the type of string, the second parameter
3838
specifies the length. The following choices are available:
@@ -49,8 +49,10 @@ The following functions are available:
4949
.. note:: When you use **crypto**, you must set an even number to the second parameter.
5050
Since v4.2.2, if you set an odd number, ``InvalidArgumentException`` will be thrown.
5151

52-
.. note:: Since v4.3.3, **numeric** uses ``random_int()``. In the previous
53-
versions, it used ``str_shuffle()`` that is not cryptographically secure.
52+
.. note:: Since v4.3.3, **alpha**, **alnum** and **nozero** use ``random_byte()``,
53+
and **numeric** uses ``random_int()``.
54+
In the previous versions, it used ``str_shuffle()`` that is not
55+
cryptographically secure.
5456

5557
Usage example:
5658

0 commit comments

Comments
 (0)