From 551f71ec6c94325619225b6a08753f9ad120e913 2011-11-18 18:51:40 From: Stefan van der Walt Date: 2011-11-18 18:51:40 Subject: [PATCH] Add hashed passphrase generation and verification. --- diff --git a/IPython/lib/__init__.py b/IPython/lib/__init__.py index 65b0ae3..dc07221 100644 --- a/IPython/lib/__init__.py +++ b/IPython/lib/__init__.py @@ -25,6 +25,8 @@ from IPython.lib.inputhook import ( current_gui ) +from IPython.lib.security import passwd + #----------------------------------------------------------------------------- # Code #----------------------------------------------------------------------------- diff --git a/IPython/lib/security.py b/IPython/lib/security.py new file mode 100644 index 0000000..2c283a8 --- /dev/null +++ b/IPython/lib/security.py @@ -0,0 +1,79 @@ +""" +Password generation for the IPython notebook. +""" + +import hashlib +import random + +def passwd(passphrase): + """Generate hashed password and salt for use in notebook configuration. + + Parameters + ---------- + passphrase : str + Password to hash. + + Returns + ------- + hashed_passphrase : str + Hashed password, in the format 'hash_algorithm:salt:passphrase_hash'. + + Examples + -------- + In [1]: passwd('mypassword') + Out[1]: 'sha1:7cf3:b7d6da294ea9592a9480c8f52e63cd42cfb9dd12' + + """ + algorithm = 'sha1' + + h = hashlib.new(algorithm) + salt = hex(int(random.getrandbits(16)))[2:] + h.update(passphrase + salt) + + return ':'.join((algorithm, salt, h.hexdigest())) + +def passwd_check(hashed_passphrase, passphrase): + """Verify that a given passphrase matches its hashed version. + + Parameters + ---------- + hashed_passphrase : str + Hashed password, in the format returned by `passwd`. + passphrase : str + Passphrase to validate. + + Returns + ------- + valid : bool + True if the passphrase matches the hash. + + Examples + -------- + In [1]: from IPython.lib.security import passwd_check + + In [2]: passwd_check('sha1:7cf3:b7d6da294ea9592a9480c8f52e63cd42cfb9dd12', + ...: 'mypassword') + Out[2]: True + + In [3]: passwd_check('sha1:7cf3:b7d6da294ea9592a9480c8f52e63cd42cfb9dd12', + ...: 'anotherpassword') + Out[3]: False + + """ + # Algorithm and hash length + supported_algorithms = {'sha1': 40} + + try: + algorithm, salt, pw_digest = hashed_passphrase.split(':', 2) + except (ValueError, TypeError): + return False + + if not (algorithm in supported_algorithms and \ + len(pw_digest) == supported_algorithms[algorithm] and \ + len(salt) == 4): + return False + + h = hashlib.new(algorithm) + h.update(passphrase + salt) + + return h.hexdigest() == pw_digest diff --git a/IPython/lib/tests/test_security.py b/IPython/lib/tests/test_security.py new file mode 100644 index 0000000..38bad4b --- /dev/null +++ b/IPython/lib/tests/test_security.py @@ -0,0 +1,21 @@ +from IPython.lib import passwd +from IPython.lib.security import passwd_check +import nose.tools as nt + +def test_passwd_structure(): + p = passwd('passphrase') + algorithm, salt, hashed = p.split(':') + nt.assert_equals(algorithm, 'sha1') + nt.assert_equals(len(salt), 4) + nt.assert_equals(len(hashed), 40) + +def test_roundtrip(): + p = passwd('passphrase') + nt.assert_equals(passwd_check(p, 'passphrase'), True) + +def test_bad(): + p = passwd('passphrase') + nt.assert_equals(passwd_check(p, p), False) + nt.assert_equals(passwd_check(p, 'a:b:c:d'), False) + nt.assert_equals(passwd_check(p, 'a:b'), False) +