2015-12-25 21 views
4

運行在以下mkpasswd -m sha-512 -S salt1234 password結果:SHA 512地穴輸出不同於mkpasswd

$6$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81 

我有Python代碼這個片段,我認爲將輸出相同,但並不:

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

相反,它會導致:

nOkBUt6l7zlKAfjtk1EfB0TmckXfDiA4FPLcpywOLORZ1PWQK4+PZVEiT4+9rFjqR3xnaruZBiRjDGcDpxxTig== 

不知道我是什麼DOI恩錯了。

我的另一個問題是,我如何告訴sha512函數做自定義回合。它似乎只需要1個參數。

+3

'mkpasswd'是前端到['隱窩()'函數(http://linux.die.net/man/3/crypt)。我不認爲這是一個簡單的SHA512哈希值。 –

+1

我認爲passlib可能更接近你想要的東西https://pythonhosted.org/passlib/lib/passlib.hash.sha512_crypt.html# hashlib和crypto(3) –

+3

規範對於SHA512-crypt也有很大區別:http://www.akkadia.org/drepper/sha-crypt.html –

回答

7

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 
$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也可以訪問。

您需要用'$6$作爲鹽的前綴來指定不同的方法。

import crypt 
import os 
import random 
import string 

def sha512_crypt(password, salt=None, rounds=None): 
    if salt is None: 
     rand = random.SystemRandom() 
     salt = ''.join([rand.choice(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命令行相同的:你可以通過添加'$6$'字符串和您的鹽之間的'rounds=<N>$'字符串指定回合數

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

您需要使用crypt.crypt

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

但是爲什麼我寫的代碼不能做我期望的代碼。 hashlib.sha512函數與SHA-512 crypt不同?我想從高層角度理解SHA-512隱私算法。 – user1720897

+0

@ user1720897,閱讀Martijn Pieters的評論。 – falsetru

1

這是一個純粹的python3實現了基於規範的sha512_crypt函數。 這僅用於說明,請始終使用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 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' 

''' 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