1<?php 2/************************* 3 Coppermine Photo Gallery 4 ************************ 5 Copyright (c) 2003-2016 Coppermine Dev Team 6 v1.0 originally written by Gregory Demar 7 8 This program is free software; you can redistribute it and/or modify 9 it under the terms of the GNU General Public License version 3 10 as published by the Free Software Foundation. 11 12 ******************************************** 13 Coppermine version: 1.6.03 14 $HeadURL$ 15**********************************************/ 16 17/* 18 * Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm). 19 * Copyright (c) 2013, Taylor Hornby 20 * All rights reserved. 21 * 22 * Redistribution and use in source and binary forms, with or without 23 * modification, are permitted provided that the following conditions are met: 24 * 25 * 1. Redistributions of source code must retain the above copyright notice, 26 * this list of conditions and the following disclaimer. 27 * 28 * 2. Redistributions in binary form must reproduce the above copyright notice, 29 * this list of conditions and the following disclaimer in the documentation 30 * and/or other materials provided with the distribution. 31 * 32 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 33 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 34 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 35 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 36 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 37 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 38 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 39 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 40 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 41 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 42 * POSSIBILITY OF SUCH DAMAGE. 43 */ 44 45// These constants may be changed without breaking existing hashes. 46define("PBKDF2_HASH_ALGORITHM", isset($CONFIG['pbkdf2_hash_algorithm']) ? $CONFIG['pbkdf2_hash_algorithm'] : "sha256"); 47define("PBKDF2_ITERATIONS", isset($CONFIG['pbkdf2_iterations']) ? $CONFIG['pbkdf2_iterations'] : 1000); 48define("PBKDF2_SALT_BYTE_SIZE", isset($CONFIG['pbkdf2_salt_byte_size']) ? $CONFIG['pbkdf2_salt_byte_size'] : 24); 49define("PBKDF2_HASH_BYTE_SIZE", isset($CONFIG['pbkdf2_hash_byte_size']) ? $CONFIG['pbkdf2_hash_byte_size'] : 24); 50 51define("HASH_SECTIONS", 4); 52define("HASH_ALGORITHM_INDEX", 0); 53define("HASH_ITERATION_INDEX", 1); 54define("HASH_SALT_INDEX", 2); 55define("HASH_PBKDF2_INDEX", 3); 56 57function cpg_password_create_hash($password) 58{ 59 // format: algorithm:iterations:salt:hash 60 if (function_exists('random_bytes')) { 61 $vect = random_bytes(PBKDF2_SALT_BYTE_SIZE); 62 } else { 63 $vect = mcrypt_create_iv(PBKDF2_SALT_BYTE_SIZE, MCRYPT_DEV_URANDOM); 64 } 65 $salt = base64_encode($vect); 66 return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" . $salt . ":" . 67 base64_encode(pbkdf2( 68 PBKDF2_HASH_ALGORITHM, 69 $password, 70 $salt, 71 PBKDF2_ITERATIONS, 72 PBKDF2_HASH_BYTE_SIZE, 73 true 74 )); 75} 76 77function cpg_password_validate($password, $correct_hash) 78{ 79 if (is_array($correct_hash)) { 80 $params = array( 81 HASH_ALGORITHM_INDEX => $correct_hash['user_password_hash_algorithm'], 82 HASH_ITERATION_INDEX => $correct_hash['user_password_iterations'], 83 HASH_SALT_INDEX => $correct_hash['user_password_salt'], 84 HASH_PBKDF2_INDEX => $correct_hash['user_password'], 85 ); 86 } else { 87 $params = explode(":", $correct_hash); 88 } 89 if(count($params) < HASH_SECTIONS) 90 return false; 91 $pbkdf2 = base64_decode($params[HASH_PBKDF2_INDEX]); 92 return slow_equals( 93 $pbkdf2, 94 pbkdf2( 95 $params[HASH_ALGORITHM_INDEX], 96 $password, 97 $params[HASH_SALT_INDEX], 98 (int)$params[HASH_ITERATION_INDEX], 99 strlen($pbkdf2), 100 true 101 ) 102 ); 103} 104 105function cpg_password_create_update_string($password) 106{ 107 $password_params = explode(':', cpg_password_create_hash($password)); 108 return "user_password = '{$password_params[HASH_PBKDF2_INDEX]}', user_password_salt = '{$password_params[HASH_SALT_INDEX]}', user_password_hash_algorithm = '{$password_params[HASH_ALGORITHM_INDEX]}', user_password_iterations = '{$password_params[HASH_ITERATION_INDEX]}'"; 109} 110 111// Compares two strings $a and $b in length-constant time. 112function slow_equals($a, $b) 113{ 114 $diff = strlen($a) ^ strlen($b); 115 for($i = 0; $i < strlen($a) && $i < strlen($b); $i++) 116 { 117 $diff |= ord($a[$i]) ^ ord($b[$i]); 118 } 119 return $diff === 0; 120} 121 122/* 123 * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt 124 * $algorithm - The hash algorithm to use. Recommended: SHA256 125 * $password - The password. 126 * $salt - A salt that is unique to the password. 127 * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000. 128 * $key_length - The length of the derived key in bytes. 129 * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise. 130 * Returns: A $key_length-byte key derived from the password and salt. 131 * 132 * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt 133 * 134 * This implementation of PBKDF2 was originally created by https://defuse.ca 135 * With improvements by http://www.variations-of-shadow.com 136 */ 137function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false) 138{ 139 $algorithm = strtolower($algorithm); 140 if(!in_array($algorithm, hash_algos(), true)) 141 trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR); 142 if($count <= 0 || $key_length <= 0) 143 trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR); 144 145 if (function_exists("hash_pbkdf2")) { 146 // The output length is in NIBBLES (4-bits) if $raw_output is false! 147 if (!$raw_output) { 148 $key_length = $key_length * 2; 149 } 150 return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output); 151 } 152 153 $hash_length = strlen(hash($algorithm, "", true)); 154 $block_count = ceil($key_length / $hash_length); 155 156 $output = ""; 157 for($i = 1; $i <= $block_count; $i++) { 158 // $i encoded as 4 bytes, big endian. 159 $last = $salt . pack("N", $i); 160 // first iteration 161 $last = $xorsum = hash_hmac($algorithm, $last, $password, true); 162 // perform the other $count - 1 iterations 163 for ($j = 1; $j < $count; $j++) { 164 $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true)); 165 } 166 $output .= $xorsum; 167 } 168 169 if($raw_output) 170 return substr($output, 0, $key_length); 171 else 172 return bin2hex(substr($output, 0, $key_length)); 173} 174//EOF