2121salt_len = 12
2222
2323
24- def passwd (passphrase = None , algorithm = 'sha1 ' ):
24+ def passwd (passphrase = None , algorithm = 'argon2 ' ):
2525 """Generate hashed password and salt for use in notebook configuration.
2626
2727 In the notebook configuration, set `c.NotebookApp.password` to
@@ -34,7 +34,7 @@ def passwd(passphrase=None, algorithm='sha1'):
3434 and verify a password.
3535 algorithm : str
3636 Hashing algorithm to use (e.g, 'sha1' or any argument supported
37- by :func:`hashlib.new`).
37+ by :func:`hashlib.new`, or 'argon2' ).
3838
3939 Returns
4040 -------
@@ -59,11 +59,22 @@ def passwd(passphrase=None, algorithm='sha1'):
5959 else :
6060 raise ValueError ('No matching passwords found. Giving up.' )
6161
62- h = hashlib .new (algorithm )
63- salt = ('%0' + str (salt_len ) + 'x' ) % random .getrandbits (4 * salt_len )
64- h .update (cast_bytes (passphrase , 'utf-8' ) + str_to_bytes (salt , 'ascii' ))
62+ if algorithm == 'argon2' :
63+ from argon2 import PasswordHasher
64+ ph = PasswordHasher (
65+ memory_cost = 10240 ,
66+ time_cost = 10 ,
67+ parallelism = 8 ,
68+ )
69+ h = ph .hash (passphrase )
70+
71+ return ':' .join ((algorithm , cast_unicode (h , 'ascii' )))
72+ else :
73+ h = hashlib .new (algorithm )
74+ salt = ('%0' + str (salt_len ) + 'x' ) % random .getrandbits (4 * salt_len )
75+ h .update (cast_bytes (passphrase , 'utf-8' ) + str_to_bytes (salt , 'ascii' ))
6576
66- return ':' .join ((algorithm , salt , h .hexdigest ()))
77+ return ':' .join ((algorithm , salt , h .hexdigest ()))
6778
6879
6980def passwd_check (hashed_passphrase , passphrase ):
@@ -84,30 +95,41 @@ def passwd_check(hashed_passphrase, passphrase):
8495 Examples
8596 --------
8697 >>> from notebook.auth.security import passwd_check
87- >>> passwd_check('sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a',
88- ... 'mypassword')
98+ >>> passwd_check('argon2:...', 'mypassword')
8999 True
90100
91- >>> passwd_check('sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a',
92- ... 'anotherpassword')
101+ >>> passwd_check('argon2:...', 'otherpassword')
93102 False
94- """
95- try :
96- algorithm , salt , pw_digest = hashed_passphrase .split (':' , 2 )
97- except (ValueError , TypeError ):
98- return False
99103
100- try :
101- h = hashlib .new (algorithm )
102- except ValueError :
103- return False
104-
105- if len (pw_digest ) == 0 :
106- return False
107-
108- h .update (cast_bytes (passphrase , 'utf-8' ) + cast_bytes (salt , 'ascii' ))
109-
110- return h .hexdigest () == pw_digest
104+ >>> passwd_check('sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a',
105+ ... 'mypassword')
106+ True
107+ """
108+ if hashed_passphrase .startswith ('argon2:' ):
109+ import argon2
110+ import argon2 .exceptions
111+ ph = argon2 .PasswordHasher ()
112+ try :
113+ return ph .verify (hashed_passphrase [7 :], passphrase )
114+ except argon2 .exceptions .VerificationError :
115+ return False
116+ else :
117+ try :
118+ algorithm , salt , pw_digest = hashed_passphrase .split (':' , 2 )
119+ except (ValueError , TypeError ):
120+ return False
121+
122+ try :
123+ h = hashlib .new (algorithm )
124+ except ValueError :
125+ return False
126+
127+ if len (pw_digest ) == 0 :
128+ return False
129+
130+ h .update (cast_bytes (passphrase , 'utf-8' ) + cast_bytes (salt , 'ascii' ))
131+
132+ return h .hexdigest () == pw_digest
111133
112134@contextmanager
113135def persist_config (config_file = None , mode = 0o600 ):
0 commit comments