diff options
Diffstat (limited to 'qvisqve_secrets/secrets.py')
-rw-r--r-- | qvisqve_secrets/secrets.py | 90 |
1 files changed, 90 insertions, 0 deletions
diff --git a/qvisqve_secrets/secrets.py b/qvisqve_secrets/secrets.py new file mode 100644 index 0000000..b469ed8 --- /dev/null +++ b/qvisqve_secrets/secrets.py @@ -0,0 +1,90 @@ +# Copyright (C) 2018 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import codecs + + +from Cryptodome.Random import get_random_bytes +from Cryptodome.Protocol.KDF import scrypt + + +class SecretHasher: + + # The following have been chosen based on: + # https://www.pycryptodome.org/en/latest/src/protocol/kdf.html + # Corrections, with references, welcome. + _salt_size = 16 + _key_len = 128 + _N = 16384 + _r = 8 + _p = 1 + + _version = 1 + + _ok_args = ['salt', 'key_len', 'N', 'r', 'p'] + + def get_salt(self): + return get_random_bytes(self._salt_size) + + def set_n(self, n): + self._N = n + + def _full_hash(self, cleartext, **kwargs): + assert kwargs == self._filter_kwargs(kwargs) + + hashed_binary = scrypt(cleartext, **kwargs) + params = { + 'version': self._version, + 'hash': self.hex_encode(hashed_binary), + 'salt': self.hex_encode(kwargs['salt']) + } + for key in kwargs: + if key not in params: + params[key] = kwargs[key] + + return params + + def hex_encode(self, byte_string): + return codecs.encode(byte_string, 'hex').decode('ascii') + + def hex_decode(self, hex_string): + return codecs.decode(bytes(hex_string, 'ascii'), 'hex') + + def _filter_kwargs(self, kwargs): + return { + key: kwargs[key] + for key in self._ok_args + } + + def hash(self, cleartext, salt=None): + if salt is None: + salt = self.get_salt() + assert isinstance(salt, bytes) + + kwargs = { + 'salt': salt, + 'key_len': self._key_len, + 'N': self._N, + 'r': self._r, + 'p': self._p, + } + return self._full_hash(cleartext, **kwargs) + + def is_correct(self, hashed, cleartext): + kwargs = self._filter_kwargs(hashed) + salt = self.hex_decode(kwargs.pop('salt')) + h2 = self._full_hash(cleartext, salt=salt, **kwargs) + return hashed == h2 |