refactor; rename classes and methods

This commit is contained in:
2025-03-10 09:26:55 -05:00
parent c1ca01eb93
commit 571268b86a
15 changed files with 160 additions and 160 deletions

View File

@@ -2,7 +2,7 @@ from jinja2 import Environment, FileSystemLoader
import os import os
from src.nkode_api import NKodeAPI from src.nkode_api import NKodeAPI
from src.models import NKodePolicy, KeypadSize, EncipheredNKode from src.models import NKodePolicy, KeypadSize, EncipheredNKode
from src.user_cipher_keys import UserCipherKeys from src.user_cipher_keys import UserCipher
from src.utils import list_to_matrix, matrix_transpose, xor_lists from src.utils import list_to_matrix, matrix_transpose, xor_lists
from secrets import choice from secrets import choice
from string import ascii_lowercase from string import ascii_lowercase
@@ -58,16 +58,16 @@ if __name__ == "__main__":
) )
keypad_size = KeypadSize( keypad_size = KeypadSize(
numb_of_keys=5, numb_of_keys=5,
attrs_per_key=6 # aka number of sets props_per_key=6 # aka number of sets
) )
customer_id = api.create_new_customer(keypad_size, policy) customer_id = api.create_new_customer(keypad_size, policy)
customer = api.customers[customer_id] customer = api.customers[customer_id]
set_vals = customer.attributes.set_vals set_vals = customer.customer_cipher.set_key
attr_vals = customer.attributes.attr_vals attr_vals = customer.customer_cipher.prop_key
customer_attr_view = list_to_matrix(attr_vals, keypad_size.attrs_per_key) customer_attr_view = list_to_matrix(attr_vals, keypad_size.props_per_key)
attr_keypad_view = list_to_matrix(attr_vals, keypad_size.attrs_per_key) attr_keypad_view = list_to_matrix(attr_vals, keypad_size.props_per_key)
attr_set_view = matrix_transpose(attr_keypad_view) attr_set_view = matrix_transpose(attr_keypad_view)
set_attribute_dict = dict(zip(set_vals, attr_set_view)) set_attribute_dict = dict(zip(set_vals, attr_set_view))
@@ -78,7 +78,7 @@ if __name__ == "__main__":
passcode_len = 4 passcode_len = 4
user_passcode = signup_interface[:passcode_len] user_passcode = signup_interface[:passcode_len]
selected_keys_set = select_keys_with_passcode_values(user_passcode, signup_interface, keypad_size.numb_of_keys) selected_keys_set = select_keys_with_passcode_values(user_passcode, signup_interface, keypad_size.numb_of_keys)
server_side_attr = [customer.attributes.attr_vals[idx] for idx in user_passcode] server_side_attr = [customer.customer_cipher.prop_key[idx] for idx in user_passcode]
confirm_interface = api.set_nkode(username, customer_id, selected_keys_set, session_id) confirm_interface = api.set_nkode(username, customer_id, selected_keys_set, session_id)
@@ -88,20 +88,20 @@ if __name__ == "__main__":
success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id) success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id)
assert success assert success
passcode_server_attr = [customer.attributes.attr_vals[idx] for idx in user_passcode] passcode_server_attr = [customer.customer_cipher.prop_key[idx] for idx in user_passcode]
passcode_server_set = [customer.attributes.get_attr_set_val(attr) for attr in passcode_server_attr] passcode_server_set = [customer.customer_cipher.get_prop_set_val(attr) for attr in passcode_server_attr]
user_keys = customer.users[username].user_keys user_keys = customer.users[username].user_keys
padded_passcode_server_set = user_keys.pad_user_mask(passcode_server_set, customer.attributes.set_vals) padded_passcode_server_set = user_keys.pad_user_mask(passcode_server_set, customer.customer_cipher.set_key)
set_idx = [customer.attributes.get_set_index(set_val) for set_val in padded_passcode_server_set] set_idx = [customer.customer_cipher.get_set_index(set_val) for set_val in padded_passcode_server_set]
mask_set_keys = [user_keys.set_key[idx] for idx in set_idx] mask_set_keys = [user_keys.set_key[idx] for idx in set_idx]
ciphered_mask = xor_lists(mask_set_keys, padded_passcode_server_set) ciphered_mask = xor_lists(mask_set_keys, padded_passcode_server_set)
ciphered_mask = xor_lists(ciphered_mask, user_keys.mask_key) ciphered_mask = xor_lists(ciphered_mask, user_keys.mask_key)
mask = user_keys.encode_base64_str(ciphered_mask) mask = user_keys.encode_base64_str(ciphered_mask)
ciphered_customer_attrs = xor_lists(customer.attributes.attr_vals, user_keys.alpha_key) ciphered_customer_attrs = xor_lists(customer.customer_cipher.prop_key, user_keys.alpha_key)
passcode_ciphered_attrs = [ciphered_customer_attrs[idx] for idx in user_passcode] passcode_ciphered_attrs = [ciphered_customer_attrs[idx] for idx in user_passcode]
pad_len = customer.nkode_policy.max_nkode_len - passcode_len pad_len = customer.nkode_policy.max_nkode_len - passcode_len
@@ -122,8 +122,8 @@ if __name__ == "__main__":
USER LOGIN USER LOGIN
""" """
login_interface = api.get_login_interface(username, customer_id) login_interface = api.get_login_interface(username, customer_id)
login_keypad = list_to_matrix(login_interface, keypad_size.attrs_per_key) login_keypad = list_to_matrix(login_interface, keypad_size.props_per_key)
selected_keys_login = select_keys_with_passcode_values(user_passcode, login_interface, keypad_size.attrs_per_key) selected_keys_login = select_keys_with_passcode_values(user_passcode, login_interface, keypad_size.props_per_key)
success = api.login(customer_id, username, selected_keys_login) success = api.login(customer_id, username, selected_keys_login)
assert success assert success
@@ -133,7 +133,7 @@ if __name__ == "__main__":
""" """
user = customer.users[username] user = customer.users[username]
set_vals = customer.attributes.set_vals set_vals = customer.customer_cipher.set_key
user_keys = user.user_keys user_keys = user.user_keys
user_mask = user.enciphered_passcode.mask user_mask = user.enciphered_passcode.mask
decoded_mask = user_keys.decode_base64_str(user_mask) decoded_mask = user_keys.decode_base64_str(user_mask)
@@ -148,7 +148,7 @@ if __name__ == "__main__":
GET PRESUMED ATTRIBUTES GET PRESUMED ATTRIBUTES
""" """
set_vals_idx = [customer.attributes.get_set_index(set_val) for set_val in login_passcode_sets] set_vals_idx = [customer.customer_cipher.get_set_index(set_val) for set_val in login_passcode_sets]
presumed_selected_attributes_idx = [] presumed_selected_attributes_idx = []
for idx in range(passcode_len): for idx in range(passcode_len):
@@ -161,12 +161,12 @@ if __name__ == "__main__":
RENEW KEYS RENEW KEYS
""" """
old_attrs = customer.attributes.attr_vals.copy() old_attrs = customer.customer_cipher.prop_key.copy()
old_sets = customer.attributes.set_vals.copy() old_sets = customer.customer_cipher.set_key.copy()
customer.attributes.renew() customer.customer_cipher.renew()
new_attrs = customer.attributes.attr_vals new_attrs = customer.customer_cipher.prop_key
new_sets = customer.attributes.set_vals new_sets = customer.customer_cipher.set_key
customer_new_attr_view = list_to_matrix(new_attrs, keypad_size.attrs_per_key) customer_new_attr_view = list_to_matrix(new_attrs, keypad_size.props_per_key)
""" """
RENEW USER RENEW USER
@@ -181,12 +181,12 @@ if __name__ == "__main__":
""" """
REFRESH USER KEYS REFRESH USER KEYS
""" """
user.user_keys = UserCipherKeys.create( user.user_keys = UserCipher.create(
customer.attributes.keypad_size, customer.customer_cipher.keypad_size,
customer.attributes.set_vals, customer.customer_cipher.set_key,
user.user_keys.max_nkode_len user.user_keys.max_nkode_len
) )
user.enciphered_passcode = user.user_keys.encipher_nkode(presumed_selected_attributes_idx, customer.attributes) user.enciphered_passcode = user.user_keys.encipher_nkode(presumed_selected_attributes_idx, customer.customer_cipher)
user.renew = False user.renew = False
# Define some data to pass to the template # Define some data to pass to the template

