Skip to content

Commit

Permalink
Merge pull request #12858 from sergeyklay/feature/security/random/bas…
Browse files Browse the repository at this point in the history
…e_62

Added Phalcon\Security\Random:base62
  • Loading branch information
sergeyklay authored May 21, 2017
2 parents 4d68d8a + 622b568 commit 22c403b
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 84 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- Added a new `Phalcon\Mvc\Model\Binder::findBoundModel` method. Params fetched from cache are being added to `internalCache` class property in `Phalcon\Mvc\Model\Binder::getParamsFromCache`
- Added `Phalcon\Mvc\Model\Criteria::createBuilder` to create a query builder from criteria
- Added `dispatcher::beforeForward` event to allow forwarding request to the separated module [#121](https://github.com/phalcon/cphalcon/issues/121), [#12417](https://github.com/phalcon/cphalcon/issues/12417)
- Added `Phalcon\Security\Random:base62` to provide the largest value that can safely be used in URLs without needing to take extra characters into consideration [#12105](https://github.com/phalcon/cphalcon/issues/12105)
- Fixed Dispatcher forwarding when handling exception [#11819](https://github.com/phalcon/cphalcon/issues/11819), [#12154](https://github.com/phalcon/cphalcon/issues/12154)
- Fixed params view scope for PHP 7 [#12648](https://github.com/phalcon/cphalcon/issues/12648)
- Fixed `Phalcon\Mvc\Micro::handle` to prevent attemps to send response twice [#12668](https://github.com/phalcon/cphalcon/pull/12668)
Expand Down
81 changes: 59 additions & 22 deletions phalcon/security/random.zep
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ namespace Phalcon\Security;
* echo $random->hex(12); // 95469d667475125208be45c4
* echo $random->hex(13); // 05475e8af4a34f8f743ab48761
*
* // Random base62 string
* echo $random->base62(); // z0RkwHfh8ErDM1xw
*
* // Random base64 string
* echo $random->base64(12); // XfIN81jGGuKkcE1E
* echo $random->base64(12); // 3rcq39QzGK9fUqh8
Expand Down Expand Up @@ -172,45 +175,52 @@ class Random
* If $len is not specified, 16 is assumed. It may be larger in future.
* The result may contain alphanumeric characters except 0, O, I and l.
*
* It is similar to Base64 but has been modified to avoid both non-alphanumeric
* It is similar to `Phalcon\Security\Random:base64` but has been modified to avoid both non-alphanumeric
* characters and letters which might look ambiguous when printed.
*
*<code>
* <code>
* $random = new \Phalcon\Security\Random();
*
* echo $random->base58(); // 4kUgL2pdQMSCQtjE
*</code>
* </code>
*
* @link https://en.wikipedia.org/wiki/Base58
* @see \Phalcon\Security\Random:base64
* @link https://en.wikipedia.org/wiki/Base58
* @throws Exception If secure random number generator is not available or unexpected partial read
*/
public function base58(n = null) -> string
public function base58(int len = null) -> string
{
var bytes, idx;
string byteString = "",
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

let bytes = unpack("C*", this->bytes(n));

for idx in bytes {
let idx = idx % 64;

if idx >= 58 {
let idx = this->number(57);
}

let byteString .= alphabet[(int) idx];
}
return this->base("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", 58, len);
}

return byteString;
/**
* Generates a random base62 string
*
* If $len is not specified, 16 is assumed. It may be larger in future.
*
* It is similar to `Phalcon\Security\Random:base58` but has been modified to provide the largest value that can
* safely be used in URLs without needing to take extra characters into consideration because it is [A-Za-z0-9].
*
*< code>
* $random = new \Phalcon\Security\Random();
*
* echo $random->base62(); // z0RkwHfh8ErDM1xw
* </code>
*
* @see \Phalcon\Security\Random:base58
* @throws Exception If secure random number generator is not available or unexpected partial read
*/
public function base62(int len = null) -> string
{
return this->base("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 62, len);
}

/**
* Generates a random base64 string
*
* If $len is not specified, 16 is assumed. It may be larger in future.
* The length of the result string is usually greater of $len.
* Size formula: 4 *( $len / 3) and this need to be rounded up to a multiple of 4.
* Size formula: 4 * ($len / 3) and this need to be rounded up to a multiple of 4.
*
*<code>
* $random = new \Phalcon\Security\Random();
Expand Down Expand Up @@ -343,4 +353,31 @@ class Random

return hexdec(array_shift(ret));
}

/**
* Generates a random string based on the number ($base) of characters ($alphabet).
*
* If $n is not specified, 16 is assumed. It may be larger in future.
*
* @throws Exception If secure random number generator is not available or unexpected partial read
*/
protected function base(string alphabet, int base, n = null) -> string
{
var bytes, idx;
string byteString = "";

let bytes = unpack("C*", this->bytes(n));

for idx in bytes {
let idx = idx % 64;

if idx >= base {
let idx = this->number(base - 1);
}

let byteString .= alphabet[(int) idx];
}

return byteString;
}
}
180 changes: 118 additions & 62 deletions tests/unit/Security/RandomTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,7 @@ public function testRandomBase58()
{
$this->specify(
"base58 does not generate a valid string",
function () {
$lens = [
2,
12,
16,
24,
48,
100
];

function ($len) {
$random = new Random();

$isValid = function ($base58) {
Expand All @@ -131,17 +122,69 @@ function () {
return (preg_match('#^[^'.join('', $alphabet).']+$#i', $base58) === 0);
};

foreach ($lens as $len) {
$actual = $random->base58($len);
$actual = $random->base58($len);

if ($len === null) {
expect(strlen($actual))->equals(16);
} else {
expect(strlen($actual))->equals($len);
expect($isValid($actual))->true();
}

$actual = $random->base58();
expect(strlen($actual))->equals(16);
expect($isValid($actual))->true();
}
},
[
'examples' => [
[null],
[2],
[12],
[16],
[24],
[48],
[100],
]
]
);
}

/**
* Tests the random base62 generation
*
* @issue 12105
* @author Serghei Iakovlev <serghei@phalconphp.com>
* @since 2017-05-21
*/
public function testRandomBase62()
{
$this->specify(
'base62 does not generate a valid string',
function ($len) {
$random = new Random();

$isValid = function ($base62) {
return (preg_match("#^[^a-z0-9]+$#i", $base62) === 0);
};

$actual = $random->base62($len);

if ($len === null) {
expect(strlen($actual))->equals(16);
} else {
expect(strlen($actual))->equals($len);
}

expect($isValid($actual))->true();
},
[
'examples' => [
[null],
[2],
[12],
[16],
[24],
[48],
[100],
]
]
);
}

Expand All @@ -155,40 +198,34 @@ public function testRandomBase64()
{
$this->specify(
"base64 does not generate a valid string",
function () {
$lens = [
2,
12,
16,
24,
48,
100
];

function ($len) {
$random = new Random();

$checkSize = function ($base64, $len) {
// Size formula: 4 *( $len / 3) and this need to be rounded up to a multiple of 4.
$formula = (round(4*($len/3))%4 === 0) ? round(4*($len/3)) : round((4*($len/3)+4/2)/4)*4;

return strlen($base64) == $formula;
};

$isValid = function ($base64) {
return (preg_match("#[^a-z0-9+_=/-]+#i", $base64) === 0);
};

foreach ($lens as $len) {
$actual = $random->base64($len);
$actual = $random->base64($len);

expect($checkSize($actual, $len))->true();
expect($isValid($actual))->true();
if ($len === null) {
expect($this->checkSize($actual, 16))->true();
} else {
expect($this->checkSize($actual, $len))->true();
}

$actual = $random->base64();
expect($checkSize($actual, 16))->true();
expect($isValid($actual))->true();
}
},
[
'examples' => [
[null],
[2],
[12],
[16],
[24],
[48],
[100],
]
]
);
}

Expand All @@ -202,34 +239,34 @@ public function testRandomBase64Safe()
{
$this->specify(
"base64Safe does not generate a valid string",
function () {
$lens = [
2,
12,
16,
24,
48,
100
];

function ($len, $padding, $pattern) {
$random = new Random();

$isValid = function ($base64, $padding = false) {
$pattern = $padding ? "a-z0-9_=-" : "a-z0-9_-";
$isValid = function ($base64) use ($pattern) {
return (preg_match("#[^$pattern]+#i", $base64) === 0);
};

foreach ($lens as $len) {
$actual = $random->base64Safe($len);
expect($isValid($actual))->true();
}

$actual = $random->base64Safe();
$actual = $random->base64Safe($len, $padding);
expect($isValid($actual))->true();

$actual = $random->base64Safe(null, true);
expect($isValid($actual, true))->true();
}
},
[
'examples' => [
[null, false, 'a-z0-9_-' ],
[null, true, 'a-z0-9_=-'],
[2, false, 'a-z0-9_-' ],
[2, true, 'a-z0-9_=-'],
[12, false, 'a-z0-9_-' ],
[12, true, 'a-z0-9_=-'],
[16, false, 'a-z0-9_-' ],
[16, true, 'a-z0-9_=-'],
[24, false, 'a-z0-9_-' ],
[24, true, 'a-z0-9_=-'],
[48, false, 'a-z0-9_-' ],
[48, true, 'a-z0-9_=-'],
[100, false, 'a-z0-9_-' ],
[100, true, 'a-z0-9_=-'],
]
]
);
}

Expand Down Expand Up @@ -317,4 +354,23 @@ function () {
}
);
}

/**
* Size formula: 4 * ($n / 3) and this need to be rounded up to a multiple of 4.
*
* @param string $string
* @param int $n
*
* @return bool
*/
protected function checkSize($string, $n)
{
if (round(4 * ($n / 3)) % 4 === 0) {
$len = round(4 * ($n / 3));
} else {
$len = round((4 * ($n / 3) + 4 / 2) / 4) * 4;
}

return strlen($string) == $len;
}
}

0 comments on commit 22c403b

Please sign in to comment.