将 python 2 校验哈希转换为 python 3

Convert python 2 check hash to python 3

我需要将用户及其密码从 python 2 系统转移到 python 3 系统。

PW 哈希如下所示:

PBKDF2$sha256000$KlCW+ewerd19fS9f$l+5LgvcWTzghtz77086MSVG+q5z2Lij

在 python 2 系统中,我使用这些函数来检查哈希值:

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

还有这个:

_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]

到目前为止我做了什么:

将代码复制到我的 python 3 个脚本后,我不得不更改一些变量:

izip -> zip

我保留了 unicode:from past.builtins import unicode

我保留了 xrange: from past.builtins import xrange

现在我没有脚本错误,但是在执行脚本后我在这里(在pbkdf2_bin函数中)出现错误:

rv = u = _pseudorandom(salt + _pack_int(block))
TypeError: must be str, not bytes

所以我通过将字节转换为 str 来修复它:

rv = u = _pseudorandom(salt + _pack_int(block).decode('utf-8'))

现在出现下一个错误(在pbkdf2_bin函数中):

h.update(x)
TypeError: Unicode-objects must be encoded before hashing

我还用正确的编码解决了这个问题:

h.update(x.encode('utf-8'))

下一个错误:

  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

_pseudorandom 的 return 值存在问题(在 pbkdf2_bin 函数中)。它必须被转换,所以我修复了它:

可能问题出在这里

#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

最后一个错误在check_hash函数的末尾:

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 是一个整数,而 chat_b 不是。我能够通过将 char_a 转换为真正的 char:

来解决这个问题
for char_a, char_b in zip(hash_a, hash_b):
    diff |= ord(chr(char_a)) ^ ord(char_b)

终于没有报错了,但是提示密码输入错误, 所以某处是一个错误,因为我知道密码是正确的并且它适用于 python 2 app.

编辑

有人提到2to3库,所以我试了一下。总而言之,它做了同样的事情,我已经做了,问题是一样的。

编辑赏金

总结一下。我上面发布的 2 个函数来自 python 2 并在 python 2 中工作。

这个散列:

PBKDF2$sha256000$r+Gy8ewTkE7Qv0V7$uqmgaPgpaT1RSvFPMcGb6cGaFAhjyxE9

这个密码是:Xs12'io!12

我可以在 python 2 应用程序上使用此密码正确登录。

现在我想在 python 3 中使用相同的两个函数,但是即使我解决了所有错误,它仍然告诉我密码错误。

进口:

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

这些导入在 python 2 脚本中使用。

我想我成功了。我在 github 上找到了原始代码,它帮助我创建了一个测试用例。在查看问题后,我遵循了您提出的解决方案,并将字节解码为 iso-8859-1 而不是 utf-8 并且它起作用了。

from struct import Struct
from base64 import b64decode
import hashlib
import hmac
from operator import xor
from itertools import starmap


_pack_int = Struct('>I').pack


def check_hash(password, hash_):
    """Check a password against an existing hash."""
    if isinstance(password, str):
        password = password.encode('utf-8')
    algorithm, hash_function, cost_factor, salt, hash_a = hash_.split('$')
    assert algorithm == 'PBKDF2'
    hash_a = b64decode(hash_a).decode('iso-8859-1')
    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 zip(hash_a, hash_b):
        diff |= ord(char_a) ^ ord(char_b)
    return diff == 0


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 list(map(ord, h.digest().decode('iso-8859-1')))
    buf = []
    for block in range(1, -(-keylen // mac.digest_size) + 1):
        myx = salt.encode('utf-8') + _pack_int(block)
        rv = u = _pseudorandom(myx)
        for i in range(iterations - 1):
            u = _pseudorandom(''.join(map(chr, u)).encode('iso-8859-1'))
            rv = starmap(xor, zip(rv, u))
        buf.extend(rv)
    return ''.join(map(chr, buf))[:keylen]


if __name__ == "__main__":
    print(check_hash('Xs12\'io!12', 'PBKDF2$sha256000$r+Gy8ewTkE7Qv0V7$uqmgaPgpaT1RSvFPMcGb6cGaFAhjyxE9'))

与其继续改进并维护此脚本,我建议查看此相同功能的 python3 实现。

参考资料