Skip to content

Instantly share code, notes, and snippets.

@crmne
Created August 15, 2010 10:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save crmne/525352 to your computer and use it in GitHub Desktop.
Save crmne/525352 to your computer and use it in GitHub Desktop.
UNIX and Apache compatible MD5 password encryption
#!/usr/bin/env python
# Based on FreeBSD src/lib/libcrypt/crypt.c 1.2
# http://www.freebsd.org/cgi/cvsweb.cgi/~checkout~/src/lib/libcrypt/crypt.c?rev=1.2&content-type=text/plain
# Original license:
# * "THE BEER-WARE LICENSE" (Revision 42):
# * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
# * can do whatever you want with this stuff. If we meet some day, and you think
# * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
# This port adds no further stipulations. I forfeit any copyright interest.
from random import choice
import hashlib, string, sys, getopt, getpass
def md5crypt(password, salt, magic):
# /* The password first, since that is what is most unknown */ /* Then our magic string */ /* Then the raw salt */
m = hashlib.md5()
m.update(password + magic + salt)
# /* Then just as many characters of the MD5(pw,salt,pw) */
mixin = hashlib.md5(password + salt + password).digest()
for i in range(0, len(password)):
m.update(mixin[i % 16])
# /* Then something really weird... */
i = len(password)
while i:
if i & 1:
m.update('\x00')
else:
m.update(password[0])
i >>= 1
final = m.digest()
# /* and now, just to make sure things don't run too fast */
for i in range(1000):
m2 = hashlib.md5()
if i & 1:
m2.update(password)
else:
m2.update(final)
if i % 3:
m2.update(salt)
if i % 7:
m2.update(password)
if i & 1:
m2.update(final)
else:
m2.update(password)
final = m2.digest()
# This is the bit that uses to64() in the original code.
itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
rearranged = ''
for a, b, c in ((0, 6, 12), (1, 7, 13), (2, 8, 14), (3, 9, 15), (4, 10, 5)):
v = ord(final[a]) << 16 | ord(final[b]) << 8 | ord(final[c])
for i in range(4):
rearranged += itoa64[v & 0x3f]; v >>= 6
v = ord(final[11])
for i in range(2):
rearranged += itoa64[v & 0x3f]; v >>= 6
return magic + salt + '$' + rearranged
def genrandom8():
return ''.join([choice(string.letters+string.digits) for i in range(1,8)])
def usage(exitcode=0):
print "usage: md5crypt.py [-h] [-a] [-u]\n"
print "UNIX and Apache compatible MD5 password encryption\n"
print "optional arguments:"
print "\t-h, --help\tshow this help message and exit"
print "\t-a, --apache\tencrypt password like htpasswd"
print "\t-u, --unix\tencrypt password like passwd (default)"
sys.exit(exitcode)
def main():
try:
opts, args = getopt.getopt(sys.argv[1:], "hua", ["help", "unix", "apache"])
except getopt.GetoptError, err:
print >> sys.stderr, str(err) + '\n'
usage(2)
magic = '$1$'
for o, a in opts:
if o in ("-h", "--help"):
usage()
elif o in ("-a", "--apache"):
magic = '$apr1$'
elif o in ("-u","--unix"):
pass
print "=> %s" % md5crypt(getpass.getpass(),genrandom8(),magic)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment