I need to transfer users and their passwords from a python 2 system to a python 3 system.
The PW hash looks like this:
PBKDF2$sha256$10000$KlCW+ewerd19fS9f$l+5LgvcWTzghtz77086MSVG+q5z2Lij
In the python 2 system I used these functions to check hashes:
def check_hash(password, hash_):
"""Check a password against an existing hash."""
if isinstance(password, unicode):
password = password.encode('utf-8')
algorithm, hash_function, cost_factor, salt, hash_a = hash_.split('$')
assert algorithm == 'PBKDF2'
hash_a = b64decode(hash_a)
hash_b = pbkdf2_bin(password, salt, int(cost_factor), len(hash_a),
getattr(hashlib, hash_function))
assert len(hash_a) == len(hash_b) # we requested this from pbkdf2_bin()
# Same as "return hash_a == hash_b" but takes a constant time.
# See http://carlos.bueno.org/2011/10/timing.html
diff = 0
for char_a, char_b in izip(hash_a, hash_b):
diff |= ord(char_a) ^ ord(char_b)
return diff == 0
and this:
_pack_int = Struct('>I').pack
def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None):
"""Returns a binary digest for the PBKDF2 hash algorithm of `data`
with the given `salt`. It iterates `iterations` time and produces a
key of `keylen` bytes. By default SHA-1 is used as hash function,
a different hashlib `hashfunc` can be provided.
"""
hashfunc = hashfunc or hashlib.sha1
mac = hmac.new(data, None, hashfunc)
def _pseudorandom(x, mac=mac):
h = mac.copy()
h.update(x)
return map(ord, h.digest())
buf = []
for block in xrange(1, -(-keylen // mac.digest_size) + 1):
rv = u = _pseudorandom(salt + _pack_int(block))
for i in xrange(iterations - 1):
u = _pseudorandom(''.join(map(chr, u)))
rv = starmap(xor, izip(rv, u))
buf.extend(rv)
return ''.join(map(chr, buf))[:keylen]
What have I done so far:
Right after copying the code into my python 3 scripts I had to change some variables:
izip -> zip
I kept unicode: from past.builtins import unicode
I kept xrange: from past.builtins import xrange
Now I had no script errors, but after executing the script I got an error here (in the pbkdf2_bin function):
rv = u = _pseudorandom(salt + _pack_int(block))
TypeError: must be str, not bytes
So I fixed it by converting bytes to str:
rv = u = _pseudorandom(salt + _pack_int(block).decode('utf-8'))
Now the next error appears (in the pbkdf2_bin function):
h.update(x)
TypeError: Unicode-objects must be encoded before hashing
I also fixed this with the proper encoding:
h.update(x.encode('utf-8'))
Next error:
File "C:\Users\User\Eclipse-Workspace\Monteurzimmer-Remastered\hash_passwords.py", line 123, in check_hash
getattr(hashlib, hash_function))
File "C:\Users\User\Eclipse-Workspace\Monteurzimmer-Remastered\pbkdf2.py", line 125, in pbkdf2_bin_old_2
u = _pseudorandom(''.join(map(chr, u)))
TypeError: ord() expected string of length 1, but int found
There was an issue with the return value of _pseudorandom (in the pbkdf2_bin function). It had to be converted, so I fixed it:
Maybe the issue is here
#return map(ord, h.digest()) # throws the error
#return map(int, h.digest()) # returns nothing (length 0)
#return list(map(ord, h.digest())) # throws the error
return list(map(int, h.digest())) # seems to work with the correct length
The last error is at the end of the check_hash function:
File "C:\Users\User\Eclipse-Workspace\Monteurzimmer-Remastered\hash_passwords.py", line 129, in check_hash
diff |= ord(char_a) ^ ord(char_b)
TypeError: ord() expected string of length 1, but int found
for char_a, char_b in zip(hash_a, hash_b):
diff |= ord(char_a) ^ ord(char_b)
char_a is an integer and chat_b is not. I was able to fix this by converting char_a to the real char:
for char_a, char_b in zip(hash_a, hash_b):
diff |= ord(chr(char_a)) ^ ord(char_b)
Finally I had no errors, but it tells me the entered password is wrong, so somewhere is an error, because I know that the password is correct and it works on the python 2 app.
EDIT
Someone mentioned the 2to3 library, so I tried it. All in all it has done the same things, which I have done already and the problem is the same.
EDIT for bounty
To sum it up. The 2 functions which I posted above come from python 2 and work in python 2.
This hash:
PBKDF2$sha256$10000$r+Gy8ewTkE7Qv0V7$uqmgaPgpaT1RSvFPMcGb6cGaFAhjyxE9
is this password: Xs12'io!12
I can correctly login with this password on my python 2 app.
Now I want to use the same two functions in python 3, but eventhough I worked through all errors, it tells me the password is wrong.
The imports:
import hmac
import hashlib
from struct import Struct
from operator import xor
from itertools import izip, starmap
from base64 import b64encode, b64decode
import hashlib
from itertools import izip
from os import urandom
import random
import string
These imports are used in the python 2 script.
h.update(x)=>h.update(x.encode("utf-8"))unicodeandxrange, instead of replacing them with their Python 3 equivalents?strandbytes, but the contents should be the same). That may show you where the scripts go wrong.