【问题标题】:SHA 512 crypt output written with Python code is different from mkpasswd用 Python 代码编写的 SHA 512 crypt 输出与 mkpasswd 不同
【发布时间】:2021-10-25 12:39:54
【问题描述】:

运行 mkpasswd -m sha-512 -S salt1234 password 会产生以下结果:

$6$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81

我有这个 Python 代码的 sn-p,我认为它会输出相同的结果,但事实并非如此:

import hashlib, base64
print(base64.b64encode(hashlib.sha512('password' + 'salt1234').digest()))

反而会导致:

nOkBUt6l7zlKAfjtk1EfB0TmckXfDiA4FPLcpywOLORZ1PWQK4+PZVEiT4+9rFjqR3xnaruZBiRjDGcDpxxTig==

不知道我做错了什么。

我的另一个问题是,我如何告诉 sha512 函数进行自定义回合。似乎只需要 1 个参数。

【问题讨论】:

  • 我觉得passlib可能更接近你想要的pythonhosted.org/passlib/lib/passlib.hash.sha512_crypt.html#hashlib和crypto(3)也有很大区别
  • SHA512-crypt 规范:akkadia.org/drepper/sha-crypt.html
  • 如果你想使用 passlib 你也可以指定轮数,以匹配你当前的输出sha512_crypt.encrypt(password, salt=salt, rounds=5000)
  • 您需要花时间阅读文档、有关散列和密码处理的信息。安全漏洞是不同的,代码似乎可以工作,但它是不安全的。错误或滥用可能意味着没有安全性。您需要了解代码在做什么以及做什么。

标签: python python-2.7 hash sha512 hashlib


【解决方案1】:

mkpasswdcrypt() function 的前端。我不认为这是一个直接的 SHA512 哈希。

一点研究指向specification for SHA256-crypt and SHA512-crypt,这表明哈希默认应用了 5000 次。您可以使用-R 切换到mkpasswd 来指定不同的轮数; -R 5000 确实为您提供了相同的输出:

$ mkpasswd -m sha-512 -S salt1234 -R 5000 password
$6$rounds=5000$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81

命令行工具提供的最小轮数为 1000:

$ mkpasswd -m sha-512 -S salt1234 -R 999 password
$6$rounds=1000$salt1234$SVDFHbJXYrzjGi2fA1k3ws01/D9q0ZTAh1KfRF5.ehgjVBqfHUaKqfynXefJ4DxIWxkMAITYq9mmcBl938YQ//
$ mkpasswd -m sha-512 -S salt1234 -R 1 password
$6$rounds=1000$salt1234$SVDFHbJXYrzjGi2fA1k3ws01/D9q0ZTAh1KfRF5.ehgjVBqfHUaKqfynXefJ4DxIWxkMAITYq9mmcBl938YQ//

该算法涉及更多,需要您创建多个摘要。您可以改为通过crypt.crypt() function 访问C crypt() 函数,并以与mkpasswd 命令行相同的方式驱动它。

SHA512-crypt 方法是否可用取决于您的平台; crypt 模块的 Python 3 版本提供了一个 crypt.methods list,它告诉您您的平台支持哪些方法。由于这使用了与 mkpasswd 完全相同的库,因此您的操作系统显然确实支持 SHA512-crypt 并且 Python 也可以访问。

您需要在 salt 前面加上 '$6$ 以指定不同的方法。您可以通过在'$6$' 字符串和您的盐之间添加一个'rounds=<N>$' 字符串来指定轮数:

import crypt
import os
import string

try:  # 3.6 or above
    from secrets import choice as randchoice
except ImportError:
    from random import SystemRandom
    randchoice = SystemRandom().choice

def sha512_crypt(password, salt=None, rounds=None):
    if salt is None:
        salt = ''.join([randchoice(string.ascii_letters + string.digits)
                        for _ in range(8)])

    prefix = '$6$'
    if rounds is not None:
        rounds = max(1000, min(999999999, rounds or 5000))
        prefix += 'rounds={0}$'.format(rounds)
    return crypt.crypt(password, prefix + salt)

这将产生与mkpasswd 命令行相同的输出:

>>> sha512_crypt('password', 'salt1234')
'$6$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81'
>>> sha512_crypt('password', 'salt1234', rounds=1000)
'$6$rounds=1000$salt1234$SVDFHbJXYrzjGi2fA1k3ws01/D9q0ZTAh1KfRF5.ehgjVBqfHUaKqfynXefJ4DxIWxkMAITYq9mmcBl938YQ//'

