Why does using salted hash on python and php give me different results?

161 views Asked by At

I ran into a problem in translating code from PHP to Python. Initially, there is a PHP code that creates a salted hash of a password with verification, and this code works fine and performs its functions. But I had a need to transfer this code to python. However, the resulting final hash does not match the one obtained on PHP. Help me please.

Here is the PHP code that works fine:

<?php
$username = 'test';
$password = '1234';
$salt = '5CD6A52E4F7046241C1607233395461D69D8C21709DD661FA1E9A24C8DF39647';

$g = gmp_init(7);
$N = gmp_init('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16);

$h1 = sha1(strtoupper($username . ':' . $password), TRUE);
$h2 = sha1($salt . $h1, TRUE);
$h2 = gmp_import($h2, 1, GMP_LSW_FIRST);

// g^h2 mod N
$verifier = gmp_powm($g, $h2, $N);

// convert back to a byte array (little-endian)
$verifier = gmp_export($verifier, 1, GMP_LSW_FIRST);

// pad to 32 bytes, remember that zeros go on the end in little-endian!
$verifier = str_pad($verifier, 32, chr(0), STR_PAD_RIGHT);

ECHO 'Verifier FINAL ', $verifier;
?>

Here's the python code I'm stuck on that doesn't produce the correct hash:

import hashlib
import secrets
import sys

USERNAME = 'test'
PASSWORD = '1234'
salt = '5CD6A52E4F7046241C1607233395461D69D8C21709DD661FA1E9A24C8DF39647'

g = 7
N = '894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7'

N = int('{0:08b}'.format(int(N, 16)), 2)

h1 = str(hashlib.sha1(str((USERNAME + ':' + PASSWORD).upper()).encode('utf-8')).hexdigest())
h2 = str(hashlib.sha1(str(salt + h1).encode('utf-8')).hexdigest())
h2 = int('{0:08b}'.format(int(h2, 16)), 2)

verifier = pow(g, h2, N)
verifier = format(verifier, "x").upper()
verifier = verifier.ljust(64, '0')

print('Verifier FINAL : ', verifier)
print('Verifier should be: E08847151054CB20CCD00A546A85D9A4E6EB882EDAB678DD8C68BB28DA22C678')
1

There are 1 answers

0
Saint On

That's it, I managed to completely write a library for generating hashes and checking them.

import hashlib
import secrets

ACCOUNT_NAME = 'MyTestLogin'
ACCOUNT_PASSWORD = 'MyTestPassword'

def calculateSRP6Verifier(ACCOUNT_NAME: str, ACCOUNT_PASSWORD: str, SALT: str):
    g = int(7)
    N = int('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16)
    h1 = bytes.fromhex(hashlib.sha1(((ACCOUNT_NAME + ':' + ACCOUNT_PASSWORD).upper()).encode()).hexdigest())
    h2 = int(bytes.fromhex(hashlib.sha1(bytes.fromhex(SALT) + h1).hexdigest())[::-1].hex(), 16)
    VERIFIER = bytes.fromhex(format(pow(g, h2, N), 'X').ljust(64, '0'))[::-1].hex().upper()
    return VERIFIER

def getRegistrationData(ACCOUNT_NAME: str, ACCOUNT_PASSWORD: str):
    SALT = secrets.token_hex(32)
    VERIFIER = calculateSRP6Verifier(ACCOUNT_NAME, ACCOUNT_PASSWORD, SALT)
    return SALT.upper(), VERIFIER

def verifySRP6(ACCOUNT_NAME: str, ACCOUNT_PASSWORD: str, SALT: str, VERIFIER: str):
    g = int(7)
    N = int('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16)
    x = int(bytes.fromhex(hashlib.sha1(bytes.fromhex(SALT) + bytes.fromhex(hashlib.sha1(((ACCOUNT_NAME + ':' + ACCOUNT_PASSWORD).upper()).encode()).hexdigest())).hexdigest())[::-1].hex(), 16)
    VERIFIER = bytes.fromhex(format(pow(g, x, N), "X").ljust(64, '0'))[::-1].hex().upper()
    return VERIFIER
    
RESULT = getRegistrationData(ACCOUNT_NAME, ACCOUNT_PASSWORD)
print('This is SRP6 crypto library for Trinity Core 335a World of Warcraft server.')
print('')
print('Login   : ' + ACCOUNT_NAME)
print('Password: ' + ACCOUNT_PASSWORD)
print('')
print('salt    : ' + RESULT[0])
print('verifier: ' + RESULT[1])

# Note, that SALT and VERIFIER saved to the database as a binary string (not as hexadecimal, that returned from getRegistrationData() function)!
# Therefore, when inserting data into the MySQL database, you need to use such a query:
sql = "INSERT INTO account (username, salt, verifier, email) VALUES ('" + ACCOUNT_NAME.upper() + "', X'" + SALT + "', X'" + VERIFIER + "', '" + ACCOUNT_EMAIL.upper() + "')"



# This is simple example, how registration data is being verified.
#
#CODE - is return from verifySRP6() function
#VERIFIER and SALT - saved data from the MySQL database, from account table, converted from binary to hexadecimal string in upper() mode.
# MySQL query must be:
#    cursor.execute("SELECT salt FROM account WHERE username = '" + ACCOUNT_NAME.upper() + "'")
#    SALT = (cursor.fetchone()['salt']).hex().upper()
#    cursor.execute("SELECT verifier FROM account WHERE username = '" + ACCOUNT_NAME.upper() + "'")
#    VERIFIER = (cursor.fetchone()['verifier']).hex().upper()
#
#
#CODE = verifySRP6(ACCOUNT_NAME, ACCOUNT_PASSWORD, SALT, VERIFIER)
#print('check   :', CODE)
#if(VERIFIER == CODE):
#    print('Password is OK!')
#else:
#    print('Wrong password!')