Skip to content

Commit

Permalink
Implement password hashing with argon2-cffi
Browse files Browse the repository at this point in the history
  • Loading branch information
afshin committed Mar 19, 2021
1 parent d24f9f6 commit 054cc4a
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 12 deletions.
35 changes: 29 additions & 6 deletions jupyter_server/auth/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
import traceback
import warnings

import argon2
import argon2.exceptions
from argon2 import PasswordHasher
from ipython_genutils.py3compat import cast_bytes, str_to_bytes, cast_unicode
from traitlets.config import Config, ConfigFileNotFound, JSONFileConfigLoader
from jupyter_core.paths import jupyter_config_dir
Expand All @@ -21,7 +24,7 @@
salt_len = 12


def passwd(passphrase=None, algorithm='sha1'):
def passwd(passphrase=None, algorithm='argon2'):
"""Generate hashed password and salt for use in server configuration.
In the server configuration, set `c.ServerApp.password` to
Expand All @@ -34,7 +37,7 @@ def passwd(passphrase=None, algorithm='sha1'):
and verify a password.
algorithm : str
Hashing algorithm to use (e.g, 'sha1' or any argument supported
by :func:`hashlib.new`).
by :func:`hashlib.new`, or 'argon2').
Returns
-------
Expand All @@ -59,6 +62,16 @@ def passwd(passphrase=None, algorithm='sha1'):
else:
raise ValueError('No matching passwords found. Giving up.')

if algorithm == 'argon2':
ph = PasswordHasher(
memory_cost=10240,
time_cost=10,
parallelism=8,

This comment has been minimized.

Copy link
@manmedia

manmedia Sep 29, 2022

Why having 8 is a good choice, what if it fails?

This comment has been minimized.

Copy link
@kevin-bates

kevin-bates Sep 30, 2022

Member

FWIW, 8 has been the default value for the parallelism parameter since August 2018 (18.2.0 release) according to the docs. How to deal with failure seems independent of parameter choices in this case. Could you please elaborate on your concern?

This comment has been minimized.

Copy link
@manmedia

manmedia Sep 30, 2022

Please see this thread - jupyterlab/jupyterlab#13070 - it's blocking us now

This comment has been minimized.

Copy link
@kevin-bates

kevin-bates Sep 30, 2022

Member

Thank you - that link is helpful. We should probably keep the discussion there.

This comment has been minimized.

Copy link
@afshin

afshin Oct 1, 2022

Author Contributor

(I deleted my comment because my page had fallen out of sync with the thread and I'd missed the conversation.)

)
h = ph.hash(passphrase)

return ':'.join((algorithm, cast_unicode(h, 'ascii')))

h = hashlib.new(algorithm)
salt = ('%0' + str(salt_len) + 'x') % random.getrandbits(4 * salt_len)
h.update(cast_bytes(passphrase, 'utf-8') + str_to_bytes(salt, 'ascii'))
Expand All @@ -84,14 +97,24 @@ def passwd_check(hashed_passphrase, passphrase):
Examples
--------
>>> from jupyter_server.auth.security import passwd_check
>>> passwd_check('sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a',
... 'mypassword')
>>> passwd_check('argon2:...', 'mypassword')
True
>>> passwd_check('sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a',
... 'anotherpassword')
>>> passwd_check('argon2:...', 'otherpassword')
False
>>> passwd_check('sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a',
... 'mypassword')
True
"""
if hashed_passphrase.startswith('argon2:'):
ph = argon2.PasswordHasher()

try:
return ph.verify(hashed_passphrase[7:], passphrase)
except argon2.exceptions.VerificationError:
return False

try:
algorithm, salt, pw_digest = hashed_passphrase.split(':', 2)
except (ValueError, TypeError):
Expand Down
14 changes: 8 additions & 6 deletions jupyter_server/tests/auth/test_security.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import pytest

from jupyter_server.auth.security import passwd, passwd_check, salt_len
from jupyter_server.auth.security import passwd, passwd_check


def test_passwd_structure():
p = passwd('passphrase')
algorithm, salt, hashed = p.split(':')
assert algorithm == 'sha1'
assert len(salt) == salt_len
assert len(hashed) == 40
algorithm, hashed = p.split(':')
assert algorithm == 'argon2', algorithm
assert hashed.startswith('$argon2id$'), hashed


def test_roundtrip():
Expand All @@ -26,4 +25,7 @@ def test_bad():
def test_passwd_check_unicode():
# GH issue #4524
phash = u'sha1:23862bc21dd3:7a415a95ae4580582e314072143d9c382c491e4f'
assert passwd_check(phash, u"łe¶ŧ←↓→")
assert passwd_check(phash, u"łe¶ŧ←↓→")
phash = (u'argon2:$argon2id$v=19$m=10240,t=10,p=8$'
u'qjjDiZUofUVVnrVYxacnbA$l5pQq1bJ8zglGT2uXP6iOg')
assert passwd_check(phash, u"łe¶ŧ←↓→")
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
'jinja2',
'tornado>=6.1.0',
'pyzmq>=17',
'argon2-cffi',
'ipython_genutils',
'traitlets>=4.2.1',
'jupyter_core>=4.4.0',
Expand Down

0 comments on commit 054cc4a

Please sign in to comment.