【问题标题】:Convert python 2 check hash to python 3将 python 2 检查哈希转换为 python 3
【发布时间】:2019-06-12 14:11:50
【问题描述】:

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

PW 哈希如下所示:

PBKDF2$sha256$10000$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(在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 应用程序。

编辑

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

为赏金编辑

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

这个哈希:

PBKDF2$sha256$10000$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 脚本中使用。

【问题讨论】:

  • 我会试试h.update(x) => h.update(x.encode("utf-8"))
  • 我已经这样做了,您在阅读问题时错过了它。我知道它很长。感谢您的回复!
  • 顺便说一句,但出于好奇:如果您想将代码转换为 Python 3,为什么要保留 unicodexrange,而不是用它们的 Python 3 等效项替换它们?
  • 你应该做一些适当的调试:在两个代码中乱扔一些打印语句/函数,然后比较所有中间值(它可能比较strbytes,但内容应该相同) .这可能会告诉你脚本哪里出错了。
  • 我已经完成了所有这些,我不想发布所有打印和测试,因为它会包含更多代码并且看起来很乱。我希望有人会立即认识到这个问题,但看起来,这是一个非常困难的问题。感谢您的回复!

标签: python python-3.x hash python-unicode


【解决方案1】:

我想我成功了。我在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$sha256$10000$r+Gy8ewTkE7Qv0V7$uqmgaPgpaT1RSvFPMcGb6cGaFAhjyxE9'))

我建议不要继续细化和维护这个脚本,而是看看这个函数的 python3 实现。

参考文献

【讨论】:

  • 非常感谢,我犯了一个非常愚蠢的错误。我第一次使用这段代码(几年前),天知道为什么,我添加了一个脚本注释来修改编码,它是iso-8859-1,但是在我使用utf-8的脚本中,我覆盖了解码只有那里。当时这是我的第一个网络项目,现在我只是忽略了这条线。非常感谢您解决问题。很抱歉你不得不到处寻找完整的版本,你应该问我的。但是你帮了我很多。
  • 赏金罐将在 21 小时内发放。
  • 没问题。我学到了一些关于编码和2to3 包的限制的知识。我很高兴能帮上忙。
  • 注意:Python 3 already implements PBKDF2 hmac hashing,这个不需要重新实现。
  • hashlib.pbkdf2_hmac(hash_function, password.encode('ascii'), salt.encode('ascii'), int(cost_factor))[:len(hash_a)] 可以替换pbkdf2_bin()
猜你喜欢
  • 2015-01-21
  • 2020-12-09
  • 2019-05-31
  • 1970-01-01
  • 1970-01-01
  • 2015-06-13
  • 1970-01-01
  • 1970-01-01
  • 2023-01-13
相关资源
最近更新 更多