Search code examples
pythonpython-2.7hashsha512hashlib

SHA 512 crypt output written with Python code is different from mkpasswd


Running mkpasswd -m sha-512 -S salt1234 password results in the following:

$6$salt1234$Zr07alHmuONZlfKILiGKKULQZaBG6Qmf5smHCNH35KnciTapZ7dItwaCv5SKZ1xH9ydG59SCgkdtsTqVWGhk81

I have this snippet of Python code that I thought would output the same, but isn't:

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

It instead results in:

nOkBUt6l7zlKAfjtk1EfB0TmckXfDiA4FPLcpywOLORZ1PWQK4+PZVEiT4+9rFjqR3xnaruZBiRjDGcDpxxTig==

Not sure what I am doing wrong.

Another question I have is, how do I tell sha512 function to do custom rounds. It seems to take only 1 argument.


Solution

  • mkpasswd is a front-end to the crypt() function. I don't think it is a straight-forward SHA512 hash here.

    A little research points to the specification for SHA256-crypt and SHA512-crypt, which shows the hash is applied a default 5000 times. You can specify a different number of rounds using the -R switch to mkpasswd; -R 5000 indeed gives you the same output:

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

    The minimum number of rounds offered by the command-line tool is 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//
    

    The algorithm is a bit more involved, requiring you to create several digests. You could instead access the C crypt() function through the crypt.crypt() function, and drive it the same way the mkpasswd commandline does.

    It depends on your platform if the SHA512-crypt method is available; the Python 3 version of the crypt module offers a crypt.methods list that tells you what methods your platform supports. Since this use the exact same library mkpasswd uses, your OS obviously does support SHA512-crypt and Python will have access too.

    You need to prefix the salt with '$6$ to specify the different method. You can specify the number of rounds by adding a 'rounds=<N>$' string between the '$6$' string and your salt:

    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)
    

    This then produces the same output as the mkpasswd command line:

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