【讨论】:

    【解决方案2】:

    你需要使用crypt.crypt:

    >>> import crypt
    >>> crypt.crypt('password', '$6$' + 'salt1234')
    '$6$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81'
    

    【讨论】:

    • 但是为什么我写的代码没有达到我期望的效果。 hashlib.sha512 函数与 SHA-512 crypt 不一样吗?我试图从高层次的角度理解 SHA-512 crypt 算法。
    • @user1720897,阅读 Martijn Pieters 的评论。
    【解决方案3】:

    这里是基于规范的sha512_crypt函数的纯python3实现。 这仅用于说明,请始终使用crypt.crypt

    import hashlib, base64
    
    SHUFFLE_SHA512_INDICES = [
      42, 21,  0,     1, 43, 22,    23,  2, 44,    45, 24,  3,     4, 46, 25,
      26,  5, 47,    48, 27,  6,     7, 49, 28,    29,  8, 50,    51, 30,  9,
      10, 52, 31,    32, 11, 53,    54, 33, 12,    13, 55, 34,    35, 14, 56,
      57, 36, 15,    16, 58, 37,    38, 17, 59,    60, 39, 18,    19, 61, 40,
      41, 20, 62,    63
    ]
    
    def shuffle_sha512(data):
      return bytes(data[i] for i in SHUFFLE_SHA512_INDICES)
    
    def extend_by_repeat(data, length):
      return (data * (length // len(data) + 1))[:length]
    
    CUSTOM_ALPHABET = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    
    '''  Base64 encode based on SECTION 22.e)
    '''
    def custom_b64encode(data, alphabet = CUSTOM_ALPHABET):
      buffer,count,result = 0,0,[]
      for byte in data:
        buffer |= byte << count
        count += 8
        while count >= 6:
          result.append(buffer & 0x3f)
          buffer >>= 6
          count -= 6
      if count > 0:
        result.append(buffer)
      return ''.join(alphabet[idx] for idx in result)
    
    '''  From http://www.akkadia.org/drepper/SHA-crypt.txt
    '''
    def sha512_crypt(password, salt, rounds_in = None):
      rounds,rounds_defined = 5000, False
      if rounds_in is not None:
        rounds,rounds_defined = rounds_in, True
    
      assert 1000 <= rounds <= 999999999
      hash = hashlib.sha512
      salt_prefix = '$6$'
      password = password.encode('utf8')
      salt = salt.encode('ascii')[:16]
    
    
      A = hash()             # SECTION 1.
      A.update(password)     # SECTION 2.
      A.update(salt)         # SECTION 3.
    
      B = hash()             # SECTION 4.
      B.update(password)     # SECTION 5.
      B.update(salt)         # SECTION 6.
      B.update(password)     # SECTION 7.
      digestB = B.digest();  # SECTION 8.
    
      A.update(extend_by_repeat(digestB, len(password)))  # SECTION 9., 10.
    
      # SECTION 11.
      i = len(password)
      while i > 0:
        if i & 1:
          A.update(digestB)   # SECTION 11.a)
        else:
          A.update(password)  # SECTION 11.b)
        i = i >> 1
    
      digestA = A.digest()    # SECTION 12.
    
      DP = hash()             # SECTION 13.
      # SECTION 14.
      for _ in range(len(password)):
        DP.update(password)
    
      digestDP = DP.digest()  # SECTION 15.
    
      P = extend_by_repeat(digestDP, len(password))  # SECTION 16.a), 16.b)
    
      DS = hash()             # SECTION 17.
      # SECTION 18.
      for _ in range(16 + digestA[0]):
        DS.update(salt)
    
      digestDS = DS.digest()  # SECTION 19.
    
      S = extend_by_repeat(digestDS, len(salt))      # SECTION 20.a), 20.b)
    
      # SECTION 21.
      digest_iteration_AC = digestA
      for i in range(rounds):
        C = hash()                        # SECTION 21.a)
        if i % 2:
          C.update(P)                     # SECTION 21.b)
        else:
          C.update(digest_iteration_AC)   # SECTION 21.c)
        if i % 3:
          C.update(S)                     # SECTION 21.d)
        if i % 7:
          C.update(P)                     # SECTION 21.e)
        if i % 2:
          C.update(digest_iteration_AC)   # SECTION 21.f)
        else:
          C.update(P)                     # SECTION 21.g)
    
        digest_iteration_AC = C.digest()  # SECTION 21.h)
    
      shuffled_digest = shuffle_sha512(digest_iteration_AC)
    
    
      prefix = salt_prefix   # SECTION 22.a)
    
      # SECTION 22.b)
      if rounds_defined:
        prefix += 'rounds={0}$'.format(rounds_in)
    
    
      return (prefix
        + salt.decode('ascii')               # SECTION 22.c)
        + '$'                                # SECTION 22.d)
        + custom_b64encode(shuffled_digest)  # SECTION 22.e)
      )
    
    actual = sha512_crypt('password', 'salt1234')
    expected = '$6$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81'
    
    print(actual)
    print(expected)
    assert actual == expected
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-06
      • 1970-01-01
      • 2018-03-12
      • 2017-08-23
      • 2017-11-21
      • 1970-01-01
      相关资源
      最近更新 更多