@@ -1148,36 +1148,174 @@ public function getDatabaseVersion($major_version_only = false) {
11481148 * Get a mysql password hash
11491149 *
11501150 * @access public
1151- * @param string cleartext password
1151+ * @param string $password cleartext password
1152+ * @param string $hash_type MySQL hash type to use. either mysql_native_password or caching_sha2_password
11521153 * @return string Password hash
11531154 */
11541155
1155- public function getPasswordHash ($ password ) {
1156+ public function getPasswordHash ($ password , $ hash_type = 'mysql_native_password ' ) {
1157+ if ($ hash_type == 'caching_sha2_password ' ) {
1158+ $ password_hash = $ this ->mysqlSha256Crypt ($ password , $ this ->genSalt (20 ), 5000 );
1159+ } else {
1160+ $ password_hash = '* ' .strtoupper (sha1 (sha1 ($ password , true )));
1161+ }
11561162
1157- $ password_type = 'password ' ;
1163+ return $ password_hash ;
1164+ }
11581165
1159- /* Disabled until caching_sha2_password is implemented
1160- if($this->getDatabaseType() == 'mysql' && $this->getDatabaseVersion(true) >= 8) {
1161- // we are in MySQL 8 mode
1162- $tmp = $this->queryOneRecord("show variables like 'default_authentication_plugin'");
1163- if($tmp['default_authentication_plugin'] == 'caching_sha2_password') {
1164- $password_type = 'caching_sha2_password';
1166+ /**
1167+ * @param $size int length of salt in bytes
1168+ *
1169+ * @return string
1170+ */
1171+ private function genSalt ($ size )
1172+ {
1173+ $ salt = random_bytes ($ size );
1174+ if ($ salt === false ) {
1175+ throw new Exception ('Cannot generate salt. ' );
1176+ }
1177+ for ($ i = 0 ; $ i < $ size ; $ i ++) {
1178+ $ ord = ord ($ salt [$ i ]) & 0x7f ;
1179+ if ($ ord < 32 ) {
1180+ $ ord += 32 ;
11651181 }
1182+ if ($ ord == 36 /* $ */ ) {
1183+ $ ord += 1 ;
1184+ }
1185+ $ salt [$ i ] = chr ($ ord );
11661186 }
1167- */
11681187
1169- if ($ password_type == 'caching_sha2_password ' ) {
1170- /*
1171- caching_sha2_password hashing needs to be implemented, have not
1172- found valid PHP implementation for the new password hash type.
1173- */
1174- } else {
1175- $ password_hash = '* ' .strtoupper (sha1 (sha1 ($ password , true )));
1188+ return $ salt ;
1189+ }
1190+
1191+ /**
1192+ * this is the SHA256 algorithm of the crypt unix call – the only difference is that we do not truncate the salt to 16 chars
1193+ * @see https://www.akkadia.org/drepper/SHA-crypt.txt
1194+ * @see https://github.com/mysql/mysql-server/blob/trunk/mysys/crypt_genhash_impl.cc
1195+ *
1196+ * @param string $plaintext the plain text password
1197+ * @param string $salt the raw salt (needs to be 20 bytes long)
1198+ * @param int $rounds number of rounds. MySQL default is 5000. Must be between 1000 and 4095000 (0xFFF * 1000)
1199+ *
1200+ * @return string hashed password in MySQL format
1201+ */
1202+ private function mysqlSha256Crypt ($ plaintext , $ salt , $ rounds )
1203+ {
1204+ $ plaintext_len = strlen ($ plaintext );
1205+ $ salt_len = strlen ($ salt );
1206+
1207+ // 1
1208+ $ ctxA = hash_init ('sha256 ' );
1209+ // 2
1210+ hash_update ($ ctxA , $ plaintext );
1211+ // 3
1212+ hash_update ($ ctxA , $ salt );
1213+ // 4
1214+ $ ctxB = hash_init ('sha256 ' );
1215+ // 5
1216+ hash_update ($ ctxB , $ plaintext );
1217+ // 6
1218+ hash_update ($ ctxB , $ salt );
1219+ // 7
1220+ hash_update ($ ctxB , $ plaintext );
1221+ // 8
1222+ $ B = hash_final ($ ctxB , true );
1223+ // 9
1224+ for ($ i = $ plaintext_len ; $ i > 32 ; $ i -= 32 ) {
1225+ hash_update ($ ctxA , $ B );
11761226 }
1227+ // 10
1228+ hash_update ($ ctxA , substr ($ B , 0 , $ i ));
1229+ // 11
1230+ for ($ i = $ plaintext_len ; $ i > 0 ; $ i >>= 1 ) {
1231+ if (($ i & 1 ) != 0 ) {
1232+ hash_update ($ ctxA , $ B );
1233+ } else {
1234+ hash_update ($ ctxA , $ plaintext );
1235+ }
1236+ }
1237+ // 12
1238+ $ A = hash_final ($ ctxA , true );
1239+ // 13
1240+ $ ctxDP = hash_init ('sha256 ' );
1241+ // 14
1242+ for ($ i = 0 ; $ i < $ plaintext_len ; $ i ++) {
1243+ hash_update ($ ctxDP , $ plaintext );
1244+ }
1245+ // 15
1246+ $ DP = hash_final ($ ctxDP , true );
1247+ // 16
1248+ $ P = "" ;
1249+ for ($ i = $ plaintext_len ; $ i > 32 ; $ i -= 32 ) {
1250+ $ P .= $ DP ;
1251+ }
1252+ $ P .= substr ($ DP , 0 , $ i );
1253+ // 17
1254+ $ ctxDS = hash_init ('sha256 ' );
1255+ // 18
1256+ for ($ i = 0 ; $ i < 16 + ord ($ A [0 ]); $ i ++) {
1257+ hash_update ($ ctxDS , $ salt );
1258+ }
1259+ // 19
1260+ $ DS = hash_final ($ ctxDS , true );
1261+ // 20
1262+ $ S = "" ;
1263+ for ($ i = $ salt_len ; $ i >= 32 ; $ i -= 32 ) {
1264+ $ S .= $ DS ;
1265+ }
1266+ $ S .= substr ($ DS , 0 , $ i );
1267+ // 21
1268+ $ C = "" ;
1269+ for ($ i = 0 ; $ i < $ rounds ; $ i ++) {
1270+ $ ctxC = hash_init ('sha256 ' );
1271+ if (($ i & 1 ) != 0 ) {
1272+ hash_update ($ ctxC , $ P );
1273+ } else {
1274+ hash_update ($ ctxC , $ i == 0 ? $ A : $ C );
1275+ }
11771276
1178- return $ password_hash ;
1179- }
1277+ if ($ i % 3 != 0 ) {
1278+ hash_update ($ ctxC , $ S );
1279+ }
1280+
1281+ if ($ i % 7 != 0 ) {
1282+ hash_update ($ ctxC , $ P );
1283+ }
1284+
1285+ if (($ i & 1 ) != 0 ) {
1286+ hash_update ($ ctxC , $ i == 0 ? $ A : $ C );
1287+ } else {
1288+ hash_update ($ ctxC , $ P );
1289+ }
1290+ $ C = hash_final ($ ctxC , true );
1291+ }
11801292
1293+ // 22
1294+ $ b64result = str_repeat (' ' , 43 );
1295+ $ p = 0 ;
1296+ $ b64_from_24bit = function ($ B2 , $ B1 , $ B0 , $ N ) use (&$ b64result , &$ p ) {
1297+ $ w = ($ B2 << 16 ) | ($ B1 << 8 ) | $ B0 ;
1298+ $ n = $ N ;
1299+ while (--$ n >= 0 ) {
1300+ $ b64result [$ p ++] = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz " [$ w & 0x3f ];
1301+ $ w = $ w >> 6 ;
1302+ }
1303+ };
1304+ $ b64_from_24bit (ord ($ C [0 ]), ord ($ C [10 ]), ord ($ C [20 ]), 4 );
1305+ $ b64_from_24bit (ord ($ C [21 ]), ord ($ C [1 ]), ord ($ C [11 ]), 4 );
1306+ $ b64_from_24bit (ord ($ C [12 ]), ord ($ C [22 ]), ord ($ C [2 ]), 4 );
1307+ $ b64_from_24bit (ord ($ C [3 ]), ord ($ C [13 ]), ord ($ C [23 ]), 4 );
1308+ $ b64_from_24bit (ord ($ C [24 ]), ord ($ C [4 ]), ord ($ C [14 ]), 4 );
1309+ $ b64_from_24bit (ord ($ C [15 ]), ord ($ C [25 ]), ord ($ C [5 ]), 4 );
1310+ $ b64_from_24bit (ord ($ C [6 ]), ord ($ C [16 ]), ord ($ C [26 ]), 4 );
1311+ $ b64_from_24bit (ord ($ C [27 ]), ord ($ C [7 ]), ord ($ C [17 ]), 4 );
1312+ $ b64_from_24bit (ord ($ C [18 ]), ord ($ C [28 ]), ord ($ C [8 ]), 4 );
1313+ $ b64_from_24bit (ord ($ C [9 ]), ord ($ C [19 ]), ord ($ C [29 ]), 4 );
1314+ $ b64_from_24bit (0 , ord ($ C [31 ]), ord ($ C [30 ]), 3 );
1315+
1316+ // we do not truncate $salt to 16 chars since MySQL does not do that and uses 20 bytes salts
1317+ return sprintf ('$A$%03x$%s%s ' , $ rounds / 1000 , $ salt , $ b64result );
1318+ }
11811319
11821320}
11831321
0 commit comments