1: <?php
2:
3: /**
4: * Pry Framework
5: *
6: * LICENSE
7: *
8: * This source file is subject to the new BSD license that is bundled
9: * with this package in the file LICENSE.txt.
10: *
11: */
12:
13: namespace Pry\Auth;
14:
15: /**
16: * Classe implémentant l'algorithm bcrypt pour le cryptage de mot de passe.
17: * <code>
18: * $bcrypt = new Bcrypt(11);
19: * $hash = $bcrypt->hash('mypass');
20: * var_dump($bcrypt->check('mypass',$hash);
21: * </code>
22: * @package Auth
23: * @version 1.0
24: * @author Olivier ROGER <oroger.fr>
25: * @see http://en.wikipedia.org/wiki/Bcrypt
26: * @see http://stackoverflow.com/questions/4795385/how-do-you-use-bcrypt-for-hashing-passwords-in-php
27: */
28: class Bcrypt
29: {
30: /** Base du grain de sel blowfish */
31:
32: const SALTBASE = '$2a$';
33:
34: /** Logarithme base 2 du compteur d'itération */
35: private $iterations;
36:
37: /** Aléa pour le grain de sel */
38: private $random;
39:
40: /**
41: * Initialise le cryptage
42: * @param int $rounds Nombre d'itération pour l'algo de hashage entre 4 et 31.
43: * Plus ce paramètre est élevé plus le temps de hashage sera long. 12 par défaut
44: * @throws RuntimeException Si le cryptage BlowFish n'est pas supporté sur l'installation
45: * @throws InvalidArgumentException Le paramètres n'est pas compris entre 4 et 31.
46: */
47: public function __construct($rounds = 12)
48: {
49: if (CRYPT_BLOWFISH != 1)
50: throw new \RuntimeException('Blowfish is not available on your system, it is required for bcrypt');
51:
52: if ($rounds < 4 || $rounds > 31)
53: throw new \InvalidArgumentException(' The number of rounds have to be between 4 and 31');
54:
55: $this->iterations = $rounds;
56: $this->random = microtime();
57:
58: if (function_exists('getmypid'))
59: $this->random .= getmypid();
60: }
61:
62: /**
63: * Hash la chaine donnée
64: * @param string $str Chaine en clair
65: * @return string Un hash de la chaine de 60 caractères
66: * @throws type
67: */
68: public function hash($str)
69: {
70: $hash = crypt($str, $this->generateSalt());
71: if (strlen($hash) != 60)
72: throw \LengthException('Hash is ' . strlen($hash) . ' characters long , somethng went wrong');
73:
74: return $hash;
75: }
76:
77: /**
78: * Vérifie une chaine avec un hash
79: * @param string $str Chaine en claire
80: * @param string $hashedStr Un hash
81: * @return boolean True si les deux correspondent , false sinon
82: */
83: public static function check($str, $hashedStr)
84: {
85: $hash = crypt($str, $hashedStr);
86: return $hash === $hashedStr;
87: }
88:
89: /**
90: * Génération d'un grain de sel pour le hashage
91: * @return string
92: */
93: private function generateSalt()
94: {
95: // Salt se forme aec : $2a$/2 digits/$/22 charactères parmis [./0-9a-Z]
96: $salt = Bcrypt::SALTBASE . $this->iterations . '$';
97: $bytes = $this->getRandomBytes(16);
98: $salt .= $this->getBlowfishSalt($bytes);
99:
100: return $salt;
101: }
102:
103: /**
104: * Génération de bytes aléatoires
105: * @param int $size Nombre de byte à générer
106: * @return bytes
107: */
108: private function getRandomBytes($size)
109: {
110: $bytes = '';
111:
112: // /dev/urandom est la meilleur source d'aléa , on test sa disponibilité :
113: if (is_readable('/dev/urandom'))
114: {
115: $handle = @fopen('/dev/urandom', 'rb');
116: if ($handle)
117: {
118: $bytes = fread($handle, $size);
119: }
120: }
121:
122: // Si pas de dev/urandom on essai avec openssl
123: if ($bytes === '' && function_exists('openssl_random_pseudo_bytes'))
124: $bytes = openssl_random_pseudo_bytes($size);
125:
126: //Si aucun des procédés dispo ou si la génération n'as pas la bonne taille
127: if (strlen($bytes) < $size)
128: {
129: $bytes = '';
130:
131: $this->random = md5(microtime() . $this->random);
132: $bytes .= md5($this->random, true); // Retour en byte et non string
133:
134: $bytes = substr($bytes, 0, $size);
135: }
136:
137: return $bytes;
138: }
139:
140: /**
141: * Transformation des bytes générés.
142: * Code original par phpass
143: * @see http://www.openwall.com/phpass/
144: * @param rawbytes $input
145: * @return string
146: */
147: private function getBlowfishSalt($input)
148: {
149: $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
150: $output = '';
151: $i = 0;
152: do {
153: $c1 = ord($input[$i++]);
154: $output .= $itoa64[$c1 >> 2];
155: $c1 = ($c1 & 0x03) << 4;
156: if ($i >= 16)
157: {
158: $output .= $itoa64[$c1];
159: break;
160: }
161:
162: $c2 = ord($input[$i++]);
163: $c1 |= $c2 >> 4;
164: $output .= $itoa64[$c1];
165: $c1 = ($c2 & 0x0f) << 2;
166:
167: $c2 = ord($input[$i++]);
168: $c1 |= $c2 >> 6;
169: $output .= $itoa64[$c1];
170: $output .= $itoa64[$c2 & 0x3f];
171: } while (1);
172:
173: return $output;
174: }
175:
176: }
177:
178: ?>
179: