From 19ad11e383c8cf6e056e1640a56d82ba42d3c736 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 30 Sep 2021 10:24:17 +0100 Subject: [PATCH] =?UTF-8?q?Don=E2=80=99t=20repeat=20digits=20in=20security?= =?UTF-8?q?=20codes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit People with dyslexia and dyscalculia find it difficult to transpose codes which have consecutive, repeated digits[1]. This commits enhances the algorithm for generating codes to not repeat the previous digit in a code. This reduces the key space for our codes from 100,000 possibilities to 65,610 possibilities. 1. https://twitter.com/annaecook/status/1442567679710150662 --- app/dao/users_dao.py | 10 +++++++++- tests/app/dao/test_users_dao.py | 12 ++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/dao/users_dao.py b/app/dao/users_dao.py index c9e6e2d9b..d24ac1414 100644 --- a/app/dao/users_dao.py +++ b/app/dao/users_dao.py @@ -20,7 +20,15 @@ def _remove_values_for_keys_if_present(dict, keys): def create_secret_code(): - return ''.join(map(str, [SystemRandom().randrange(10) for i in range(5)])) + return ''.join(get_non_repeating_random_digits(5)) + + +def get_non_repeating_random_digits(length): + output = [None] * length + for index in range(length): + while output[index] in {None, output[index - 1]}: + output[index] = str(SystemRandom().randrange(10)) + return output def save_user_attribute(usr, update_dict=None): diff --git a/tests/app/dao/test_users_dao.py b/tests/app/dao/test_users_dao.py index 2887c551c..e163d1e14 100644 --- a/tests/app/dao/test_users_dao.py +++ b/tests/app/dao/test_users_dao.py @@ -188,6 +188,18 @@ def test_create_secret_code_returns_5_digits(): assert len(str(code)) == 5 +def test_create_secret_code_never_repeats_consecutive_digits(mocker): + mocker.patch('app.dao.users_dao.SystemRandom.randrange', side_effect=[ + 1, 1, 1, + 2, + 3, + 4, 4, + 1, # Repeated allowed if not consecutive + 9, 9, # Not called because we have 5 digits now + ]) + assert create_secret_code() == '12341' + + @freeze_time('2018-07-07 12:00:00') def test_dao_archive_user(sample_user, sample_organisation, fake_uuid): sample_user.current_session_id = fake_uuid