使用 Python 代码编写的 SHA 512 crypt 输出与 mkpasswd 不同
SHA 512 crypt output written with Python code is different from mkpasswd
运行 mkpasswd -m sha-512 -S salt1234 password
结果如下:
$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81
我有这段 Python 代码,我认为它会输出相同的结果,但事实并非如此:
import hashlib, base64
print(base64.b64encode(hashlib.sha512('password' + 'salt1234').digest()))
它反而导致:
nOkBUt6l7zlKAfjtk1EfB0TmckXfDiA4FPLcpywOLORZ1PWQK4+PZVEiT4+9rFjqR3xnaruZBiRjDGcDpxxTig==
不确定我做错了什么。
我的另一个问题是,如何告诉 sha512 函数进行自定义回合。它似乎只需要 1 个参数。
您需要使用crypt.crypt
:
>>> import crypt
>>> crypt.crypt('password', '$' + 'salt1234')
'$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81'
mkpasswd
是 crypt()
function 的前端。我认为这里不是直接的 SHA512 哈希。
一点研究指向 specification for SHA256-crypt and SHA512-crypt,它显示哈希值默认应用 5000 次。您可以使用 -R
切换到 mkpasswd
来指定不同的回合数; -R 5000
确实给你相同的输出:
$ mkpasswd -m sha-512 -S salt1234 -R 5000 password
$rounds=5000$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81
命令行工具提供的最小回合数为 1000:
$ mkpasswd -m sha-512 -S salt1234 -R 999 password
$rounds=1000$salt1234$SVDFHbJXYrzjGi2fA1k3ws01/D9q0ZTAh1KfRF5.ehgjVBqfHUaKqfynXefJ4DxIWxkMAITYq9mmcBl938YQ//
$ mkpasswd -m sha-512 -S salt1234 -R 1 password
$rounds=1000$salt1234$SVDFHbJXYrzjGi2fA1k3ws01/D9q0ZTAh1KfRF5.ehgjVBqfHUaKqfynXefJ4DxIWxkMAITYq9mmcBl938YQ//
算法有点复杂,需要您创建多个摘要。您 可以 通过 crypt.crypt()
function 访问 C crypt()
函数,并以与 mkpasswd
命令行相同的方式驱动它。
SHA512-crypt
方法是否可用取决于您的平台; Python 3 版本的 crypt
模块提供了一个 crypt.methods
list 来告诉您您的平台支持哪些方法。由于这使用完全相同的库 mkpasswd
使用,您的 OS 显然支持 SHA512-crypt
并且 Python 也可以访问。
您需要在 salt 前加上 '$
以指定不同的方法。您可以通过在 '$'
字符串和盐之间添加 '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 = '$'
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')
'$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81'
>>> sha512_crypt('password', 'salt1234', rounds=1000)
'$rounds=1000$salt1234$SVDFHbJXYrzjGi2fA1k3ws01/D9q0ZTAh1KfRF5.ehgjVBqfHUaKqfynXefJ4DxIWxkMAITYq9mmcBl938YQ//'
这是基于规范的 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 = '$'
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 = '$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81'
print(actual)
print(expected)
assert actual == expected
运行 mkpasswd -m sha-512 -S salt1234 password
结果如下:
$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81
我有这段 Python 代码,我认为它会输出相同的结果,但事实并非如此:
import hashlib, base64
print(base64.b64encode(hashlib.sha512('password' + 'salt1234').digest()))
它反而导致:
nOkBUt6l7zlKAfjtk1EfB0TmckXfDiA4FPLcpywOLORZ1PWQK4+PZVEiT4+9rFjqR3xnaruZBiRjDGcDpxxTig==
不确定我做错了什么。
我的另一个问题是,如何告诉 sha512 函数进行自定义回合。它似乎只需要 1 个参数。
您需要使用crypt.crypt
:
>>> import crypt
>>> crypt.crypt('password', '$' + 'salt1234')
'$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81'
mkpasswd
是 crypt()
function 的前端。我认为这里不是直接的 SHA512 哈希。
一点研究指向 specification for SHA256-crypt and SHA512-crypt,它显示哈希值默认应用 5000 次。您可以使用 -R
切换到 mkpasswd
来指定不同的回合数; -R 5000
确实给你相同的输出:
$ mkpasswd -m sha-512 -S salt1234 -R 5000 password
$rounds=5000$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81
命令行工具提供的最小回合数为 1000:
$ mkpasswd -m sha-512 -S salt1234 -R 999 password
$rounds=1000$salt1234$SVDFHbJXYrzjGi2fA1k3ws01/D9q0ZTAh1KfRF5.ehgjVBqfHUaKqfynXefJ4DxIWxkMAITYq9mmcBl938YQ//
$ mkpasswd -m sha-512 -S salt1234 -R 1 password
$rounds=1000$salt1234$SVDFHbJXYrzjGi2fA1k3ws01/D9q0ZTAh1KfRF5.ehgjVBqfHUaKqfynXefJ4DxIWxkMAITYq9mmcBl938YQ//
算法有点复杂,需要您创建多个摘要。您 可以 通过 crypt.crypt()
function 访问 C crypt()
函数,并以与 mkpasswd
命令行相同的方式驱动它。
SHA512-crypt
方法是否可用取决于您的平台; Python 3 版本的 crypt
模块提供了一个 crypt.methods
list 来告诉您您的平台支持哪些方法。由于这使用完全相同的库 mkpasswd
使用,您的 OS 显然支持 SHA512-crypt
并且 Python 也可以访问。
您需要在 salt 前加上 '$
以指定不同的方法。您可以通过在 '$'
字符串和盐之间添加 '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 = '$'
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')
'$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81'
>>> sha512_crypt('password', 'salt1234', rounds=1000)
'$rounds=1000$salt1234$SVDFHbJXYrzjGi2fA1k3ws01/D9q0ZTAh1KfRF5.ehgjVBqfHUaKqfynXefJ4DxIWxkMAITYq9mmcBl938YQ//'
这是基于规范的 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 = '$'
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 = '$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81'
print(actual)
print(expected)
assert actual == expected