def test_performance_scalability(self): """ Theory: If you run with 100 iterations, it should take 100 times as long as running with 1 iteration. """ n1, n2 = 100, 10000 elapsed = lambda f: timeit.timeit(f, number=1) t1 = elapsed(lambda: pbkdf2("password", "salt", iterations=n1)) t2 = elapsed(lambda: pbkdf2("password", "salt", iterations=n2)) measured_scale_exponent = math.log(t2 / t1, n2 / n1) self.assertLess(measured_scale_exponent, 1.1)
def test_locked_hashes(self): # Forcefully lock the context and flush everything related to # accounts self.hasher.update(secret=None, thresholdlesskey=None, is_unlocked=False) cache.clear() password1 = make_share('password1') # get an account algorithm, sharenumber, iterations, salt, passhash = \ password1.split('$',4) passhash = passhash.encode('ascii').strip() iterations = int(iterations) self.assertTrue('pph' == algorithm) self.assertTrue(sharenumber.startswith('-')) # now let's do a plain pbkdf2 hash and compare the results proposed_hash = pbkdf2('password1', salt, iterations, digest=sha256) proposed_hash = b64encode(proposed_hash) self.assertTrue(proposed_hash == passhash)
def encode(self, password, salt, iterations=None): assert password is not None assert salt and '$' not in salt iterations = iterations or self.iterations hash = pbkdf2(password, salt, iterations, digest=self.digest) hash = base64.b64encode(hash).decode('ascii').strip() return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
def change_pw(request): if request.method == 'POST' and request.user.is_authenticated: if 'cipherKey' not in request.session: return redirect('verify_pw') form = PasswordChangeForm(request.user, request.POST) if form.is_valid(): user = form.save() update_session_auth_hash(request, user) data = Passwords.objects.filter(user=request.user) _, _, salt, _ = user.password.split('$') pw = form.cleaned_data['new_password1'] key = pbkdf2(pw, salt, 50000, 48) key, iv = key[16:], key[0:16] for obj in data: encryption_suite = AES.new( bytes.fromhex(request.session.get('cipherKey')), AES.MODE_CFB, bytes.fromhex(request.session.get('iv'))) obj.pw = encryption_suite.decrypt(bytes.fromhex( obj.pw)).decode('utf-8') new_encryption_suite = AES.new(key, AES.MODE_CFB, iv) obj.pw = new_encryption_suite.encrypt( obj.pw.encode('utf-8')).hex() obj.save() logout(request) return redirect('login') else: return render(request, 'password/change_pw.html', {'password_change_form': form}) else: form = PasswordChangeForm(request.user) return render(request, 'password/change_pw.html', {'password_change_form': form})
def test_update_hash_threshold(self): # we will remove the $ because we are going to create the entry # artificially salt = get_random_string(6).strip('$') password = '******' iterations = 12000 pbkdf2_encoded_password = pbkdf2(password, salt, iterations, digest=sha256) pbkdf2_encoded_password = b64encode(pbkdf2_encoded_password) polyhash, sharenumber = self.hasher.update_hash_threshold( pbkdf2_encoded_password) password_string = "{0}${1}${2}${3}${4}".format('pph', sharenumber, iterations, salt, polyhash) # TODO we should cehck whether they can provide partial verification self.assertTrue(check(password, password_string))
def post(self, request): user_id = str(self.request.data.get('user_id', '')).strip() if not user_id: return Response({'error': 'Missing user_id'}) try: login_user = User.objects.filter(id=user_id).get() except User.DoesNotExist: return Response({'error': 'Unknown user_id'}) # Remove existing tokens on user RatatoskrAdminLoginToken.objects.filter(login_user_id=user_id).delete() # Create a new token token_str = RatatoskrAdminLoginToken.generate_token() salt = crypto.get_random_string() iterations = randint(10000, 10100) encrypted_token = crypto.pbkdf2(token_str, salt, iterations) RatatoskrAdminLoginToken.objects.create( token=encrypted_token, salt=salt, iterations=iterations, login_user=login_user, admin_user=request.user, ) return Response({'token': token_str})
def get_revocation_key(user): """ When the value returned by this method changes, this revocates tokens. It is derived from the hashed password so that changing the password revokes tokens. For one-time tokens, it also contains the last login datetime so that logging in revokes existing tokens. """ data = "" if settings.INVALIDATE_ON_PASSWORD_CHANGE: data += user.password if settings.ONE_TIME: data += str(user.last_login) # The password is expected to be a secure hash but we hash it again # for additional safety. We default to MD5 to minimize the length of # the token. (Remember, if an attacker obtains the URL, he can already # log in. This isn't high security.) return crypto.pbkdf2( data, settings.SALT, settings.ITERATIONS, digest=settings.DIGEST, )
def post(self, request): phone_number = str(self.request.data.get('phone_number', '')).strip() users = lookup_users_by_phone(phone_number) users += self.lookup_local_users_by_phone(phone_number) if not len(users): return Response({'error': 'not found'}, status=403) SMSAuthCode.objects.filter(phone_number=phone_number).delete() auth_code = SMSAuthCode.generate_code() salt = crypto.get_random_string() iterations = randint(10000, 10100) encrypted_code = crypto.pbkdf2(auth_code, salt, iterations) code = SMSAuthCode.objects.create( phone_number=phone_number, code=encrypted_code, salt=salt, iterations=iterations ) for user in users: code.users.add(user) try: send_sms(phone_number, 'Din innloggingskode er: %s' % auth_code) except SMSGatewayDeliveryError: return Response({'error': 'unable to send sms'}, 503) except Exception: return Response({'error': 'server error'}, 503) return Response({'status': 'ok'})
def save(self): invitation_code = self.cleaned_data['invitation_code'] # make the invitation a invalid/spent. invite = UserRegisterCode.objects.get( code=str(invitation_code), used=False) new_user = User.objects.create_user( username=self.cleaned_data['username'], first_name=self.cleaned_data['first_name'], last_name=self.cleaned_data['last_name'], password=self.cleaned_data['password1'], email=self.cleaned_data['email'], is_active=False) UserProfile.objects.create(user=new_user, user_type="BEN", create_applications=False, ) # TODO: Add Crosswalk Create. Crosswalk.objects.create(user=new_user, user_id_hash=binascii.hexlify(pbkdf2(self.cleaned_data['id_number'], settings.USER_ID_SALT, settings.USER_ID_ITERATIONS)).decode("ascii")) # group = Group.objects.get(name='BlueButton') new_user.groups.add(group) # Send a verification email create_activation_key(new_user) invite.used = True invite.save() return new_user
def encrypt_match_report(apps, schema_editor): db_alias = schema_editor.connection.alias MatchReport = apps.get_model('delivery', 'MatchReport') for match_report in MatchReport.objects.using(db_alias).all(): match_report_content = MatchReportContent( identifier=match_report.identifier, perp_name=match_report.name, email=match_report.contact_email, phone=match_report.contact_phone, contact_name=match_report.contact_name, voicemail=match_report.contact_voicemail, notes=match_report.contact_notes) match_report.salt = get_random_string() stretched_identifier = pbkdf2( match_report.identifier, match_report.salt, settings.ORIGINAL_KEY_ITERATIONS, digest=hashlib.sha256) encrypted_match_report = security.pepper( security.encrypt_report( stretched_key=stretched_identifier, report_text=json.dumps( match_report_content.__dict__))) match_report.encrypted = encrypted_match_report if match_report.seen: match_report.identifier = None match_report.save()
def test_update_hash_thresholdless(self): # we will strip $ to avoid creating a threshold account by accident salt = get_random_string(6).strip('$') password = '******' iterations = 12000 pbkdf2_encoded_password = pbkdf2(password, salt, iterations, digest=sha256) pbkdf2_encoded_password = b64encode(pbkdf2_encoded_password) password_normally = make_password(password, salt, hasher='pph') password_normally = password_normally.split('$', 4)[4] password_through_update = self.hasher.update_hash_thresholdless( pbkdf2_encoded_password) self.assertTrue( constant_time_compare(password_normally, password_through_update)) # finally, try to log in with our updated password pass_string = "{0}${1}${2}${3}${4}".format('pph', 0, iterations, salt, password_through_update) # TODO we should cehck whether they can provide partial verification self.assertTrue(check(password, pass_string))
def parse_token(self, token): """ Obtain a user from a signed token. """ try: data = self.unsign(token) except signing.SignatureExpired: logger.debug("Expired token: %s", token) return except signing.BadSignature: logger.debug("Bad token: %s", token) return except Exception: logger.exception( "Valid signature but unexpected token - if you changed " "django-sesame settings, you must regenerate tokens") return user_pk, data = self.packer.unpack_pk(data) user = self.get_user(user_pk) if user is None: logger.debug("Unknown or inactive user: %s", user_pk) return h = crypto.pbkdf2( self.get_revocation_key(user), self.salt, self.iterations, digest=self.digest, ) if not crypto.constant_time_compare(data, h): logger.debug("Invalid token: %s", token) return logger.debug("Valid token for user %s: %s", user, token) return user
def parse_token(self, token): """ Obtain a user from a signed token. """ try: data = self.unsign(token) except signing.SignatureExpired: logger.debug("Expired token: %s", token) return except signing.BadSignature: logger.debug("Bad token: %s", token) return except Exception: logger.exception( "Valid signature but unexpected token - if you changed " "django-sesame settings, you must regenerate tokens" ) return user_pk, data = self.packer.unpack_pk(data) user = self.get_user(user_pk) if user is None: logger.debug("Unknown or inactive user: %s", user_pk) return h = crypto.pbkdf2( self.get_revocation_key(user), self.salt, self.iterations, digest=self.digest, ) if not crypto.constant_time_compare(data, h): logger.debug("Invalid token: %s", token) return logger.debug("Valid token for user %s: %s", user, token) return user
def parse_token(self, token): """ Obtain a user from a signed token. """ try: data = self.unsign(token) except signing.SignatureExpired: logger.debug("Expired token: %s", token) return except signing.BadSignature: logger.debug("Bad token: %s", token) return user = self.get_user(*struct.unpack(str('!i'), data[:4])) if user is None: logger.debug("Unknown token: %s", token) return h = crypto.pbkdf2(user.password, self.salt, self.iterations, digest=self.digest) if not crypto.constant_time_compare(data[4:], h): logger.debug("Invalid token: %s", token) return logger.debug("Valid token for user %s: %s", user, token) return user
def parse_token(self, token): """ Obtain a user from a signed token. """ try: data = self.unsign(token) except signing.SignatureExpired: logger.debug("Expired token: %s", token) return except signing.BadSignature: logger.debug("Bad token: %s", token) return pk_packer = self.pk_packer() user_pk = pk_packer.parse(data[:pk_packer.PACKED_SIZE]) user = self.get_user(user_pk) if user is None: logger.debug("Unknown token: %s", token) return h = crypto.pbkdf2(user.password, self.salt, self.iterations, digest=self.digest) if not crypto.constant_time_compare(data[pk_packer.PACKED_SIZE:], h): logger.debug("Invalid token: %s", token) return logger.debug("Valid token for user %s: %s", user, token) return user
def encrypt_match_report(apps, schema_editor): db_alias = schema_editor.connection.alias MatchReport = apps.get_model("delivery", "MatchReport") for match_report in MatchReport.objects.using(db_alias).all(): match_report_content = MatchReportContent( identifier=match_report.identifier, perp_name=match_report.name, email=match_report.contact_email, phone=match_report.contact_phone, contact_name=match_report.contact_name, voicemail=match_report.contact_voicemail, notes=match_report.contact_notes, ) match_report.salt = get_random_string() stretched_identifier = pbkdf2( match_report.identifier, match_report.salt, settings.ORIGINAL_KEY_ITERATIONS, digest=hashlib.sha256, ) encrypted_match_report = security.pepper( security.encrypt_report( stretched_key=stretched_identifier, report_text=json.dumps(match_report_content.__dict__), )) match_report.encrypted = encrypted_match_report if match_report.seen: match_report.identifier = None match_report.save()
def encode(self, password, salt, iterations=None): assert password assert salt and '$' not in salt if not iterations: iterations = self.iterations hash = pbkdf2(password, salt, iterations, digest=self.digest) hash = hash.encode('base64').strip() return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
def django_check_hash(password, hash): if not hash.startswith('pbkdf2_sha256'): raise ValueError('Invalid hash ' + hash) _type, iterations, salt, hashvalue = hash.split('$', 3) stored_hash = b64decode(hashvalue) given_hash = pbkdf2(password, salt, int(iterations)) return constant_time_compare(given_hash, stored_hash)
def check_password(self, raw_password): # Convert Play format to Django format iterations, salt, pbkdf2_hash = self.password.split(':') new_raw_hash = pbkdf2(raw_password, base64.b16decode(salt.upper()), iterations, 24, hashlib.sha1) new_hash = base64.b16encode(new_raw_hash).lower() return pbkdf2_hash == new_hash
def decrypted_report(self, key): stretched_key = pbkdf2(key, self.salt, settings.KEY_ITERATIONS, digest=hashlib.sha256) box = nacl.secret.SecretBox(stretched_key) decrypted = box.decrypt(bytes(self.encrypted)) return decrypted.decode('utf-8')
def makeCred(account, password): cred = Credential() cred.prf = "HMAC+SHA256" cred.salt = crypto.get_random_string() cred.iterations = 20000 cred.hash = base64.b64encode(crypto.pbkdf2(password, cred.salt, iterations=cred.iterations)) cred.account = account return cred
def encode(self, password, salt, iterations=None): assert password is not None assert salt and "$" not in salt if not iterations: iterations = self.iterations hash = pbkdf2(password, "", iterations, digest=self.digest, dklen=40) hash = base64.b64encode(hash) return "%s" % hash
def encode(self, password, salt, iterations=None): assert password assert salt and "$" not in salt if not iterations: iterations = self.iterations hash = pbkdf2(password, salt, iterations, digest=self.digest) hash = base64.b64encode(hash).decode("ascii").strip() return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
def hash_password(raw_password, salt, permutation, iterations=HASH_ITERATIONS): """ Hash data of the form "<raw_password>$<comma sep permutations>" """ permutation = ','.join(map(str, permutation)) password = '******' % (raw_password, permutation) hashed = pbkdf2(password, salt, iterations) return base64.b64encode(hashed).decode('ascii').strip()
def encode(self, password, salt, iterations=None): assert password is not None assert salt and '$' not in salt if not iterations: iterations = self.iterations hash = pbkdf2(password, salt, iterations, digest=sha256) hash = base64.b64encode(hash).decode('ascii').strip() return "%s$%d$%s$%s" % ("pbkdf2_sha256", iterations, salt, hash)
def encode(self, password, salt, iterations=None): # Override the standard encoding routine to encode the hash as hex rather than base64 and truncate at 40 chars # Alternately, this could run super().encode(), split the result by $, re-encode with hex, and reassemble assert password is not None assert salt and '$' not in salt iterations = iterations or self.iterations hash_ = pbkdf2(password, salt, iterations, digest=self.digest) hash_hex = hash_.hex()[:40] return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash_hex)
def create_token(self, user): """Create a signed token from an `auth.User`.""" # Include a hash derived from the password so changing the password # revokes the token. Usually, user.password will be a secure hash # already, but we hash it again in case it isn't. We use MD5 # to minimize the length of the token. (Remember, if an attacker # obtains the URL, he can already log in. This isn't high security.) h = crypto.pbkdf2(user.password, 'sesame', 10000, digest=hashlib.md5) return self.sign(struct.pack(str('!i'), user.pk) + h)
def validate_authentication(self, username, password, handler): try: password_info = self.user_table[username]['pwd'].split('$') user_password = base64.b64encode(pbkdf2(password, password_info[2], int(password_info[1]))) if user_password != password_info[3]: raise KeyError except KeyError: raise AuthenticationFailed
def hash_id_value(hicn): """ Hashes an MBI or HICN to match fhir server logic: Both currently use the same hash salt ENV values. https://github.com/CMSgov/beneficiary-fhir-data/blob/master/apps/bfd-pipeline/bfd-pipeline-rif-load/src/main/java/gov/cms/bfd/pipeline/rif/load/RifLoader.java#L665-L706 """ return binascii.hexlify( pbkdf2(hicn, get_user_id_salt(), settings.USER_ID_ITERATIONS)).decode("ascii")
def _is_valid_token(self, token, token_str): test_token = crypto.pbkdf2( token_str, token.salt, token.iterations ) if test_token == bytes(token.token): return True return False
def __init__(self, key, block_size=32): self.bs = block_size key = key.encode('UTF-8', errors='replace') if len(key) >= block_size: self.key = key[:block_size] else: self.key = self._pad(key) self.key_hash = crypto.pbkdf2(key, key[:10], 10000, 0) if len(self.key_hash) < AES.block_size: self.key_hash += b'0' * (AES.block_size - len(self.key_hash))
def generate_token(self): # Inspired by: https://github.com/aaugustin/django-sesame/ raw_token = struct.pack( str("!i"), self.invited_user.pk) + crypto.pbkdf2( self.invited_user.email, "radiologo", 10000) url_ready_token = signing.TimestampSigner(salt="radiologo").sign( signing.b64_encode(raw_token).decode()) self.sent_token = url_ready_token return url_ready_token
def test_default_hmac_alg(self): kwargs = { 'password': b'password', 'salt': b'salt', 'iterations': 1, 'dklen': 20 } self.assertEqual( pbkdf2(**kwargs), hashlib.pbkdf2_hmac(hash_name=hashlib.sha256().name, **kwargs))
def hexlify_pbkdf2(password, salt, iterations, length, digest=hashlib.sha1): """ >>> hash = hexlify_pbkdf2("not secret", "a salt value", iterations=100, length=16) >>> hash == '0b919231515dde16f76364666cf07107' True """ # log.debug("hexlify_pbkdf2 with iterations=%i", iterations) hash = crypto.pbkdf2(password, salt, iterations=iterations, dklen=length, digest=digest) hash = binascii.hexlify(hash) hash = six.text_type(hash, "ascii") return hash
def test_default_hmac_alg(self): kwargs = { "password": b"password", "salt": b"salt", "iterations": 1, "dklen": 20, } self.assertEqual( pbkdf2(**kwargs), hashlib.pbkdf2_hmac(hash_name=hashlib.sha256().name, **kwargs), )
def encrypt_report(self, report_text, key): if not self.salt: self.salt = get_random_string() else: self.last_edited = timezone.now() stretched_key = pbkdf2(key, self.salt, settings.KEY_ITERATIONS, digest=hashlib.sha256) box = nacl.secret.SecretBox(stretched_key) message = report_text.encode('utf-8') nonce = nacl.utils.random(nacl.secret.SecretBox.NONCE_SIZE) encrypted = box.encrypt(message, nonce) self.encrypted = encrypted
def verify_pw(request): if request.method == 'POST': if check_password(request.POST.get("Password"), request.user.password): _, _, salt, _ = request.user.password.split('$') key = pbkdf2(request.POST.get("Password"), salt, 50000, 48) request.session['iv'] = key[0:16].hex() request.session['cipherKey'] = key[16:].hex() return redirect('/password/') else: messages.error(request, 'Incorrect Password') return render(request, "password/verify_pw.html", {})
def hash_hicn(hicn): """ Hashes a hicn to match fhir server logic: https://github.com/CMSgov/beneficiary-fhir-data/blob/master/apps/bfd-pipeline/bfd-pipeline-rif-load/src/main/java/gov/cms/bfd/pipeline/rif/load/RifLoader.java#L665-L706 """ assert hicn != "", "HICN cannot be the empty string" return binascii.hexlify(pbkdf2(hicn, get_user_id_salt(), settings.USER_ID_ITERATIONS)).decode("ascii")
def _get_password_hash(self, password, fixed_salt=None): """ Return password after hash operation :param password: String with plain text password :param fixed_salt: If Set, use the given fixed_salt in pbkdf2 operation instead of computing a new one This is used to compare existing password :return: String with hashed password """ if self.password_salt is not None: salt = self.password_salt if self.password_salt_position == 'begin': password = "******".format(salt, password) elif self.password_salt_position == 'end': password = "******".format(password, salt) hash_algo = self.password_hash_algo if not isinstance(password, bytes): password = str(password).encode('utf8') if hash_algo == 'plain': return password elif hash_algo == 'md5': return hashlib.md5(password).hexdigest() elif hash_algo == 'sha1': return hashlib.sha1(password).hexdigest() elif hash_algo == 'sha256': return hashlib.sha256(password).hexdigest() elif hash_algo == 'sha384': return hashlib.sha384(password).hexdigest() elif hash_algo == 'sha512': return hashlib.sha512(password).hexdigest() elif hash_algo == 'make_password': return make_password(password) elif hash_algo.startswith('pbkdf2'): args = hash_algo.split('-') hash = args[1] iter = args[2] for alg in hashlib.algorithms_available: if str(hash) in alg: algo = getattr(hashlib, 'sha' + str(hash)) if not fixed_salt: rdm_str = get_random_string() else: rdm_str = fixed_salt pwd = base64.b64encode( pbkdf2(password, rdm_str, int(iter), digest=algo)).decode('utf8') return "pbkdf2_sha" + str(hash) + '$' + str( iter) + '$' + rdm_str + '$' + pwd else: raise NotImplemented("Password hash algorithm not implemented, " "algo: {}".format(hash_algo))
def get_bucket_key(self, *args, **kwargs): ip_address = super(HashedRemoteIP, self).get_bucket_key(*args, **kwargs) bucket_key = pbkdf2(ip_address, self.get_salt(), self.get_iterations(), digest=hashlib.sha256) bucket_key = binascii.b2a_hex(bucket_key) return bucket_key
def post(self, request): phone_number = str(self.request.data.get('phone_number', '')).strip() search_for_code = str(self.request.data.get('code', '')).strip() codes = SMSAuthCode.objects.filter(phone_number=phone_number).all() for code in codes: test_code = crypto.pbkdf2( search_for_code, code.salt, code.iterations ) if test_code == bytes(code.code): if code.expires < datetime.now(): code.delete() return Response({'error': 'code expired'}, status=403) auth_token = SMSAuthToken.generate_token() salt = crypto.get_random_string() iterations = randint(10000, 10100) encrypted = crypto.pbkdf2(auth_token, salt, iterations) token = SMSAuthToken.objects.create( phone_number=phone_number, token=encrypted, salt=salt, iterations=iterations ) for user in code.users.all(): token.users.add(user) serialized_users = [ UserSerializer(u).data for u in code.users.all() ] code.delete() return Response({ 'users': serialized_users, 'token': auth_token, }) return Response({'error': 'invalid'}, status=403)
def create_token(self, user): """ Create a signed token from a user. """ # Include a hash derived from the password so changing the password # revokes the token. Usually, user.password will be a secure hash # already, but we hash it again in case it isn't. We default to MD5 # to minimize the length of the token. (Remember, if an attacker # obtains the URL, he can already log in. This isn't high security.) h = crypto.pbkdf2( user.password, self.salt, self.iterations, digest=self.digest) return self.sign(struct.pack(str('!i'), user.pk) + h)
def begin_two_factor_authentication(self, session): # Generate a TFA key and salt, store them for later verification tfa_key = str(uuid.uuid4()) tfa_salt = str(uuid.uuid4()) session['tfa_key'] = tfa_key session['tfa_salt'] = tfa_salt # Generate the signature to send back to the user tfa_sig = base64.b16encode(pbkdf2(tfa_key + self.password, tfa_salt, 1000, 24)) return { 'status': 'tfa', 'tfa_userid': self.id, 'tfa_signature': tfa_sig }
def create_token(self, user): """ Create a signed token from a user. """ # The password is expected to be a secure hash but we hash it again # for additional safety. We default to MD5 to minimize the length of # the token. (Remember, if an attacker obtains the URL, he can already # log in. This isn't high security.) h = crypto.pbkdf2( self.get_revocation_key(user), self.salt, self.iterations, digest=self.digest, ) return self.sign(self.packer.pack_pk(user.pk) + h)
def parse_token(self, token): """Obtain an `auth.User` from a signed token.""" try: data = self.unsign(token) except signing.BadSignature: logger.debug("Invalid token: %s", token) return user = self.get_user(*struct.unpack(str('!i'), data[:4])) if user is None: logger.debug("Unknown token: %s", token) return h = crypto.pbkdf2(user.password, 'sesame', 10000, digest=hashlib.md5) if not crypto.constant_time_compare(data[4:], h): logger.debug("Expired token: %s", token) return logger.debug("Valid token for user %s: %s", user, token) return user
def _encrypt_report(salt, key, report_text): """Encrypts a report using the given secret key & salt. The secret key is stretched to 32 bytes using Django's PBKDF2+SHA256 implementation. The encryption uses PyNacl & Salsa20 stream cipher. Args: salt (str): cryptographic salt key (str): secret key report_text (str): full report as a string Returns: bytes: the encrypted bytes of the report """ stretched_key = pbkdf2(key, salt, settings.KEY_ITERATIONS, digest=hashlib.sha256) box = nacl.secret.SecretBox(stretched_key) message = report_text.encode('utf-8') nonce = nacl.utils.random(nacl.secret.SecretBox.NONCE_SIZE) return box.encrypt(message, nonce)
def _decrypt_report(salt, key, encrypted): """Decrypts an encrypted report. Args: salt (str): cryptographic salt key (str): secret key encrypted (bytes): full report encrypted Returns: str: the decrypted report as a string Raises: CryptoError: If the key and salt fail to decrypt the record. """ stretched_key = pbkdf2(key, salt, settings.KEY_ITERATIONS, digest=hashlib.sha256) box = nacl.secret.SecretBox(stretched_key) decrypted = box.decrypt(bytes(encrypted)) # need to force to bytes bc BinaryField can return as memoryview return decrypted.decode('utf-8')
def _is_valid_token(self, request, token, password, user_id): test_token = crypto.pbkdf2( password, token.salt, token.iterations ) if test_token == bytes(token.token): if token.expires < datetime.now(): return False if token.users.count() == 1: request.user = token.users.all()[0] return True elif token.users.count() > 1: if not user_id: return False user = [u for u in token.users.all() if u.id == user_id] if len(user) == 1: request.user = user[0] return True return False
def decrypt_secret(secret, password, username): """ We will get passed in a 16 byte block of data that has been enrypted with AES using the PIN the first 10 bytes are the secret and the remaining 6 are padding :param secret: The secret. :type secret: str :param password: The password. :type password: str :param username: The user this is for. :type username: str :returns: str """ secret = base64.b32decode(secret) pw_hash = pbkdf2(password.encode('ascii'), username.encode('ascii'), 10000) cipher = AES.new(pw_hash, AES.MODE_ECB, '') tmp = cipher.decrypt(secret) return tmp[:10]
def decrypt_secret(secret, password, username): """ We will get passed in a 16 byte block of data that has been enrypted with AES using the PIN the first 10 bytes are the secret and the remaining 6 are padding :param secret: The secret. :type secret: str :param password: The password. :type password: str :param username: The user this is for. :type username: str :returns: str """ secret = base64.b32decode(secret) pw_hash = pbkdf2(password.encode('ascii'), username.encode('ascii'), 10000) crypt_object = EVP.Cipher('aes_128_ecb', pw_hash, '', 0, padding=0) tmp = crypt_object.update(secret) return tmp[:10]
def authenticate(self, session=None, user_id=None, token=None, signature=None): # Ensure that we have TFA parameters if token is None or signature is None: return None # Fetch the user try: user = User.objects.get(pk=user_id) except: return None # Verify the signature from the user is the one we generated expected_tfa_sig = base64.b16encode(pbkdf2(session.get('tfa_key', '') + user.password, session.get('tfa_salt', ''), 1000, 24)) if expected_tfa_sig != signature: return None # Verify the token from the user expected_token = str(get_totp_token(user.totp_secret.upper())).zfill(6) if expected_token == token: return user return None
def encrypt_secret(secret, password, username): """ Pad the secret with 6 random bytes which makes the secret 16 bytes for ECB but also makes all encrypted secrets unique even if they have the same original value which is important for cases where we might allow a user to carry over an existing secret from another system :param secret: The secret. :type secret: str :param password: The password. :type password: str :param username: The user this is for. :type username: str :returns: str """ secret += gen_random(6) pw_hash = pbkdf2(password.encode('ascii'), username.encode('ascii'), 10000) cipher = AES.new(pw_hash, AES.MODE_ECB, '') tmp = cipher.encrypt(secret) return tmp
def encrypt_secret(secret, password, username): """ Pad the secret with 6 random bytes which makes the secret 16 bytes for ECB but also makes all encrypted secrets unique even if they have the same oroginal value which is important for cases where we might allow a user to carry over an existing secret from another system :param secret: The secret. :type secret: str :param password: The password. :type password: str :param username: The user this is for. :type username: str :returns: str """ secret += gen_random(6) pw_hash = pbkdf2(password.encode('ascii'), username.encode('ascii'), 10000) crypt_object = EVP.Cipher('aes_128_ecb', pw_hash, '', 1, padding=0) tmp = crypt_object.update(secret) return tmp