1

I try to call API that specifically uses MD5 hash in one of the steps. In the documentation they specifically show example reference that generates MD5 in the following way

$ openssl passwd -1 -salt stack overflow
$1$stack$MVcBmQ3RlrBu5Xoj74NBA0

or to be more exact, they just use the part after the third $

$ openssl passwd -1 -salt stack overflow | cut -f 4 -d '$'
MVcBmQ3RlrBu5Xoj74NBA0

At first, I tried to use hashlib and got the hexadecimal output that does not resemble the exampla at all.

salt = b'stack'
input = b'overflow'
output = hashlib.md5(salt + input).hexdigest()
print(output)

73868cb1848a216984dca1b6b0ee37bc

I figured that I just need to decode those hex values to characters, but decode does not work for default utf8 or for latin1

salt = b'stack'
input = b'overflow'
output = hashlib.md5(salt + input).digest().decode()
print(output)

UnicodeDecodeError: 'utf-8' codec can't decode byte 0x86 in position 1: invalid start byte

I found some help here python version of openssl passwd and here MD5 hash in Python

I could reproduce this one with crypt

$ openssl passwd -salt stack overflow
st22n6QiCXNQY
salt = 'stack'
input = 'overflow'
output = crypt.crypt(input, salt)
print(output)

st22n6QiCXNQY

But as soon openssl passwd -1 is added, which stands for

-1                  MD5-based password algorithm

I cannot reproduce it anymore.

How can I recreate MD5-based password algorithm in Python? I would preferably use hashlib if possible.

0

3 Answers 3

2

I found the solution using passlib

from passlib.hash import md5_crypt

salt = 'stack'
input = 'overflow'
output = md5_crypt.using(salt=salt).hash(input)
print(output)

$1$stack$MVcBmQ3RlrBu5Xoj74NBA0
Sign up to request clarification or add additional context in comments.

Comments

1

On Linux you can do this with the crypt module. However it will be removed in Python 3.13.

import crypt
crypt.crypt("overflow", "$1$stack")

Here $1$ specifies the use of MD5 and "stack" is the salt.

1 Comment

This works! Thank you so much! I would never figure this one out. I also found the solution with the passlib.
-1

https://passlib.readthedocs.io/en/stable/lib/passlib.hash.md5_crypt.html

Python code for copy-pasters

MAGIC = '$1$'           # Magic string
ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

from hashlib import md5

def to64 (v, n):
    ret = ''
    while (n - 1 >= 0):
        n = n - 1
    ret = ret + ITOA64[v & 0x3f]
    v = v >> 6
    return ret


def apache_md5_crypt (pw, salt):
    # change the Magic string to match the one used by Apache
    return unix_md5_crypt(pw, salt, '$apr1$')


def unix_md5_crypt(pw, salt, magic=None):
    
    if magic==None:
        magic = MAGIC

    # Take care of the magic string if present
    if salt[:len(magic)] == magic:
        salt = salt[len(magic):]
        

    # salt can have up to 8 characters:
    import string
    salt = string.split(salt, '$', 1)[0]
    salt = salt[:8]

    ctx = pw + magic + salt

    final = md5.md5(pw + salt + pw).digest()

    for pl in range(len(pw),0,-16):
        if pl > 16:
            ctx = ctx + final[:16]
        else:
            ctx = ctx + final[:pl]


    # Now the 'weird' xform (??)

    i = len(pw)
    while i:
        if i & 1:
            ctx = ctx + chr(0)  #if ($i & 1) { $ctx->add(pack("C", 0)); }
        else:
            ctx = ctx + pw[0]
        i = i >> 1

    final = md5.md5(ctx).digest()
    
    # The following is supposed to make
    # things run slower. 

    # my question: WTF???

    for i in range(1000):
        ctx1 = ''
        if i & 1:
            ctx1 = ctx1 + pw
        else:
            ctx1 = ctx1 + final[:16]

        if i % 3:
            ctx1 = ctx1 + salt

        if i % 7:
            ctx1 = ctx1 + pw

        if i & 1:
            ctx1 = ctx1 + final[:16]
        else:
            ctx1 = ctx1 + pw
            
            
        final = md5.md5(ctx1).digest()


    # Final xform
                                
    passwd = ''

    passwd = passwd + to64((int(ord(final[0])) << 16)
                           |(int(ord(final[6])) << 8)
                           |(int(ord(final[12]))),4)

    passwd = passwd + to64((int(ord(final[1])) << 16)
                           |(int(ord(final[7])) << 8)
                           |(int(ord(final[13]))), 4)

    passwd = passwd + to64((int(ord(final[2])) << 16)
                           |(int(ord(final[8])) << 8)
                           |(int(ord(final[14]))), 4)

    passwd = passwd + to64((int(ord(final[3])) << 16)
                           |(int(ord(final[9])) << 8)
                           |(int(ord(final[15]))), 4)

    passwd = passwd + to64((int(ord(final[4])) << 16)
                           |(int(ord(final[10])) << 8)
                           |(int(ord(final[5]))), 4)

    passwd = passwd + to64((int(ord(final[11]))), 2)


    return magic + salt + '$' + passwd


## assign a wrapper function:
md5crypt = unix_md5_crypt

if __name__ == "__main__":
    print unix_md5_crypt("overflow", "stack", "$1$")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.