This week I needed to work with passwords from an OpenLDAP database. I needed to create users and encode their passwords as SSHA. After much googling and reading of authentication code examples, this is what I came up with.
import hashlib
import os
from base64 import urlsafe_b64encode as encode
from base64 import urlsafe_b64decode as decode
def makeSecret(password):
salt = os.urandom(4)
h = hashlib.sha1(password)
h.update(salt)
return "{SSHA}" + encode(h.digest() + salt)
def checkPassword(challenge_password, password):
challenge_bytes = decode(challenge_password[6:])
digest = challenge_bytes[:20]
salt = challenge_bytes[20:]
hr = hashlib.sha1(password)
hr.update(salt)
return digest == hr.digest()
Wow, python rules. So first I want to make sure I can validate a password against one generated by slappasswd
reedobrien$ slappasswd -s topsecret
{SSHA}Ccpjsip2UZL2CR2VsWTH7aF0vWKHQ7jn
And then see if I can validate it
>>> checkPassword('{SSHA}Ccpjsip2UZL2CR2VsWTH7aF0vWKHQ7jn',
... 'topsecret')
True
Next I need to make sure I can generate one that OpenLDAP can use. So I used a script I wrote to create a user in ldap. This time I use the same password and get a different hash which is good.
>>> pw = "{SSHA}hq5ROYE8RoLA1Zcz6azgNQP5PkdETocx"
which OpenLDAP represents in LDIFs as base64 encoded
...
# tester, people, reedobrien.com
dn: uid=tester,ou=people,dc=reedobrien,dc=com
objectClass: top
objectClass: inetOrgPerson
uid: tester
cn: test test
sn: test
mail: test@example.com
userPassword:: e1NTSEF9MHhaMEdZc2Fob1JNeXZWR2FVdGszS0VwSFZTQnVLTlc=
...
So I try authenticating:
>>> userPasword = "e1NTSEF9MHhaMEdZc2Fob1JNeXZWR2FVdGszS0VwSFZTQnVLTlc="
>>> decode(userPassword) == pw
True
>>> checkPassword(decode(userPassword), 'topsecret')
True
Now I want to make sure it works through the ldap server itself
In python:
>>> import ldap
>>> con = ldap.initialize("ldap://127.0.0.1")
>>> con.simple_bind_s('uid=tester,ou=people,dc=reedbrien,dc=com',
'topsecret')
(97, []) ## indicates success
And not using my stuff.
reedobrien$ ldapsearch -x -D "uid=tester,ou=people,dc=reedobrien,dc=com" -w topsecret
# extended LDIF
#
# LDAPv3
# base <> with scope sub
# filter: (objectclass=*)
# requesting: ALL
#
# search result
search: 2
result: 32 No such object
# numResponses: 1
How do they say, `w00t!`
So it was cool doing it and it made me feel smart for a minute. I am sure that there is a module or ten out there that already does this, but I couldn't find it. At least it was educational.
Unfortunately, after getting it to work; I don't I understand any better what it really does or how SSHA is better than plain old SHA.
Oh, well. Hopefully it helps some weary traveller out there one day.