1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\Encoder;
use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
use Symfony\Component\Security\Core\Util\SecureRandomInterface;
/**
* @author Elnur Abdurrakhimov <elnur@elnur.pro>
* @author Terje BrĂ¥ten <terje@braten.be>
*/
class BCryptPasswordEncoder extends BasePasswordEncoder
{
/**
* @var SecureRandomInterface
*/
private $secureRandom;
/**
* @var string
*/
private $cost;
private static $prefix = null;
/**
* Constructor.
*
* @param SecureRandomInterface $secureRandom A SecureRandomInterface instance
* @param integer $cost The algorithmic cost that should be used
*
* @throws \InvalidArgumentException if cost is out of range
*/
public function __construct(SecureRandomInterface $secureRandom, $cost)
{
$this->secureRandom = $secureRandom;
$cost = (int) $cost;
if ($cost < 4 || $cost > 31) {
throw new \InvalidArgumentException('Cost must be in the range of 4-31.');
}
$this->cost = sprintf('%02d', $cost);
if (!self::$prefix) {
self::$prefix = '$'.(version_compare(phpversion(), '5.3.7', '>=') ? '2y' : '2a').'$';
}
}
/**
* {@inheritdoc}
*/
public function encodePassword($raw, $salt)
{
if (function_exists('password_hash')) {
return password_hash($raw, PASSWORD_BCRYPT, array('cost' => $this->cost));
}
$salt = self::$prefix.$this->cost.'$'.$this->encodeSalt($this->getRawSalt());
$encoded = crypt($raw, $salt);
if (!is_string($encoded) || strlen($encoded) <= 13) {
return false;
}
return $encoded;
}
/**
* {@inheritdoc}
*/
public function isPasswordValid($encoded, $raw, $salt)
{
if (function_exists('password_verify')) {
return password_verify($raw, $encoded);
}
$crypted = crypt($raw, $encoded);
if (strlen($crypted) <= 13) {
return false;
}
return $this->comparePasswords($encoded, $crypted);
}
/**
* Encodes the salt to be used by Bcrypt.
*
* The blowfish/bcrypt algorithm used by PHP crypt expects a different
* set and order of characters than the usual base64_encode function.
* Regular b64: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
* Bcrypt b64: ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
* We care because the last character in our encoded string will
* only represent 2 bits. While two known implementations of
* bcrypt will happily accept and correct a salt string which
* has the 4 unused bits set to non-zero, we do not want to take
* chances and we also do not want to waste an additional byte
* of entropy.
*
* @param bytes $random a string of 16 random bytes
*
* @return string Properly encoded salt to use with php crypt function
*
* @throws \InvalidArgumentException if string of random bytes is too short
*/
protected function encodeSalt($random)
{
$len = strlen($random);
if ($len < 16) {
throw new \InvalidArgumentException('The bcrypt salt needs 16 random bytes.');
}
if ($len > 16) {
$random = substr($random, 0, 16);
}
$base64raw = str_replace('+', '.', base64_encode($random));
$salt128bit = substr($base64raw, 0, 21);
$lastchar = substr($base64raw, 21, 1);
$lastchar = strtr($lastchar, 'AQgw', '.Oeu');
$salt128bit .= $lastchar;
return $salt128bit;
}
/**
* @return bytes 16 random bytes to be used in the salt
*/
protected function getRawSalt()
{
$rawSalt = false;
$numBytes = 16;
if (function_exists('mcrypt_create_iv')) {
$rawSalt = mcrypt_create_iv($numBytes, MCRYPT_DEV_URANDOM);
}
if (!$rawSalt) {
$rawSalt = $this->secureRandom->nextBytes($numBytes);
}
return $rawSalt;
}
}
|