From 5031062e87dac6daffe2d3aba9d7ae54444f68e0 Mon Sep 17 00:00:00 2001 From: Donovan Date: Fri, 21 Mar 2025 06:32:45 -0500 Subject: [PATCH] delete docs --- docs/__init__.py | 0 docs/readme_template.md | 422 ---------------------------------------- docs/render_readme.py | 204 ------------------- 3 files changed, 626 deletions(-) delete mode 100644 docs/__init__.py delete mode 100644 docs/readme_template.md delete mode 100644 docs/render_readme.py diff --git a/docs/__init__.py b/docs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/docs/readme_template.md b/docs/readme_template.md deleted file mode 100644 index 820f295..0000000 --- a/docs/readme_template.md +++ /dev/null @@ -1,422 +0,0 @@ -# README -Play around with the code in /notebooks - -## Customer Creation -Before creating a user, a customer generates random properties and set -values. The customers manage users. They define an nKode policy, keypad's dimensions, -properties/sets in the keypad, and the frequency of property renewal. -### nKode Policy and Keypad Size -An nKode policy defines: - - -The keypad size defines: - - -To be [dispersion](nkode_concepts.md/#dispersion-resistant-interface) resistant, the number of properties must be greater than the number of keys. - -``` -api = NKodeAPI() - -policy = NKodePolicy( - max_nkode_len=10, - min_nkode_len=4, - distinct_sets=0, - distinct_properties=4, - byte_len=2 -) - -keypad_size = KeypadSize( - numb_of_keys = {{ keypad_size.numb_of_keys }}, - props_per_key = {{ keypad_size.props_per_key }} # aka number of sets -) - -customer_id = api.create_new_customer(keypad_size, policy) -customer = api.customers[customer_id] -``` -### Customer properties and Sets -A customer has users and defines the properties and set values for all its users. -Since our customer has {{ keypad_size.numb_of_keys }} keys and {{ keypad_size.props_per_key }} properties per key, -this gives a customer interface of {{ keypad_size.numb_of_props }} distinct properties and {{ keypad_size.props_per_key }} distinct property sets. -Each property belongs to one of the {{ keypad_size.props_per_key }} sets. Each property and set value is a unique 2-byte integer in this example. - -``` -set_vals = customer.cipher.set_key - -Customer Sets: {{ customer_set_vals }} -``` - -``` -prop_vals = customer.cipher.prop_key -keypad_view(prop_vals, keypad_size.props_per_key) - -Customer properties: -{% for props in customer_prop_view -%} -{{ props }} -{% endfor %} -``` - -properties organized by set: -``` -prop_set_view = matrix_transpose(prop_keypad_view) -set_property_dict = dict(zip(set_vals, prop_set_view)) - -Set to property Map: -{% for set_val, props in set_property_dict.items() -%} -{{ set_val }} : {{ props }} -{% endfor %} -``` - -## User Signup -Now that we have a customer, we can create users. To create a new user: - -1. Generate a random interface -2. The user sets their nKode and sends their selection to the server -3. The user confirms their nKode. If the user's nKode matches the policy, the server creates the user. -### Random Interface Generation -The user's interface must be dispersable so the server can determine the user's nkode. -The server randomly drops property sets until -the number of properties equals the number of keys, making the interface dispersable. -In our case, the server randomly drops {{ keypad_size.props_per_key - keypad_size.numb_of_keys }} property {{ "sets" if keypad_size.props_per_key - keypad_size.numb_of_keys > 1 else "set" }}. -to give us a {{ keypad_size.numb_of_keys }} X {{ keypad_size.numb_of_keys }} keypad with possible index values ranging from 0-{{ keypad_size.numb_of_props - 1 }}. -Each value in the interface is the index value of a customer property. -The user never learns what their "real" property is. They do not see the index value representing their nKode or -the customer server-side value. - -``` -session_id, signup_interface = api.generate_index_interface(customer_id) -signup_interface_keypad = list_to_matrix(signup_interface, keypad_size.props_per_key) - -Signup Keypad: -{% for key in signup_keypad -%} -Key {{ loop.index }}: {{ key }} -{% endfor %} -``` - -### Set nKode -The user identifies properties in the interface they want in their nkode. Each property has an index value. -Below, the user has selected `{{ user_passcode }}`. These index values can be represented by anything in the GUI. -The only requirement is that the GUI properties be associated with the same index every time the user logs in. -If users want to change anything about their interface, they must also change their nkode. - -``` -username = {{ username }} -user_passcode = {{ user_passcode }} -selected_keys_set = select_keys_with_passcode_values(user_passcode, signup_interface, keypad_size.props_per_key) - -Selected Keys -{{ selected_keys_set }} -``` - -The user's passcode server side properties are: -``` -server_side_prop = [customer.cipher.prop_key[idx] for idx in user_passcode] - -User Passcode Server-side properties: {{ server_side_prop }} -``` - -### Confirm nKode -The user submits the set interface to the server and receives the _confirm interface_ as a response. -The user finds their nKode again. - -``` -confirm_interface = api.set_nkode(username, customer_id, selected_keys_set, session_id) -keypad_view(confirm_interface, keypad_size.numb_of_keys) -selected_keys_confirm = select_keys_with_passcode_values(user_passcode, confirm_interface, keypad_size.numb_of_keys) - -Confirm Keypad: -{% for key in confirm_keypad -%} -Key {{ loop.index }}: {{ key }} -{% endfor %} -Selected Keys: -{{ selected_keys_confirm }} -``` - -The user submits their confirmation key selection and the user is created -``` -success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id) -``` - -### Passcode Enciphering, Hashing, and Salting -When a new user creates an nKode, the server caches its set and confirms the interface and the user's key selection. -On the last api.confirm_nkode, the server: - -1. Deduces the user's properties -2. Validates the Passcode against the nKodePolicy -3. Creates new User Cipher Keys -4. Enciphers the user's mask -5. Enciphers, salts, and hashes the user's passcode - -Steps 1-2 are straightforward. For a better idea of how they work, see pyNKode. - -#### User Cipher Keys - -##### User Cipher Keys Data Structure -``` -set_key = generate_random_nonrepeating_list(keypad_size.props_per_key, max_numb=2**(8*numb_of_bytes)) -set_key = xor_lists(set_key, customer_prop.set_vals) - -UserCipherKeys( - prop_key=generate_random_nonrepeating_list(keypad_size.props_per_key * keypad_size.numb_of_keys, max_numb=2**(8*numb_of_bytes)), - pass_key=generate_random_nonrepeating_list(max_nkode_len, max_numb=2**(8*numb_of_bytes)), - mask_key=generate_random_nonrepeating_list(max_nkode_len, max_numb=2**(8*numb_of_bytes)), - set_key=set_key, - salt=bcrypt.gensalt(), - max_nkode_len=max_nkode_len -) -``` - -##### User Cipher Keys Values -``` -user_cipher = UserCipherKeys( - prop_key = {{ user_cipher.prop_key }}, - pass_key = {{ user_cipher.pass_key }}, - mask_key = {{ user_cipher.mask_key }}, - set_key = {{ user_cipher.set_key }}, - salt = {{ user_cipher.salt }}, - max_nkode_len = {{ user_cipher.max_nkode_len }} -) -``` - -The method UserCipherKeys.encipher_nkode secures a user's nKode in the database. This method is called in api.confirm_nkode - -``` -class EncipheredNKode(BaseModel): - code: str - mask: str -``` - -#### Mask Enciphering - -Recall: - -- set_key_i = (set_rand_numb_i ^ set_val_i) -- mask_key_i = mask_rand_numb_i -- padded_passcode_server_set_i = set_val_i -- len(set_key) == len(mask_key) == (padded_passcode_server_set) == max_nkode_len == 10 - where i is the index - -- mask_i = mask_key_i ^ padded_passcode_server_set_i ^ set_key_i -- mask_i = mask_rand_num_i ^ set_val_i ^ set_rand_numb_i ^ set_val_i -- mask_i = mask_rand_num_i ^ set_rand_numb_i # set_val_i is cancelled out - - -``` -passcode = {{ user_passcode }} -passcode_server_prop = [customer.cipher.prop_key[idx] for idx in passcode] -passcode_server_set = [customer.cipher.get_prop_set_val(prop) for prop in passcode_server_prop] - -Passcode Set Vals: {{ passcode_server_prop }} -Passcode prop Vals: {{ passcode_server_set }} -``` - -``` -padded_passcode_server_set = user_cipher.pad_user_mask(passcode_server_set, customer.nkode_policy.max_nkode_len) - -set_idx = [customer.cipher.get_set_index(set_val) for set_val in padded_passcode_server_set] -mask_set_keys = [user_cipher.set_key[idx] for idx in set_idx] - -ciphered_mask = xor_lists(mask_set_keys, padded_passcode_server_set) -ciphered_mask = xor_lists(ciphered_mask, user_cipher.mask_key) - -mask = user_cipher.encode_base64_str(ciphered_mask) -Mask: {{ enciphered_nkode.mask }} -``` - -#### Passcode Enciphering and Hashing - -- ciphered_customer_prop = prop_key ^ customer_prop -- ciphered_passcode_i = pass_key_i ^ ciphered_customer_prop_i -- code = hash(ciphered_passcode, salt) - -``` -ciphered_customer_props = xor_lists(customer.cipher.prop_key, user_cipher.prop_key) -passcode_ciphered_props = [ciphered_customer_props[idx] for idx in passcode] -pad_len = customer.nkode_policy.max_nkode_len - passcode_len - -passcode_ciphered_props.extend([0 for _ in range(pad_len)]) - -ciphered_code = xor_lists(passcode_ciphered_props, user_cipher.pass_key) - -passcode_bytes = int_array_to_bytes(ciphered_code) -passcode_digest = base64.b64encode(hashlib.sha256(passcode_bytes).digest()) -hashed_data = bcrypt.hashpw(passcode_digest, user_cipher.salt) -code = hashed_data.decode("utf-8") - -Code: {{ enciphered_nkode.code }} -``` - -## User Login -To login, a user: - -1. Gets login interface -2. Submits key entry - -### Get Login Interface -The client requests the user's login interface. -``` -login_interface = api.get_login_interface(username, customer_id) -keypad_view(login_interface, keypad_size.props_per_key) -``` -The server returns a randomly shuffled interface. Learn more about how the [User Interface Shuffle](nkode_concepts.md/#user-interface-shuffle) works -``` -Login Interface Keypad View: -{% for key in login_keypad -%} -Key {{ loop.index }}: {{ key }} -{% endfor %} -``` -Recall the user's passcode is `user_passcode = {{ user_passcode }}` so the user selects keys ` selected_keys_login = {{ selected_login_keys }}` - -``` -success = api.login(customer_id, username, selected_keys_login) -``` - -### Validate Login Key Entry -- decipher user mask and recover nkode set values -- get presumed property from key selection and set values -- encipher, salt, and hash presumed property values and compare them to the users hashed code - -#### Decipher Mask - -Recall: - -- set_key_i = (set_key_rand_numb_i ^ set_val_i) -- mask_i = mask_key_rand_num_i ^ set_key_rand_numb_i - -Recover nKode set values: - -- decode mask from base64 to int -- deciphered_mask = mask ^ mask_key -- deciphered_mask_i = set_key_rand_numb # mask_key_rand_num_i is cancelled out -- set_key_rand_component = set_key ^ set_values -- deduce the set value - -``` -user = customer.users[username] -user_cipher = user.user_cipher -user_mask = user.enciphered_passcode.mask -decoded_mask = user_cipher.decode_base64_str(user_mask) -deciphered_mask = xor_lists(decoded_mask, user_cipher.mask_key) -set_key_rand_component = xor_lists(set_vals, user_cipher.set_key) -passcode_sets = [] -for set_cipher in deciphered_mask[:passcode_len]: - set_idx = set_key_rand_component.index(set_cipher) - passcode_sets.append(set_vals[set_idx]) - -Passcode Sets: {{ login_passcode_sets }} -``` - - -### Get Presumed properties -``` -set_vals_idx = [customer.cipher.get_set_index(set_val) for set_val in passcode_sets] - -presumed_selected_properties_idx = [] -for idx in range(passcode_len): - key_numb = selected_keys_login[idx] - set_idx = set_vals_idx[idx] - selected_prop_idx = customer.users[username].user_interface.get_prop_idx_by_keynumb_setidx(key_numb, set_idx) - presumed_selected_properties_idx.append(selected_prop_idx) - -Presumped Passcode: {{ presumed_selected_properties_idx }} -Recall User Passcode: {{ user_passcode }} -``` -### Compare Enciphered Passcodes -``` -enciphered_nkode = user_cipher.encipher_salt_hash_code(presumed_selected_properties_idx, customer.cipher) -``` -If `enciphered_nkode == user.enciphered_passcode.code`, the user's key selection is valid, and the login is successful. - -## Renew properties -properties renew is invoked with the renew_properties method: `api.renew_properties(customer_id)` -The renew properties process has three steps: -1. Renew Customer properties -2. Renew User Keys -3. Refresh User on Login - -When the customer calls the `renew_properties` method, the method replaces the customer's properties and set values. All its users go through an intermediate -renewal step. The users fully renew after their first successful login. This first login refreshes their keys, salt, and hash with new values. - - -### Customer Renew -Old Customer properties and set values are cached and copied to variables before renewal. -``` -old_sets = customer.cipher.set_key - -Customer Sets: {{ customer_set_vals }} -``` - -``` -old_prop = customer.cipher.prop_key - -Customer properties: -{% for props in customer_prop_view -%} -{{ props }} -{% endfor %} -``` - -After the renewal, the customer properties and sets are new randomly generated values. -``` -api.renew_properties(customer_id) - -set_vals = customer.cipher.set_key - -Customer Sets: {{ customer_new_set_vals }} -``` - -``` -prop_vals = customer.cipher.prop_key - -Customer properties: -{% for props in customer_new_prop_view -%} -{{ props }} -{% endfor %} -``` - -### Renew User -During the renewal, each user goes through a temporary transition period. -``` -props_xor = xor_lists(new_props, old_props) -sets_xor = xor_lists(new_sets, old_sets) -for user in customer.users.values(): - user.renew = True - user.user_cipher.set_key = xor_lists(user.user_cipher.set_key, sets_xor) - user.user_cipher.prop_key = xor_lists(user.user_cipher.prop_key, props_xor) -``` -##### User prop Key -The user's prop key was a randomly generated list of length `numb_of_keys * prop_per_key`. -Now each value in the prop key is `prop_key_i = old_prop_key_i ^ new_prop_i ^ old_prop_i`. -Recall in the login process, `ciphered_customer_props = prop_key ^ customer_prop`. -Since the customer_prop is now the new value, it gets canceled out, leaving: -``` -new_prop_key = old_prop_key ^ old_prop ^ new_prop -ciphered_customer_props = new_prop_key ^ new_prop -ciphered_customer_props = old_prop_key ^ old_prop # since new_prop cancel out -``` -Using the new customer properties, we can validate the user's login attempt with the same hash. - -##### User Set Key -The user's set key was a randomly generated list of length `prop_per_key` xor `customer_set_vals`. -The `old_set_vals` have been replaced with the new `new_set_vals`. The deciphering process described above -remains the same. - -### User Refresh -Once the user has a successful login, they get a new salt and cipher keys, and their `enciphered_passcode` is recomputed -with the new values. -``` -user.user_cipher = UserCipherKeys.new( - customer.cipher.keypad_size, - customer.cipher.set_key, - user.user_cipher.max_nkode_len -) -user.enciphered_passcode = user.user_cipher.encipher_nkode(presumed_selected_properties_idx, customer.cipher) -user.renew = False -``` diff --git a/docs/render_readme.py b/docs/render_readme.py deleted file mode 100644 index 92238e0..0000000 --- a/docs/render_readme.py +++ /dev/null @@ -1,204 +0,0 @@ -import numpy as np -from jinja2 import Environment, FileSystemLoader -import os -from src.nkode_api import NKodeAPI -from src.models import NKodePolicy, KeypadSize, EncipheredNKode -from src.user_cipher import UserCipher -from secrets import choice -from string import ascii_lowercase -import bcrypt -import hashlib -import base64 - - -def random_username() -> str: - return "test_username" + "".join([choice(ascii_lowercase) for _ in range(6)]) - - -def select_keys_with_passcode_values(user_passcode: list[int], keypad: np.ndarray, props_per_key: int) -> list[int]: - indices = [np.where(keypad == prop)[0][0] for prop in user_passcode] - return [int(index // props_per_key) for index in indices] - - -def visualize_keypad(keypad_list: np.ndarray, props_per_key: int): - print("Keypad View") - keypad_mat = keypad_list.reshape(-1, props_per_key) - for idx, key_vals in enumerate(keypad_mat): - print(f"Key {idx}: {key_vals}") - - -def render_nkode_authentication(data: dict): - # Set up the Jinja2 environment and template loader - file_loader = FileSystemLoader('') - env = Environment(loader=file_loader) - - # Load the template - template = env.get_template('readme_template.md') - - print(os.getcwd()) - # Render the template with the data - output = template.render(data) - - # Print or save the output - # output_file = os.path.expanduser("~/Desktop/nkode_authentication.md") - output_file = '../README.md' - with open(output_file, 'w') as fp: - fp.write(output) - print("File written successfully") - - -if __name__ == "__main__": - api = NKodeAPI() - - policy = NKodePolicy( - max_nkode_len=10, - min_nkode_len=4, - distinct_positions=0, - distinct_properties=4, - byte_len=2, - ) - keypad_size = KeypadSize( - numb_of_keys=5, - props_per_key=6 # aka number of sets - ) - customer_id = api.create_new_customer(keypad_size, policy) - customer = api.customers[customer_id] - - set_vals = customer.cipher.position_key - prop_vals = customer.cipher.property_key - customer_prop_view = prop_vals.reshape(-1, keypad_size.props_per_key) - - prop_keypad_view = prop_vals.reshape(-1, keypad_size.props_per_key) - prop_set_view = prop_keypad_view.T - set_property_dict = dict(zip(set_vals, prop_set_view)) - - session_id, signup_interface = api.generate_signup_keypad(customer_id) - signup_keypad = signup_interface.reshape(-1, keypad_size.numb_of_keys) - - username = random_username() - passcode_len = 4 - user_passcode = signup_interface[:passcode_len].tolist() - selected_keys_set = select_keys_with_passcode_values(user_passcode, signup_interface, keypad_size.numb_of_keys) - server_side_prop = [customer.cipher.property_key[idx] for idx in user_passcode] - - confirm_interface = api.set_nkode(username, customer_id, selected_keys_set, session_id) - - confirm_keypad = confirm_interface.reshape(-1, keypad_size.numb_of_keys) - - selected_keys_confirm = select_keys_with_passcode_values(user_passcode, confirm_interface, keypad_size.numb_of_keys) - - success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id) - assert success - passcode_server_prop = [customer.cipher.property_key[idx] for idx in user_passcode] - passcode_server_set = customer.cipher.get_props_position_vals(user_passcode) - user_keys = customer.users[username].cipher - # TODO: pad_user_mask is deprecated - padded_passcode_server_set = user_keys.pad_user_mask(np.array(passcode_server_set), customer.cipher.position_key) - - set_idx = [customer.cipher.get_position_index(set_val) for set_val in padded_passcode_server_set] - mask_set_keys = [user_keys.combined_position_key[idx] for idx in set_idx] - ciphered_mask = mask_set_keys ^ padded_passcode_server_set ^ user_keys.mask_key - mask = user_keys.encode_base64_str(ciphered_mask) - ciphered_customer_props = customer.cipher.property_key ^ user_keys.property_key - passcode_ciphered_props = [ciphered_customer_props[idx] for idx in user_passcode] - pad_len = customer.nkode_policy.max_nkode_len - passcode_len - passcode_ciphered_props.extend([0 for _ in range(pad_len)]) - ciphered_code = np.bitwise_xor(passcode_ciphered_props, user_keys.pass_key) - passcode_bytes = ciphered_code.tobytes() - passcode_digest = base64.b64encode(hashlib.sha256(passcode_bytes).digest()) - hashed_data = bcrypt.hashpw(passcode_digest, bcrypt.gensalt(rounds=12)) - code = hashed_data.decode("utf-8") - - enciphered_nkode = EncipheredNKode( - mask=mask, - code=code, - ) - """ - USER LOGIN - """ - login_interface = api.get_login_keypad(username, customer_id) - login_keypad = login_interface.reshape(-1, keypad_size.props_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) - assert success - - """ - VALIDATE LOGIN KEY ENTRY - DECIPHER MASK - """ - - user = customer.users[username] - set_vals = customer.cipher.position_key - user_keys = user.cipher - user_mask = user.enciphered_passcode.mask - decoded_mask = user_keys.decode_base64_str(user_mask) - deciphered_mask = np.bitwise_xor(decoded_mask, user_keys.mask_key) - set_key_rand_component = np.bitwise_xor(set_vals, user_keys.combined_position_key) - login_passcode_sets = [] - for set_cipher in deciphered_mask[:passcode_len]: - set_idx = np.where(set_key_rand_component == set_cipher)[0][0] - login_passcode_sets.append(int(set_vals[set_idx])) - - """ - GET PRESUMED properties - """ - - set_vals_idx = [customer.cipher.get_position_index(set_val) for set_val in login_passcode_sets] - presumed_selected_properties_idx = customer.users[username].user_keypad.get_prop_idxs_by_keynumb_setidx(selected_keys_login, set_vals_idx) - """ - RENEW KEYS - """ - - old_props = customer.cipher.property_key.copy() - old_sets = customer.cipher.position_key.copy() - customer.cipher.renew() - new_props = customer.cipher.property_key - new_sets = customer.cipher.position_key - customer_new_prop_view = new_props.reshape(-1, keypad_size.props_per_key) - """ - RENEW USER - """ - props_xor = np.bitwise_xor(new_props, old_props) - sets_xor = np.bitwise_xor(new_sets, old_sets) - for user in customer.users.values(): - user.renew = True - user.cipher.combined_position_key = np.bitwise_xor(user.cipher.combined_position_key, sets_xor) - user.cipher.property_key = np.bitwise_xor(user.cipher.property_key, props_xor) - - """ - REFRESH USER KEYS - """ - user.cipher = UserCipher.create( - customer.cipher.keypad_size, - customer.cipher.position_key, - user.cipher.max_nkode_len - ) - user.enciphered_passcode = user.cipher.encipher_nkode(presumed_selected_properties_idx, customer.cipher) - user.renew = False - - # Define some data to pass to the template - data = { - 'keypad_size': keypad_size, - 'customer_set_vals': set_vals, - 'customer_prop_view': customer_prop_view, - 'set_property_dict': set_property_dict, - 'set_signup_keypad': signup_keypad, - 'username': 'test_user', - 'passcode_property_indices': user_passcode, - 'selected_keys_set': selected_keys_set, - 'server_side_prop': server_side_prop, - 'confirm_keypad': confirm_keypad, - 'selected_keys_confirm': selected_keys_confirm, - 'user_cipher': user_keys, - 'ordered_customer_prop_key': passcode_server_prop, - 'ordered_customer_set_key': passcode_server_set, - 'enciphered_nkode': enciphered_nkode, - 'login_keypad': login_keypad, - 'selected_login_keys': selected_keys_login, - 'login_passcode_sets': login_passcode_sets, - 'presumed_selected_properties_idx': presumed_selected_properties_idx, - 'customer_new_prop_view': customer_new_prop_view, - 'customer_new_set_vals': new_sets, - - } - render_nkode_authentication(data) \ No newline at end of file