View File

@@ -29,10 +29,10 @@
"from src.models import KeypadSize\n", "from src.models import KeypadSize\n",
"\n", "\n",
"def keypad_md_table(interface: list[int], keypad_size: KeypadSize) -> str:\n", "def keypad_md_table(interface: list[int], keypad_size: KeypadSize) -> str:\n",
" assert (keypad_size.numb_of_attrs == len(interface))\n", " assert (keypad_size.numb_of_props == len(interface))\n",
" interface_keypad = list_to_matrix(interface, keypad_size.attrs_per_key)\n", " interface_keypad = list_to_matrix(interface, keypad_size.props_per_key)\n",
" table = \"|key|\" + \"\".join([f\"set{idx}|\" for idx in range(keypad_size.attrs_per_key)])\n", " table = \"|key|\" + \"\".join([f\"set{idx}|\" for idx in range(keypad_size.props_per_key)])\n",
" table += \"\\n|\" + \"\".join(\"-|\" for _ in range(keypad_size.attrs_per_key+1))\n", " table += \"\\n|\" + \"\".join(\"-|\" for _ in range(keypad_size.props_per_key + 1))\n",
"\n", "\n",
" for key in range(keypad_size.numb_of_keys):\n", " for key in range(keypad_size.numb_of_keys):\n",
" table += f\"\\n|key{key+1}|\"\n", " table += f\"\\n|key{key+1}|\"\n",
@@ -41,7 +41,7 @@
" return table\n", " return table\n",
"\n", "\n",
"\n", "\n",
"keypad_size = KeypadSize(numb_of_keys=5, attrs_per_key=4)\n", "keypad_size = KeypadSize(numb_of_keys=5, props_per_key=4)\n",
"attrs = [1, 10, 11, 100]\n", "attrs = [1, 10, 11, 100]\n",
"keypad = []\n", "keypad = []\n",
"for key_numb in range(1,keypad_size.numb_of_keys+1):\n", "for key_numb in range(1,keypad_size.numb_of_keys+1):\n",
@@ -73,7 +73,7 @@
} }
], ],
"source": [ "source": [
"demo_interface_matrix = list_to_matrix(demo_interface.keypad, demo_interface.keypad_size.attrs_per_key)\n", "demo_interface_matrix = list_to_matrix(demo_interface.keypad, demo_interface.keypad_size.props_per_key)\n",
"shuffled_keys = secure_fisher_yates_shuffle(demo_interface_matrix)\n", "shuffled_keys = secure_fisher_yates_shuffle(demo_interface_matrix)\n",
"shuffled_keys_list = matrix_to_list(shuffled_keys)\n", "shuffled_keys_list = matrix_to_list(shuffled_keys)\n",
"display(Markdown(keypad_md_table(shuffled_keys_list, keypad_size)))\n" "display(Markdown(keypad_md_table(shuffled_keys_list, keypad_size)))\n"
@@ -100,7 +100,7 @@
} }
], ],
"source": [ "source": [
"attr_rotation = secure_fisher_yates_shuffle(list(range(keypad_size.numb_of_keys)))[:keypad_size.attrs_per_key]\n", "attr_rotation = secure_fisher_yates_shuffle(list(range(keypad_size.numb_of_keys)))[:keypad_size.props_per_key]\n",
"dispersed_interface = UserKeypad.random_attribute_rotation(\n", "dispersed_interface = UserKeypad.random_attribute_rotation(\n",
" shuffled_keys,\n", " shuffled_keys,\n",
" attr_rotation\n", " attr_rotation\n",

View File

@@ -1,6 +1,6 @@
from dataclasses import dataclass from dataclasses import dataclass
from uuid import UUID from uuid import UUID
from src.customer_attributes import CustomerAttributes from src.customer_cipher import CustomerCipher
from src.models import NKodePolicy from src.models import NKodePolicy
from src.user import User from src.user import User
from src.utils import xor_lists from src.utils import xor_lists
@@ -9,7 +9,7 @@ from src.utils import xor_lists
class Customer: class Customer:
customer_id: UUID customer_id: UUID
nkode_policy: NKodePolicy nkode_policy: NKodePolicy
attributes: CustomerAttributes customer_cipher: CustomerCipher
users: dict[str, User] users: dict[str, User]
# TODO: validate policy and keypad size don't conflict # TODO: validate policy and keypad size don't conflict
@@ -23,16 +23,16 @@ class Customer:
if username not in self.users: if username not in self.users:
raise ValueError(f"User '{username}' does not exist") raise ValueError(f"User '{username}' does not exist")
keypad_size = self.attributes.keypad_size.numb_of_keys numb_of_keys = self.customer_cipher.keypad_size.numb_of_keys
if not all(0 <= key_idx < keypad_size for key_idx in selected_keys): if not all(0 <= key_idx < numb_of_keys for key_idx in selected_keys):
raise ValueError(f"Invalid key indices. Must be between 0 and {keypad_size - 1}") raise ValueError(f"Invalid key indices. Must be between 0 and {numb_of_keys - 1}")
passcode_len = len(selected_keys) passcode_len = len(selected_keys)
user = self.users[username] user = self.users[username]
passcode_set_vals = user.user_keys.decipher_mask( passcode_set_vals = user.user_keys.decipher_mask(
user.enciphered_passcode.mask, self.attributes.set_vals, passcode_len) user.enciphered_passcode.mask, self.customer_cipher.set_key, passcode_len)
set_vals_idx = [self.attributes.get_set_index(set_val) for set_val in passcode_set_vals] set_vals_idx = [self.customer_cipher.get_set_index(set_val) for set_val in passcode_set_vals]
presumed_selected_attributes_idx = [] presumed_selected_attributes_idx = []
for idx in range(passcode_len): for idx in range(passcode_len):
@@ -41,20 +41,20 @@ class Customer:
selected_attr_idx = user.user_keypad.get_attr_idx_by_keynumb_setidx(key_numb, set_idx) selected_attr_idx = user.user_keypad.get_attr_idx_by_keynumb_setidx(key_numb, set_idx)
presumed_selected_attributes_idx.append(selected_attr_idx) presumed_selected_attributes_idx.append(selected_attr_idx)
enciphered_attr = user.user_keys.encipher_salt_hash_code(presumed_selected_attributes_idx, self.attributes) enciphered_attr = user.user_keys.encipher_salt_hash_code(presumed_selected_attributes_idx, self.customer_cipher)
if enciphered_attr != user.enciphered_passcode.code: if enciphered_attr != user.enciphered_passcode.code:
return False return False
if user.renew: if user.renew:
user.refresh_passcode(presumed_selected_attributes_idx, self.attributes) user.refresh_passcode(presumed_selected_attributes_idx, self.customer_cipher)
return True return True
def renew_keys(self) -> bool: def renew_keys(self) -> bool:
old_attrs = self.attributes.attr_vals.copy() old_attrs = self.customer_cipher.prop_key.copy()
old_sets = self.attributes.set_vals.copy() old_sets = self.customer_cipher.set_key.copy()
self.attributes.renew() self.customer_cipher.renew()
new_attrs = self.attributes.attr_vals new_attrs = self.customer_cipher.prop_key
new_sets = self.attributes.set_vals new_sets = self.customer_cipher.set_key
attrs_xor = xor_lists(new_attrs, old_attrs) attrs_xor = xor_lists(new_attrs, old_attrs)
set_xor = xor_lists(new_sets, old_sets) set_xor = xor_lists(new_sets, old_sets)
@@ -66,7 +66,7 @@ class Customer:
def valid_new_nkode(self, passcode_attr_idx: list[int]) -> bool: def valid_new_nkode(self, passcode_attr_idx: list[int]) -> bool:
nkode_len = len(passcode_attr_idx) nkode_len = len(passcode_attr_idx)
passcode_set_values = [ passcode_set_values = [
self.attributes.get_attr_set_val(self.attributes.attr_vals[attr_idx]) for attr_idx in passcode_attr_idx self.customer_cipher.get_prop_set_val(self.customer_cipher.prop_key[attr_idx]) for attr_idx in passcode_attr_idx
] ]
distinct_sets = len(set(passcode_set_values)) distinct_sets = len(set(passcode_set_values))
distinct_attributes = len(set(passcode_attr_idx)) distinct_attributes = len(set(passcode_attr_idx))

View File

@@ -1,47 +0,0 @@
from dataclasses import dataclass
from typing import ClassVar
from src.models import KeypadSize
from src.utils import generate_random_nonrepeating_list
@dataclass
class CustomerAttributes:
attr_vals: list[int]
set_vals: list[int]
keypad_size: KeypadSize
MAX_KEYS: ClassVar[int] = 256
MAX_ATTRS_PER_KEY: ClassVar[int] = 256
def __post_init__(self):
self.check_keys_vs_attrs()
def check_keys_vs_attrs(self) -> None:
if self.keypad_size.is_dispersable:
raise ValueError("number of keys must be less than the number of "
"attributes per key to be dispersion resistant")
@classmethod
def create(cls, keypad_size: KeypadSize) -> 'CustomerAttributes':
if keypad_size.numb_of_keys > cls.MAX_KEYS or keypad_size.attrs_per_key > cls.MAX_ATTRS_PER_KEY:
raise ValueError(f"Keys and attributes per key must not exceed {cls.MAX_KEYS}")
return cls(
attr_vals=generate_random_nonrepeating_list(keypad_size.numb_of_attrs),
set_vals=generate_random_nonrepeating_list(keypad_size.attrs_per_key),
keypad_size=keypad_size,
)
def renew(self):
self.attr_vals = generate_random_nonrepeating_list(self.keypad_size.numb_of_attrs)
self.set_vals = generate_random_nonrepeating_list(self.keypad_size.attrs_per_key)
def get_attr_set_val(self, attr: int) -> int:
assert (attr in self.attr_vals)
attr_idx = self.attr_vals.index(attr)
set_idx = attr_idx % self.keypad_size.attrs_per_key
return self.set_vals[set_idx]
def get_set_index(self, set_val: int) -> int:
if set_val not in self.set_vals:
raise ValueError(f"Set value {set_val} not found in set values")
return self.set_vals.index(set_val)

47
src/customer_cipher.py Normal file
View File

@@ -0,0 +1,47 @@
from dataclasses import dataclass
from typing import ClassVar
from src.models import KeypadSize
from src.utils import generate_random_nonrepeating_list
@dataclass
class CustomerCipher:
prop_key: list[int]
set_key: list[int]
keypad_size: KeypadSize
MAX_KEYS: ClassVar[int] = 256
MAX_PROP_PER_KEY: ClassVar[int] = 256
def __post_init__(self):
self.check_keys_vs_props()
def check_keys_vs_props(self) -> None:
if self.keypad_size.is_dispersable:
raise ValueError("number of keys must be less than the number of "
"properties per key to be dispersion resistant")
@classmethod
def create(cls, keypad_size: KeypadSize) -> 'CustomerCipher':
if keypad_size.numb_of_keys > cls.MAX_KEYS or keypad_size.props_per_key > cls.MAX_PROP_PER_KEY:
raise ValueError(f"Keys and properties per key must not exceed {cls.MAX_KEYS}")
return cls(
prop_key=generate_random_nonrepeating_list(keypad_size.numb_of_props),
set_key=generate_random_nonrepeating_list(keypad_size.props_per_key),
keypad_size=keypad_size,
)
def renew(self):
self.prop_key = generate_random_nonrepeating_list(self.keypad_size.numb_of_props)
self.set_key = generate_random_nonrepeating_list(self.keypad_size.props_per_key)
def get_prop_set_val(self, prop: int) -> int:
assert (prop in self.prop_key)
prop_idx = self.prop_key.index(prop)
set_idx = prop_idx % self.keypad_size.props_per_key
return self.set_key[set_idx]
def get_set_index(self, set_val: int) -> int:
if set_val not in self.set_key:
raise ValueError(f"Set value {set_val} not found in set values")
return self.set_key.index(set_val)

View File

@@ -19,13 +19,13 @@ class NKodePolicy:
@dataclass @dataclass
class KeypadSize: class KeypadSize:
attrs_per_key: int props_per_key: int
numb_of_keys: int numb_of_keys: int
@property @property
def numb_of_attrs(self) -> int: def numb_of_props(self) -> int:
return self.attrs_per_key * self.numb_of_keys return self.props_per_key * self.numb_of_keys
@property @property
def is_dispersable(self) -> bool: def is_dispersable(self) -> bool:
return self.attrs_per_key <= self.numb_of_keys return self.props_per_key <= self.numb_of_keys

View File

@@ -5,10 +5,10 @@ from typing import Dict, List, Tuple
from src.customer import Customer from src.customer import Customer
from src.models import NKodePolicy, KeypadSize from src.models import NKodePolicy, KeypadSize
from src.user import User from src.user import User
from src.user_cipher_keys import UserCipherKeys from src.user_cipher_keys import UserCipher
from src.user_signup_session import UserSignupSession from src.user_signup_session import UserSignupSession
from src.user_keypad import UserKeypad from src.user_keypad import UserKeypad
from src.customer_attributes import CustomerAttributes from src.customer_cipher import CustomerCipher
@dataclass @dataclass
@@ -19,7 +19,7 @@ class NKodeAPI:
def create_new_customer(self, keypad_size: KeypadSize, nkode_policy: NKodePolicy) -> UUID: def create_new_customer(self, keypad_size: KeypadSize, nkode_policy: NKodePolicy) -> UUID:
new_customer = Customer( new_customer = Customer(
customer_id=uuid4(), customer_id=uuid4(),
attributes=CustomerAttributes.create(keypad_size), customer_cipher=CustomerCipher.create(keypad_size),
users={}, users={},
nkode_policy=nkode_policy nkode_policy=nkode_policy
) )
@@ -30,7 +30,7 @@ class NKodeAPI:
if customer_id not in self.customers.keys(): if customer_id not in self.customers.keys():
raise ValueError(f"Customer with ID '{customer_id}' does not exist") raise ValueError(f"Customer with ID '{customer_id}' does not exist")
customer = self.customers[customer_id] customer = self.customers[customer_id]
login_keypad = UserKeypad.create(customer.attributes.keypad_size) login_keypad = UserKeypad.create(customer.customer_cipher.keypad_size)
set_keypad = login_keypad.sign_up_keypad() set_keypad = login_keypad.sign_up_keypad()
new_session = UserSignupSession( new_session = UserSignupSession(
session_id=uuid4(), session_id=uuid4(),
@@ -75,12 +75,12 @@ class NKodeAPI:
raise AssertionError(f"Username mismatch: {username} vs {session.username}") raise AssertionError(f"Username mismatch: {username} vs {session.username}")
customer = self.customers[customer_id] customer = self.customers[customer_id]
passcode = self.signup_sessions[session_id].deduce_passcode(confirm_key_entry) passcode = self.signup_sessions[session_id].deduce_passcode(confirm_key_entry)
new_user_keys = UserCipherKeys.create( new_user_keys = UserCipher.create(
customer.attributes.keypad_size, customer.customer_cipher.keypad_size,
customer.attributes.set_vals, customer.customer_cipher.set_key,
customer.nkode_policy.max_nkode_len customer.nkode_policy.max_nkode_len
) )
enciphered_passcode = new_user_keys.encipher_nkode(passcode, customer.attributes) enciphered_passcode = new_user_keys.encipher_nkode(passcode, customer.customer_cipher)
new_user = User( new_user = User(
username=username, username=username,
enciphered_passcode=enciphered_passcode, enciphered_passcode=enciphered_passcode,

View File

@@ -1,7 +1,7 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from src.models import EncipheredNKode from src.models import EncipheredNKode
from src.customer_attributes import CustomerAttributes from src.customer_cipher import CustomerCipher
from src.user_cipher_keys import UserCipherKeys from src.user_cipher_keys import UserCipher
from src.user_keypad import UserKeypad from src.user_keypad import UserKeypad
from src.utils import xor_lists from src.utils import xor_lists
@@ -10,7 +10,7 @@ from src.utils import xor_lists
class User: class User:
username: str username: str
enciphered_passcode: EncipheredNKode enciphered_passcode: EncipheredNKode
user_keys: UserCipherKeys user_keys: UserCipher
user_keypad: UserKeypad user_keypad: UserKeypad
renew: bool = field(default=False) renew: bool = field(default=False)
@@ -19,10 +19,10 @@ class User:
self.user_keys.set_key = xor_lists(self.user_keys.set_key, sets_xor) self.user_keys.set_key = xor_lists(self.user_keys.set_key, sets_xor)
self.user_keys.alpha_key = xor_lists(self.user_keys.alpha_key, attrs_xor) self.user_keys.alpha_key = xor_lists(self.user_keys.alpha_key, attrs_xor)
def refresh_passcode(self, passcode_attr_idx: list[int], customer_attributes: CustomerAttributes): def refresh_passcode(self, passcode_attr_idx: list[int], customer_attributes: CustomerCipher):
self.user_keys = UserCipherKeys.create( self.user_keys = UserCipher.create(
customer_attributes.keypad_size, customer_attributes.keypad_size,
customer_attributes.set_vals, customer_attributes.set_key,
self.user_keys.max_nkode_len self.user_keys.max_nkode_len
) )
self.enciphered_passcode = self.user_keys.encipher_nkode(passcode_attr_idx, customer_attributes) self.enciphered_passcode = self.user_keys.encipher_nkode(passcode_attr_idx, customer_attributes)

View File

@@ -4,11 +4,11 @@ from dataclasses import dataclass
import bcrypt import bcrypt
from secrets import choice from secrets import choice
from src.models import EncipheredNKode, KeypadSize from src.models import EncipheredNKode, KeypadSize
from src.customer_attributes import CustomerAttributes from src.customer_cipher import CustomerCipher
from src.utils import generate_random_nonrepeating_list, xor_lists, int_array_to_bytes from src.utils import generate_random_nonrepeating_list, xor_lists, int_array_to_bytes
@dataclass @dataclass
class UserCipherKeys: class UserCipher:
alpha_key: list[int] alpha_key: list[int]
set_key: list[int] set_key: list[int]
pass_key: list[int] pass_key: list[int]
@@ -17,15 +17,15 @@ class UserCipherKeys:
max_nkode_len: int max_nkode_len: int
@classmethod @classmethod
def create(cls, keypad_size: KeypadSize, set_values: list[int], max_nkode_len: int) -> 'UserCipherKeys': def create(cls, keypad_size: KeypadSize, set_values: list[int], max_nkode_len: int) -> 'UserCipher':
if len(set_values) != keypad_size.attrs_per_key: if len(set_values) != keypad_size.props_per_key:
raise ValueError("Invalid set values") raise ValueError("Invalid set values")
set_key = generate_random_nonrepeating_list(keypad_size.attrs_per_key) set_key = generate_random_nonrepeating_list(keypad_size.props_per_key)
set_key = xor_lists(set_key, set_values) set_key = xor_lists(set_key, set_values)
return UserCipherKeys( return UserCipher(
alpha_key=generate_random_nonrepeating_list(keypad_size.attrs_per_key * keypad_size.numb_of_keys), alpha_key=generate_random_nonrepeating_list(keypad_size.props_per_key * keypad_size.numb_of_keys),
pass_key=generate_random_nonrepeating_list(max_nkode_len), pass_key=generate_random_nonrepeating_list(max_nkode_len),
mask_key=generate_random_nonrepeating_list(max_nkode_len), mask_key=generate_random_nonrepeating_list(max_nkode_len),
set_key=set_key, set_key=set_key,
@@ -64,11 +64,11 @@ class UserCipherKeys:
def encipher_nkode( def encipher_nkode(
self, self,
passcode_attr_idx: list[int], passcode_attr_idx: list[int],
customer_attributes: CustomerAttributes customer_attributes: CustomerCipher
) -> EncipheredNKode: ) -> EncipheredNKode:
passcode_attrs = [customer_attributes.attr_vals[idx] for idx in passcode_attr_idx] passcode_attrs = [customer_attributes.prop_key[idx] for idx in passcode_attr_idx]
passcode_sets = [customer_attributes.get_attr_set_val(attr) for attr in passcode_attrs] passcode_sets = [customer_attributes.get_prop_set_val(attr) for attr in passcode_attrs]
mask = self.encipher_mask(passcode_sets, customer_attributes) mask = self.encipher_mask(passcode_sets, customer_attributes)
code = self.encipher_salt_hash_code(passcode_attr_idx, customer_attributes) code = self.encipher_salt_hash_code(passcode_attr_idx, customer_attributes)
return EncipheredNKode( return EncipheredNKode(
@@ -79,10 +79,10 @@ class UserCipherKeys:
def encipher_salt_hash_code( def encipher_salt_hash_code(
self, self,
passcode_attr_idx: list[int], passcode_attr_idx: list[int],
customer_attributes: CustomerAttributes, customer_attributes: CustomerCipher,
) -> str: ) -> str:
passcode_len = len(passcode_attr_idx) passcode_len = len(passcode_attr_idx)
passcode_attrs = [customer_attributes.attr_vals[idx] for idx in passcode_attr_idx] passcode_attrs = [customer_attributes.prop_key[idx] for idx in passcode_attr_idx]
passcode_cipher = self.pass_key.copy() passcode_cipher = self.pass_key.copy()
@@ -96,9 +96,9 @@ class UserCipherKeys:
def encipher_mask( def encipher_mask(
self, self,
passcode_sets: list[int], passcode_sets: list[int],
customer_attributes: CustomerAttributes customer_attributes: CustomerCipher
) -> str: ) -> str:
padded_passcode_sets = self.pad_user_mask(passcode_sets, customer_attributes.set_vals) padded_passcode_sets = self.pad_user_mask(passcode_sets, customer_attributes.set_key)
set_idx = [customer_attributes.get_set_index(set_val) for set_val in padded_passcode_sets] set_idx = [customer_attributes.get_set_index(set_val) for set_val in padded_passcode_sets]
mask_set_keys = [self.set_key[idx] for idx in set_idx] mask_set_keys = [self.set_key[idx] for idx in set_idx]
ciphered_mask = xor_lists(mask_set_keys, padded_passcode_sets) ciphered_mask = xor_lists(mask_set_keys, padded_passcode_sets)

View File

@@ -11,7 +11,7 @@ class UserKeypad:
@classmethod @classmethod
def create(cls, keypad_size: KeypadSize) -> 'UserKeypad': def create(cls, keypad_size: KeypadSize) -> 'UserKeypad':
keypad = UserKeypad( keypad = UserKeypad(
keypad=list(range(keypad_size.numb_of_attrs)), keypad=list(range(keypad_size.numb_of_props)),
keypad_size=keypad_size keypad_size=keypad_size
) )
keypad.random_keypad_shuffle() keypad.random_keypad_shuffle()
@@ -30,12 +30,12 @@ class UserKeypad:
keypad=matrix_to_list(keypad_matrix), keypad=matrix_to_list(keypad_matrix),
keypad_size=KeypadSize( keypad_size=KeypadSize(
numb_of_keys=self.keypad_size.numb_of_keys, numb_of_keys=self.keypad_size.numb_of_keys,
attrs_per_key=self.keypad_size.numb_of_keys props_per_key=self.keypad_size.numb_of_keys
) )
) )
def keypad_matrix(self) -> list[list[int]]: def keypad_matrix(self) -> list[list[int]]:
return list_to_matrix(self.keypad, self.keypad_size.attrs_per_key) return list_to_matrix(self.keypad, self.keypad_size.props_per_key)
def random_keypad_shuffle(self): def random_keypad_shuffle(self):
keypad_view = self.keypad_matrix() keypad_view = self.keypad_matrix()
@@ -48,11 +48,11 @@ class UserKeypad:
def disperse_keypad(self): def disperse_keypad(self):
if not self.keypad_size.is_dispersable: if not self.keypad_size.is_dispersable:
raise ValueError("Keypad size is not dispersable") raise ValueError("Keypad size is not dispersable")
user_keypad_matrix = list_to_matrix(self.keypad, self.keypad_size.attrs_per_key) user_keypad_matrix = list_to_matrix(self.keypad, self.keypad_size.props_per_key)
shuffled_keys = secure_fisher_yates_shuffle(user_keypad_matrix) shuffled_keys = secure_fisher_yates_shuffle(user_keypad_matrix)
attr_rotation = secure_fisher_yates_shuffle(list(range(self.keypad_size.numb_of_keys)))[ attr_rotation = secure_fisher_yates_shuffle(list(range(self.keypad_size.numb_of_keys)))[
:self.keypad_size.attrs_per_key] :self.keypad_size.props_per_key]
dispersed_keypad = self.random_attribute_rotation( dispersed_keypad = self.random_attribute_rotation(
shuffled_keys, shuffled_keys,
attr_rotation, attr_rotation,
@@ -61,10 +61,10 @@ class UserKeypad:
def partial_keypad_shuffle(self): def partial_keypad_shuffle(self):
# TODO: this should be split shuffle # TODO: this should be split shuffle
numb_of_selected_sets = self.keypad_size.attrs_per_key // 2 numb_of_selected_sets = self.keypad_size.props_per_key // 2
# randomly shuffle half the sets. if attrs_per_key is odd, randomly add one 50% of the time # randomly shuffle half the sets. if props_per_key is odd, randomly add one 50% of the time
numb_of_selected_sets += choice([0, 1]) if (self.keypad_size.attrs_per_key & 1) == 1 else 0 numb_of_selected_sets += choice([0, 1]) if (self.keypad_size.props_per_key & 1) == 1 else 0
selected_sets = secure_fisher_yates_shuffle(list(range(self.keypad_size.attrs_per_key)))[:numb_of_selected_sets] selected_sets = secure_fisher_yates_shuffle(list(range(self.keypad_size.props_per_key)))[:numb_of_selected_sets]
user_keypad_matrix = self.keypad_matrix() user_keypad_matrix = self.keypad_matrix()
shuffled_keys = secure_fisher_yates_shuffle(user_keypad_matrix) shuffled_keys = secure_fisher_yates_shuffle(user_keypad_matrix)
keypad_by_sets = [] keypad_by_sets = []
@@ -100,7 +100,7 @@ class UserKeypad:
def get_attr_idx_by_keynumb_setidx(self, key_numb: int, set_idx: int) -> int: def get_attr_idx_by_keynumb_setidx(self, key_numb: int, set_idx: int) -> int:
if not (0 <= key_numb < self.keypad_size.numb_of_keys): if not (0 <= key_numb < self.keypad_size.numb_of_keys):
raise ValueError(f"key_numb must be between 0 and {self.keypad_size.numb_of_keys - 1}") raise ValueError(f"key_numb must be between 0 and {self.keypad_size.numb_of_keys - 1}")
if not (0 <= set_idx < self.keypad_size.attrs_per_key): if not (0 <= set_idx < self.keypad_size.props_per_key):
raise ValueError(f"set_idx must be between 0 and {self.keypad_size.attrs_per_key - 1}") raise ValueError(f"set_idx must be between 0 and {self.keypad_size.props_per_key - 1}")
keypad_attr_idx = self.keypad_matrix() keypad_attr_idx = self.keypad_matrix()
return keypad_attr_idx[key_numb][set_idx] return keypad_attr_idx[key_numb][set_idx]

View File

@@ -19,7 +19,7 @@ class UserSignupSession(BaseModel):
def deduce_passcode(self, confirm_key_entry: list[int]) -> list[int]: def deduce_passcode(self, confirm_key_entry: list[int]) -> list[int]:
if not all(0 <= key <= self.keypad_size.numb_of_keys for key in confirm_key_entry): if not all(0 <= key <= self.keypad_size.numb_of_keys for key in confirm_key_entry):
raise ValueError("Key values must be within valid range") raise ValueError("Key values must be within valid range")
attrs_per_key = self.keypad_size.attrs_per_key attrs_per_key = self.keypad_size.props_per_key
set_key_entry = self.set_key_entry set_key_entry = self.set_key_entry
if len(set_key_entry) != len(confirm_key_entry): if len(set_key_entry) != len(confirm_key_entry):
raise ValueError("Key entry lengths must match") raise ValueError("Key entry lengths must match")

View File

@@ -9,8 +9,8 @@ def nkode_api() -> NKodeAPI:
@pytest.mark.parametrize("keypad_size,passocode_len", [ @pytest.mark.parametrize("keypad_size,passocode_len", [
(KeypadSize(numb_of_keys=10, attrs_per_key=11), 4), (KeypadSize(numb_of_keys=10, props_per_key=11), 4),
(KeypadSize(numb_of_keys=10, attrs_per_key=12), 5), (KeypadSize(numb_of_keys=10, props_per_key=12), 5),
]) ])
def test_create_new_user_and_renew_keys(nkode_api, keypad_size, passocode_len): def test_create_new_user_and_renew_keys(nkode_api, keypad_size, passocode_len):
username = "test_username" username = "test_username"
@@ -32,7 +32,7 @@ def test_create_new_user_and_renew_keys(nkode_api, keypad_size, passocode_len):
) )
assert successful_confirm assert successful_confirm
sign_in_key_selection = lambda keypad: [keypad.index(attr) // keypad_size.attrs_per_key for attr in user_passcode] sign_in_key_selection = lambda keypad: [keypad.index(attr) // keypad_size.props_per_key for attr in user_passcode]
login_keypad = nkode_api.get_login_keypad(username, customer_id) login_keypad = nkode_api.get_login_keypad(username, customer_id)
login_key_selection = sign_in_key_selection(login_keypad) login_key_selection = sign_in_key_selection(login_keypad)
successful_login = nkode_api.login(customer_id, username, login_key_selection) successful_login = nkode_api.login(customer_id, username, login_key_selection)

View File

@@ -5,11 +5,11 @@ from src.models import KeypadSize
@pytest.mark.parametrize( @pytest.mark.parametrize(
"keypad_size", "keypad_size",
[KeypadSize(numb_of_keys=10, attrs_per_key=11)] [KeypadSize(numb_of_keys=10, props_per_key=11)]
) )
def test_attr_set_idx(keypad_size): def test_attr_set_idx(keypad_size):
user_keypad = UserKeypad.create(keypad_size) user_keypad = UserKeypad.create(keypad_size)
for attr_idx in range(keypad_size.numb_of_attrs): for attr_idx in range(keypad_size.numb_of_props):
user_keypad_idx = user_keypad.keypad[attr_idx] user_keypad_idx = user_keypad.keypad[attr_idx]
assert (attr_idx % keypad_size.attrs_per_key == user_keypad_idx % keypad_size.attrs_per_key) assert (attr_idx % keypad_size.props_per_key == user_keypad_idx % keypad_size.props_per_key)

View File

@@ -1,7 +1,7 @@
import pytest import pytest
from src.models import KeypadSize from src.models import KeypadSize
from src.user_cipher_keys import UserCipherKeys, CustomerAttributes from src.user_cipher_keys import UserCipher, CustomerCipher
from src.utils import generate_random_nonrepeating_list from src.utils import generate_random_nonrepeating_list
@@ -13,8 +13,8 @@ from src.utils import generate_random_nonrepeating_list
) )
def test_encode_decode_base64(passcode_len): def test_encode_decode_base64(passcode_len):
data = generate_random_nonrepeating_list(passcode_len) data = generate_random_nonrepeating_list(passcode_len)
encoded = UserCipherKeys.encode_base64_str(data) encoded = UserCipher.encode_base64_str(data)
decoded = UserCipherKeys.decode_base64_str(encoded) decoded = UserCipher.decode_base64_str(encoded)
assert (len(data) == len(decoded)) assert (len(data) == len(decoded))
assert (all(data[idx] == decoded[idx] for idx in range(passcode_len))) assert (all(data[idx] == decoded[idx] for idx in range(passcode_len)))
@@ -22,21 +22,21 @@ def test_encode_decode_base64(passcode_len):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"keypad_size,max_nkode_len", "keypad_size,max_nkode_len",
[ [
(KeypadSize(numb_of_keys=10, attrs_per_key=11), 10), (KeypadSize(numb_of_keys=10, props_per_key=11), 10),
(KeypadSize(numb_of_keys=9, attrs_per_key=11), 10), (KeypadSize(numb_of_keys=9, props_per_key=11), 10),
(KeypadSize(numb_of_keys=8, attrs_per_key=11), 12), (KeypadSize(numb_of_keys=8, props_per_key=11), 12),
]) ])
def test_decode_mask(keypad_size, max_nkode_len): def test_decode_mask(keypad_size, max_nkode_len):
customer = CustomerAttributes.create(keypad_size) customer = CustomerCipher.create(keypad_size)
passcode_entry = generate_random_nonrepeating_list( passcode_entry = generate_random_nonrepeating_list(
keypad_size.numb_of_attrs, keypad_size.numb_of_props,
max_val=keypad_size.numb_of_attrs)[:4] max_val=keypad_size.numb_of_props)[:4]
passcode_values = [customer.attr_vals[idx] for idx in passcode_entry] passcode_values = [customer.prop_key[idx] for idx in passcode_entry]
set_vals = customer.set_vals set_vals = customer.set_key
user_keys = UserCipherKeys.create(keypad_size, set_vals, max_nkode_len) user_keys = UserCipher.create(keypad_size, set_vals, max_nkode_len)
passcode = user_keys.encipher_nkode(passcode_entry, customer) passcode = user_keys.encipher_nkode(passcode_entry, customer)
orig_passcode_set_vals = [customer.get_attr_set_val(attr) for attr in passcode_values] orig_passcode_set_vals = [customer.get_prop_set_val(attr) for attr in passcode_values]
passcode_set_vals = user_keys.decipher_mask(passcode.mask, set_vals, len(passcode_entry)) passcode_set_vals = user_keys.decipher_mask(passcode.mask, set_vals, len(passcode_entry))
assert (len(passcode_set_vals) == len(orig_passcode_set_vals)) assert (len(passcode_set_vals) == len(orig_passcode_set_vals))
assert (all(orig_passcode_set_vals[idx] == passcode_set_vals[idx] for idx in range(len(passcode_set_vals)))) assert (all(orig_passcode_set_vals[idx] == passcode_set_vals[idx] for idx in range(len(passcode_set_vals))))

View File

@@ -5,7 +5,7 @@ from src.models import KeypadSize
@pytest.fixture() @pytest.fixture()
def user_keypad(): def user_keypad():
return UserKeypad.create(keypad_size=KeypadSize(attrs_per_key=7, numb_of_keys=10)) return UserKeypad.create(keypad_size=KeypadSize(props_per_key=7, numb_of_keys=10))
def test_dispersion(user_keypad): def test_dispersion(user_keypad):
@@ -22,7 +22,7 @@ def test_shuffle_attrs(user_keypad):
expected statistical outcomes like: expected statistical outcomes like:
- every attribute gets to every key with a uniform distribution - every attribute gets to every key with a uniform distribution
- every attribute is adjacent to every other attribute with uniform distribution - every attribute is adjacent to every other attribute with uniform distribution
- the order in which the attributes move from key to key is random (i.e. the distance traveled is uniform) - the order in which the customer_cipher move from key to key is random (i.e. the distance traveled is uniform)
""" """
pre_shuffle_keypad = user_keypad.keypad pre_shuffle_keypad = user_keypad.keypad
user_keypad.partial_keypad_shuffle() user_keypad.partial_keypad_shuffle()