@@ -46,60 +46,25 @@ void php_password_algo_unregister(const char *ident) {
4646 zend_hash_str_del (& php_password_algos , ident , strlen (ident ));
4747}
4848
49- static int php_password_salt_to64 (const char * str , const size_t str_len , const size_t out_len , char * ret ) /* {{{ */
50- {
51- size_t pos = 0 ;
52- zend_string * buffer ;
53- if ((int ) str_len < 0 ) {
54- return FAILURE ;
55- }
56- buffer = php_base64_encode ((unsigned char * ) str , str_len );
57- if (ZSTR_LEN (buffer ) < out_len ) {
58- /* Too short of an encoded string generated */
59- zend_string_release_ex (buffer , 0 );
60- return FAILURE ;
61- }
62- for (pos = 0 ; pos < out_len ; pos ++ ) {
63- if (ZSTR_VAL (buffer )[pos ] == '+' ) {
64- ret [pos ] = '.' ;
65- } else if (ZSTR_VAL (buffer )[pos ] == '=' ) {
66- zend_string_free (buffer );
67- return FAILURE ;
68- } else {
69- ret [pos ] = ZSTR_VAL (buffer )[pos ];
70- }
71- }
72- zend_string_free (buffer );
73- return SUCCESS ;
74- }
75- /* }}} */
76-
7749static zend_string * php_password_make_salt (size_t length ) /* {{{ */
7850{
79- zend_string * ret , * buffer ;
51+ zend_string * salt ;
8052
81- if (length > (INT_MAX / 3 )) {
53+ // 64 ought to be enough for anybody
54+ if (length > 64 ) {
8255 zend_value_error ("Length is too large to safely generate" );
8356 return NULL ;
8457 }
8558
86- buffer = zend_string_alloc (length * 3 / 4 + 1 , 0 );
87- if (FAILURE == php_random_bytes_throw (ZSTR_VAL (buffer ), ZSTR_LEN (buffer ))) {
59+ salt = zend_string_alloc (length , 0 );
60+ if (FAILURE == php_random_bytes_throw (ZSTR_VAL (salt ), ZSTR_LEN (salt ))) {
8861 zend_value_error ("Unable to generate salt" );
89- zend_string_release_ex ( buffer , 0 );
62+ zend_string_free ( salt );
9063 return NULL ;
9164 }
9265
93- ret = zend_string_alloc (length , 0 );
94- if (php_password_salt_to64 (ZSTR_VAL (buffer ), ZSTR_LEN (buffer ), length , ZSTR_VAL (ret )) == FAILURE ) {
95- zend_value_error ("Generated salt too short" );
96- zend_string_release_ex (buffer , 0 );
97- zend_string_release_ex (ret , 0 );
98- return NULL ;
99- }
100- zend_string_release_ex (buffer , 0 );
101- ZSTR_VAL (ret )[length ] = 0 ;
102- return ret ;
66+ ZSTR_VAL (salt )[length ] = 0 ;
67+ return salt ;
10368}
10469/* }}} */
10570
@@ -113,6 +78,44 @@ static zend_string* php_password_get_salt(zval *unused_, size_t required_salt_le
11378
11479/* bcrypt implementation */
11580
81+ static bool php_password_bcrypt_salt_to64 (const char * in , const size_t in_len , const size_t out_len , char * out ) /* {{{ */
82+ {
83+ if (in_len != 16 || out_len != 22 ) {
84+ zend_value_error ("Unexpected bcrypt salt size" );
85+ return false;
86+ }
87+
88+ zend_string * buffer = php_base64_encode_ex ((const unsigned char * ) in , in_len , PHP_BASE64_NO_PADDING );
89+ if (!buffer ) {
90+ return false;
91+ }
92+
93+ if (ZSTR_LEN (buffer ) != out_len || ZSTR_VAL (buffer )[out_len ] == '=' ) {
94+ zend_string_free (buffer );
95+ return false;
96+ }
97+
98+ char * str = ZSTR_VAL (buffer );
99+ for (size_t i = 0 ; i < out_len ; i ++ ) {
100+ if (str [i ] == '+' ) out [i ] = '.' ;
101+ else out [i ] = str [i ];
102+ }
103+ zend_string_free (buffer );
104+
105+ // Bcrypt handles the last byte differently.
106+ // It's more complicated than this, but easy with the fixed length.
107+ switch (out [21 ]) {
108+ case 'A' : out [21 ] = '.' ; break ;
109+ case 'Q' : out [21 ] = 'O' ; break ;
110+ case 'g' : out [21 ] = 'e' ; break ;
111+ case 'w' : out [21 ] = 'u' ; break ;
112+ default : break ;
113+ }
114+
115+ return true;
116+ }
117+ /* }}} */
118+
116119static bool php_password_bcrypt_get_cost (const zend_string * hash , zend_long * cost ) {
117120 const char * h = ZSTR_VAL (hash );
118121
@@ -185,8 +188,7 @@ static bool php_password_bcrypt_verify(const zend_string *password, const zend_s
185188}
186189
187190static zend_string * php_password_bcrypt_hash (const zend_string * password , zend_array * options ) {
188- char hash_format [10 ];
189- size_t hash_format_len ;
191+ char salt64 [22 ];
190192 zend_string * result , * hash , * salt ;
191193 zval * zcost ;
192194 zend_long cost = PHP_PASSWORD_BCRYPT_COST ;
@@ -205,25 +207,26 @@ static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_a
205207 return NULL ;
206208 }
207209
208- hash_format_len = snprintf (hash_format , sizeof (hash_format ), "$2y$%02" ZEND_LONG_FMT_SPEC "$" , cost );
209- if (!(salt = php_password_get_salt (NULL , Z_UL (22 ), options ))) {
210+ if (!(salt = php_password_get_salt (NULL , Z_UL (16 ), options ))) {
210211 return NULL ;
211212 }
212- ZSTR_VAL (salt )[ZSTR_LEN (salt )] = 0 ;
213213
214- hash = zend_string_concat2 (hash_format , hash_format_len , ZSTR_VAL (salt ), ZSTR_LEN (salt ));
214+ if (!php_password_bcrypt_salt_to64 (ZSTR_VAL (salt ), ZSTR_LEN (salt ), sizeof (salt64 ), salt64 )) {
215+ zend_string_free (salt );
216+ return NULL ;
217+ }
215218
216- zend_string_release_ex (salt , 0 );
219+ zend_string_free (salt );
220+ hash = strpprintf (0 , "$2y$%02" ZEND_LONG_FMT_SPEC "$%s" , cost , salt64 );
217221
218- /* This cast is safe, since both values are defined here in code and cannot overflow */
222+ // This cast is safe, since both values are defined here in code and cannot overflow
219223 result = php_crypt (ZSTR_VAL (password ), (int )ZSTR_LEN (password ), ZSTR_VAL (hash ), (int )ZSTR_LEN (hash ), 1 );
220- zend_string_release_ex (hash , 0 );
221-
224+ zend_string_free (hash );
222225 if (!result ) {
223226 return NULL ;
224227 }
225228
226- if (ZSTR_LEN (result ) < 13 ) {
229+ if (ZSTR_LEN (result ) != 60 ) {
227230 zend_string_free (result );
228231 return NULL ;
229232 }
0 commit comments