# 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 . 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