diff --git a/docs/render_readme.py b/docs/render_readme.py index b33f7af..1f32057 100644 --- a/docs/render_readme.py +++ b/docs/render_readme.py @@ -64,8 +64,8 @@ if __name__ == "__main__": customer_id = api.create_new_customer(keypad_size, policy) customer = api.customers[customer_id] - set_vals = customer.cipher.set_key - prop_vals = customer.cipher.prop_key + 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) @@ -79,7 +79,7 @@ if __name__ == "__main__": 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.prop_key[idx] for idx in user_passcode] + 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) @@ -89,25 +89,24 @@ if __name__ == "__main__": success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id) assert success - passcode_server_prop = [customer.cipher.prop_key[idx] for idx in user_passcode] - passcode_server_set = [customer.cipher.get_prop_set_val(prop) for prop in passcode_server_prop] - + 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 - padded_passcode_server_set = user_keys.pad_user_mask(np.array(passcode_server_set), customer.cipher.set_key) + padded_passcode_server_set = user_keys.pad_user_mask(np.array(passcode_server_set), customer.cipher.position_key) - set_idx = [customer.cipher.get_set_index(set_val) for set_val in padded_passcode_server_set] + set_idx = [customer.cipher.get_position_index(set_val) for set_val in padded_passcode_server_set] mask_set_keys = [user_keys.combined_set_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.prop_key ^ user_keys.prop_key + ciphered_customer_props = customer.cipher.property_key ^ user_keys.prop_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, user_keys.salt) + hashed_data = bcrypt.hashpw(passcode_digest, bcrypt.gensalt(rounds=12)) code = hashed_data.decode("utf-8") enciphered_nkode = EncipheredNKode( @@ -129,7 +128,7 @@ if __name__ == "__main__": """ user = customer.users[username] - set_vals = customer.cipher.set_key + 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) @@ -144,17 +143,17 @@ if __name__ == "__main__": GET PRESUMED properties """ - set_vals_idx = [customer.cipher.get_set_index(set_val) for set_val in login_passcode_sets] + 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.prop_key.copy() - old_sets = customer.cipher.set_key.copy() + old_props = customer.cipher.property_key.copy() + old_sets = customer.cipher.position_key.copy() customer.cipher.renew() - new_props = customer.cipher.prop_key - new_sets = customer.cipher.set_key + 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 @@ -171,7 +170,7 @@ if __name__ == "__main__": """ user.cipher = UserCipher.create( customer.cipher.keypad_size, - customer.cipher.set_key, + customer.cipher.position_key, user.cipher.max_nkode_len ) user.enciphered_passcode = user.cipher.encipher_nkode(presumed_selected_properties_idx, customer.cipher) @@ -183,9 +182,9 @@ if __name__ == "__main__": 'customer_set_vals': set_vals, 'customer_prop_view': customer_prop_view, 'set_property_dict': set_property_dict, - 'signup_keypad': signup_keypad, + 'set_signup_keypad': signup_keypad, 'username': 'test_user', - 'user_passcode': user_passcode, + 'user_passcode_indices': user_passcode, 'selected_keys_set': selected_keys_set, 'server_side_prop': server_side_prop, 'confirm_keypad': confirm_keypad, diff --git a/notebooks/nkode_tutorial.ipynb b/notebooks/nkode_tutorial.ipynb index c396758..55495a6 100644 --- a/notebooks/nkode_tutorial.ipynb +++ b/notebooks/nkode_tutorial.ipynb @@ -8,32 +8,21 @@ "from secrets import choice\n", "from string import ascii_lowercase\n", "import numpy as np\n", - "import bcrypt" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2025-03-17T14:56:54.677924Z", - "start_time": "2025-03-17T14:56:54.674055Z" - } - }, - "outputs": [], - "execution_count": 52 - }, - { - "cell_type": "code", - "source": [ + "import bcrypt\n", + "import hashlib\n", + "import base64\n", + "from IPython.display import Markdown, display\n", + "\n", "def random_username() -> str:\n", " return \"test_username\" + \"\".join([choice(ascii_lowercase) for _ in range(6)])\n", "\n", "\n", - "def select_keys_with_passcode_values(user_passcode: list[int], keypad: np.ndarray, props_per_key: int) -> list[int]:\n", - " indices = [np.where(keypad == prop)[0][0] for prop in user_passcode]\n", + "def select_keys_with_passcode_values(user_passcode_idxs: list[int], keypad: np.ndarray, props_per_key: int) -> list[int]:\n", + " indices = [np.where(keypad == prop)[0][0] for prop in user_passcode_idxs]\n", " return [int(index // props_per_key) for index in indices]\n", "\n", "\n", "def keypad_view(keypad: np.ndarray, props_per_key: int):\n", - " print(\"\\nKeypad View\")\n", " interface_keypad = keypad.reshape(-1, props_per_key)\n", " for idx, key_vals in enumerate(interface_keypad):\n", " print(f\"Key {idx}: {key_vals}\")\n" @@ -41,41 +30,55 @@ "metadata": { "collapsed": false, "ExecuteTime": { - "end_time": "2025-03-17T14:56:54.687467Z", - "start_time": "2025-03-17T14:56:54.683811Z" + "end_time": "2025-03-19T14:18:48.405476Z", + "start_time": "2025-03-19T14:18:48.400091Z" } }, "outputs": [], - "execution_count": 53 + "execution_count": 195 }, { "cell_type": "code", "source": [ - "api = NKodeAPI()" + "api = NKodeAPI()\n", + "user_icons = np.array([\n", + " \"😀\", \"😂\", \"🥳\", \"😍\", \"🤓\",\n", + " \"😎\", \"🥺\", \"😡\", \"😱\", \"🤯\",\n", + " \"🥰\", \"😴\", \"🤔\", \"🙃\", \"😇\",\n", + " \"🤖\", \"👽\", \"👾\", \"🐱\", \"🐶\",\n", + " \"🦁\", \"🐻\", \"🐸\", \"🐙\", \"🦄\",\n", + " \"🌟\", \"⚡\", \"🔥\", \"🍕\", \"🎉\"\n", + "])" ], "metadata": { "collapsed": false, "ExecuteTime": { - "end_time": "2025-03-17T14:56:54.698258Z", - "start_time": "2025-03-17T14:56:54.696478Z" + "end_time": "2025-03-19T14:18:48.416324Z", + "start_time": "2025-03-19T14:18:48.413345Z" } }, "outputs": [], - "execution_count": 54 + "execution_count": 196 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### nKode Customer\n", + "An nKode customer is business has employees (users). An nKode API can service many customers each with their own users.\n", + "Each customer specifies a keypad size and a nkode policy.\n", + "The keypad can't be dispersable (`numb_of_keys < properties_per_key`)" + ] }, { "cell_type": "markdown", "source": [ - "# NKode API\n", - "### Customer NKode Policy and Interface\n", - "A customer defines their NKode Policy and their interface. Below we've set:\n", - "- max nkode length = 10\n", - "- min nkode length = 4\n", - "- distinct properties = 4\n", - "- distinct set = 0\n", - "- byte len = 2\n", - "\n", - "This customer also has an interface with 5 keys and 6 properties per key. The number of properties must be greater than the number of keys to be dispersion resistant." + "#### Customer Cipher Keys\n", + "Each customer has unique cipher keys.\n", + "These keys are used to encipher and decipher user nKode.\n", + "There are two types of Customer Cipher Keys:\n", + "1. property key: Combined with the user property key to get the server-side representation of a users icons. Each property belongs to a set.\n", + "2. set key: Combined with the user set Key to the the server-side representation the position in each key.\n" ], "metadata": { "collapsed": false @@ -89,88 +92,19 @@ " min_nkode_len=4,\n", " distinct_sets=0,\n", " distinct_properties=4,\n", - " byte_len=2,\n", ")\n", "keypad_size = KeypadSize(\n", " numb_of_keys = 5,\n", - " props_per_key = 6 # aka number of sets\n", + " props_per_key = 6\n", ")\n", "customer_id = api.create_new_customer(keypad_size, policy)\n", - "customer = api.customers[customer_id]" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2025-03-17T14:56:54.710370Z", - "start_time": "2025-03-17T14:56:54.706283Z" - } - }, - "outputs": [], - "execution_count": 55 - }, - { - "cell_type": "markdown", - "source": [ - "### NKode Customer\n", - "A customer has users and defines the properties and set values for all its users.\n", - "Since our customer has 5 keys and 6 properties per key, this gives a customer interface of 30 distinct properties and 6 distinct properties sets.\n", - "Each properties belongs to one of the 6 sets." - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "markdown", - "source": "#### Customer and Properties Values", - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "source": [ - "print(f\"Customer Set Key: {customer.cipher.set_key}\")\n", + "customer = api.customers[customer_id]\n", + "print(f\"Customer Set Key: {customer.cipher.position_key}\")\n", "print(f\"Customer Properties Key:\")\n", - "customer_prop_keypad = customer.cipher.prop_key.reshape(-1, keypad_size.props_per_key)\n", + "customer_prop_keypad = customer.cipher.property_key.reshape(-1, keypad_size.props_per_key)\n", "for idx, key_vals in enumerate(customer_prop_keypad):\n", - " print(f\"{key_vals}\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2025-03-17T14:56:54.735642Z", - "start_time": "2025-03-17T14:56:54.732921Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Customer Set Key: [15749 12291 52872 49860 5208 28289]\n", - "Customer Properties Key:\n", - "[42849 39396 49203 20824 30786 27252]\n", - "[16470 25626 64566 34252 23582 48009]\n", - "[20454 51880 19394 58106 12995 59391]\n", - "[43703 4483 7702 5423 24081 57678]\n", - "[43822 27495 59095 24249 60164 21395]\n" - ] - } - ], - "execution_count": 56 - }, - { - "cell_type": "markdown", - "source": "#### Customer Set To Properties Map", - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "source": [ - "set_properties_dict = dict(zip(customer.cipher.set_key, customer_prop_keypad.T))\n", + " print(f\"{key_vals}\")\n", + "set_properties_dict = dict(zip(customer.cipher.position_key, customer_prop_keypad.T))\n", "print(f\"Set to Properties Map:\")\n", "for set_val, props in set_properties_dict.items():\n", " print(f\"{set_val}: {props}\")" @@ -178,8 +112,8 @@ "metadata": { "collapsed": false, "ExecuteTime": { - "end_time": "2025-03-17T14:56:54.781223Z", - "start_time": "2025-03-17T14:56:54.778381Z" + "end_time": "2025-03-19T14:18:48.432441Z", + "start_time": "2025-03-19T14:18:48.426289Z" } }, "outputs": [ @@ -187,24 +121,63 @@ "name": "stdout", "output_type": "stream", "text": [ + "Customer Set Key: [52236 1143 22391 5610 24876 51604]\n", + "Customer Properties Key:\n", + "[48274 26421 59355 34554 61533 13623]\n", + "[22492 47901 17487 10515 61939 8923]\n", + "[32708 61921 973 1449 52341 29868]\n", + "[10212 24136 41690 31747 29169 19891]\n", + "[18093 29532 18702 45116 15485 53514]\n", "Set to Properties Map:\n", - "15749: [42849 16470 20454 43703 43822]\n", - "12291: [39396 25626 51880 4483 27495]\n", - "52872: [49203 64566 19394 7702 59095]\n", - "49860: [20824 34252 58106 5423 24249]\n", - "5208: [30786 23582 12995 24081 60164]\n", - "28289: [27252 48009 59391 57678 21395]\n" + "52236: [48274 22492 32708 10212 18093]\n", + "1143: [26421 47901 61921 24136 29532]\n", + "22391: [59355 17487 973 41690 18702]\n", + "5610: [34554 10515 1449 31747 45116]\n", + "24876: [61533 61939 52341 29169 15485]\n", + "51604: [13623 8923 29868 19891 53514]\n" ] } ], - "execution_count": 57 + "execution_count": 197 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-19T14:18:48.443885Z", + "start_time": "2025-03-19T14:18:48.441137Z" + } + }, + "cell_type": "code", + "source": [ + "user_icon_keypad = user_icons.reshape(-1, keypad_size.props_per_key)\n", + "set_icons_dict = dict(zip(customer.cipher.position_key, user_icon_keypad.T))\n", + "print(\"Set to Icons Map:\")\n", + "for set_val, icons in set_icons_dict.items():\n", + " print(f\"{set_val}: {icons}\")\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Set to Icons Map:\n", + "52236: ['😀' '🥺' '🤔' '🐱' '🦄']\n", + "1143: ['😂' '😡' '🙃' '🐶' '🌟']\n", + "22391: ['🥳' '😱' '😇' '🦁' '⚡']\n", + "5610: ['😍' '🤯' '🤖' '🐻' '🔥']\n", + "24876: ['🤓' '🥰' '👽' '🐸' '🍕']\n", + "51604: ['😎' '😴' '👾' '🐙' '🎉']\n" + ] + } + ], + "execution_count": 198 }, { "metadata": {}, "cell_type": "markdown", "source": [ "### User Signup\n", - "To create a new must call this endpoints in order:\n", + "Users can create an nkode with these steps:\n", "1. Generate a randomly shuffled keypad\n", "2. Set user nKode\n", "3. Confirm user nKode\n", @@ -215,101 +188,145 @@ " In our case, the server drops 1 properties set to give us a 5 X 5 keypad with possible index values ranging from 0-29.\n", " - Run the cell below over and over to see it change. Notice that values never move out of their columns just their rows.\n", " - each value in the keypad is the index value of a customer properties\n", - " - the user never learns what their \"real\" properties is. All they do is specify an index in the customer keypad\n" + " - the user never learns what their server-side properties" ] }, { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:54.814781Z", - "start_time": "2025-03-17T14:56:54.812317Z" + "end_time": "2025-03-19T14:21:05.988739Z", + "start_time": "2025-03-19T14:21:05.981809Z" } }, "cell_type": "code", "source": [ - "session_id, signup_keypad = api.generate_signup_keypad(customer_id)\n", - "print(signup_keypad.reshape(-1, keypad_size.numb_of_keys))" + "signup_session_id, set_signup_keypad = api.generate_signup_keypad(customer_id)\n", + "display(Markdown(\"\"\"### Icon Keypad\"\"\"))\n", + "keypad_view(user_icons[set_signup_keypad], keypad_size.numb_of_keys)\n", + "display(Markdown(\"\"\"### Index Keypad\"\"\"))\n", + "keypad_view(set_signup_keypad, keypad_size.numb_of_keys)\n", + "display(Markdown(\"\"\"### Customer Properties Keypad\"\"\"))\n", + "keypad_view(customer.cipher.property_key[set_signup_keypad], keypad_size.numb_of_keys)" ], "outputs": [ + { + "data": { + "text/plain": [ + "" + ], + "text/markdown": "### Icon Keypad" + }, + "metadata": {}, + "output_type": "display_data" + }, { "name": "stdout", "output_type": "stream", "text": [ - "[[24 7 2 3 29]\n", - " [12 13 26 9 11]\n", - " [ 6 1 8 15 23]\n", - " [ 0 25 20 21 5]\n", - " [18 19 14 27 17]]\n" + "Key 0: ['🙃' '🥳' '🔥' '🐸' '👾']\n", + "Key 1: ['😡' '😱' '🤯' '🥰' '😴']\n", + "Key 2: ['🌟' '🦁' '🐻' '🍕' '😎']\n", + "Key 3: ['🐶' '⚡' '🤖' '👽' '🎉']\n", + "Key 4: ['😂' '😇' '😍' '🤓' '🐙']\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ], + "text/markdown": "### Index Keypad" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Key 0: [13 2 27 22 17]\n", + "Key 1: [ 7 8 9 10 11]\n", + "Key 2: [25 20 21 28 5]\n", + "Key 3: [19 26 15 16 29]\n", + "Key 4: [ 1 14 3 4 23]\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ], + "text/markdown": "### Customer Properties Keypad" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Key 0: [32049 6729 60466 26997 1309]\n", + "Key 1: [61686 12369 40030 7765 16822]\n", + "Key 2: [29442 13543 37604 7572 18665]\n", + "Key 3: [29124 1259 53208 47841 51215]\n", + "Key 4: [48741 14803 46096 27958 19258]\n" ] } ], - "execution_count": 58 + "execution_count": 229 }, { "metadata": {}, "cell_type": "markdown", "source": [ - "#### Set nKode\n", - "The user identifies properties in the keypad they want in their nkode.\n", - "Each properties in the gui has an index value.\n", - "Below the user has selected 16, 9, 6, 19.\n", - "Graphically represent with anything.\n", - "The only requirement is that the graphical properties must be associated with the same index value everytime the user goes to login.\n", - "If the user wants to change anything about their keypad(the number of keys, properties or graphical properties), they must also change their nkode." + "## Set nKode\n", + "The client receives `user_icons`, `set_signup_keypad`\n" ] }, { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:54.845027Z", - "start_time": "2025-03-17T14:56:54.840926Z" + "end_time": "2025-03-19T14:22:48.143268Z", + "start_time": "2025-03-19T14:22:48.140039Z" } }, "cell_type": "code", "source": [ - "keypad_view(signup_keypad, keypad_size.numb_of_keys)\n", "username = random_username()\n", "passcode_len = 4\n", - "user_passcode = signup_keypad[:passcode_len]\n", - "selected_keys_set = select_keys_with_passcode_values(user_passcode, signup_keypad, keypad_size.numb_of_keys)\n", - "print(f\"User Passcode: {user_passcode}\")\n", - "print(f\"Selected Keys\\n{selected_keys_set}\")\n", - "server_side_prop = [int(customer.cipher.prop_key[idx]) for idx in user_passcode]\n", - "print(f\"User Passcode Server-side properties: {server_side_prop}\")" + "user_passcode_indices = np.random.choice(set_signup_keypad.reshape(-1), size=passcode_len, replace=False).tolist()\n", + "selected_keys_set = select_keys_with_passcode_values(user_passcode_indices, set_signup_keypad, keypad_size.numb_of_keys)\n", + "print(f\"User Passcode Indices: {user_passcode_indices}\")\n", + "print(f\"User Passcode Icons: {user_icons[user_passcode_indices]}\")\n", + "print(f\"User Passcode Server-side properties: {customer.cipher.property_key[user_passcode_indices]}\")\n", + "print(f\"Selected Keys: {selected_keys_set}\")" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\n", - "Keypad View\n", - "Key 0: [24 7 2 3 29]\n", - "Key 1: [12 13 26 9 11]\n", - "Key 2: [ 6 1 8 15 23]\n", - "Key 3: [ 0 25 20 21 5]\n", - "Key 4: [18 19 14 27 17]\n", - "User Passcode: [24 7 2 3]\n", - "Selected Keys\n", - "[0, 0, 0, 0]\n", - "User Passcode Server-side properties: [43822, 25626, 49203, 20824]\n" + "User Passcode Indices: [11, 22, 1, 23]\n", + "User Passcode Icons: ['😴' '🐸' '😂' '🐙']\n", + "User Passcode Server-side properties: [16822 26997 48741 19258]\n", + "Selected Keys: [1, 0, 4, 4]\n" ] } ], - "execution_count": 59 + "execution_count": 237 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:54.869735Z", - "start_time": "2025-03-17T14:56:54.866365Z" + "end_time": "2025-03-19T14:18:48.697364Z", + "start_time": "2025-03-19T14:18:48.694055Z" } }, "cell_type": "code", "source": [ - "confirm_keypad = api.set_nkode(username, customer_id, selected_keys_set, session_id)\n", + "confirm_keypad = api.set_nkode(username, customer_id, selected_keys_set, signup_session_id)\n", "keypad_view(confirm_keypad, keypad_size.numb_of_keys)\n", - "selected_keys_confirm = select_keys_with_passcode_values(user_passcode, confirm_keypad, keypad_size.numb_of_keys)\n", + "selected_keys_confirm = select_keys_with_passcode_values(user_passcode_indices, confirm_keypad, keypad_size.numb_of_keys)\n", "print(f\"Selected Keys\\n{selected_keys_confirm}\")" ], "outputs": [ @@ -317,102 +334,210 @@ "name": "stdout", "output_type": "stream", "text": [ - "\n", - "Keypad View\n", - "Key 0: [24 13 20 15 17]\n", - "Key 1: [ 6 7 14 21 11]\n", - "Key 2: [ 0 1 26 27 29]\n", - "Key 3: [18 25 2 9 23]\n", - "Key 4: [12 19 8 3 5]\n", + "Key 0: [13 14 9 10 23]\n", + "Key 1: [25 20 21 4 17]\n", + "Key 2: [ 1 8 15 22 5]\n", + "Key 3: [19 26 27 28 11]\n", + "Key 4: [ 7 2 3 16 29]\n", "Selected Keys\n", - "[0, 1, 3, 4]\n" + "[2, 2, 3, 0]\n" ] } ], - "execution_count": 60 + "execution_count": 202 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:55.202903Z", - "start_time": "2025-03-17T14:56:54.894565Z" + "end_time": "2025-03-19T14:18:49.028822Z", + "start_time": "2025-03-19T14:18:48.719642Z" } }, "cell_type": "code", "source": [ "# the session is deleted after the nkode is confirmed. To rerun this cell, rerun the cells above starting with cell 8 where the username is created\n", - "success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id)\n", + "success = api.confirm_nkode(username, customer_id, selected_keys_confirm, signup_session_id)\n", "assert success" ], "outputs": [], - "execution_count": 61 + "execution_count": 203 }, { "metadata": {}, "cell_type": "markdown", "source": [ - "## Enciphering nKode\n", - "The method UserCipherKeys.encipher_nkode secures a users nKode in the database. This method is called in api.confirm_nkode\n", - "```\n", - "class EncipheredNKode(BaseModel):\n", - " code: str\n", - " mask: str\n", - "```\n", + "## User Cipher\n", + "\n", + "Users have 4 cipher keys:\n", + "1. prop_key: Half of the user's server-side passcode. the counterpart to the `customer_prop_key`. A user's passcode is made from elements in `user_prop_key XOR customer_prop_key`. Each property belongs to a set.\n", + "2. pass_key: The passcode key is used to encipher user passcode\n", + "3. combined_set_key: The combined set key is `user_set_key XOR customer_set_key`. The user_set_key isn't stored and can't be recovered with the `customer_set_key`\n", + "4. mask_key: The mask key used to encipher user nKode\n", + "\n", "\n" ] }, { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:55.227304Z", - "start_time": "2025-03-17T14:56:55.222844Z" + "end_time": "2025-03-19T14:18:49.044808Z", + "start_time": "2025-03-19T14:18:49.038903Z" } }, "cell_type": "code", "source": [ "from src.user_cipher import UserCipher\n", - "\n", - "user_set_key = np.array([46785, 4782, 4405, 44408, 35377, 55527])\n", - "combined_set_key = user_set_key ^ customer.cipher.set_key\n", - "user_keys = UserCipher(\n", - " prop_key = np.array([\n", - " 57200, 8398, 54694, 25997, 30388,\n", - " 46948, 45549, 30364, 49712, 10447,\n", - " 9205, 1777, 10731, 30979, 2795,\n", - " 17068, 56758, 62574, 28641, 11451,\n", - " 26820, 50373, 48783, 25350, 62177,\n", - " 60608, 54242, 4637, 3525, 16313\n", - " ]),\n", - " pass_key=np.array([16090, 38488, 45111, 32674, 46216, 52013, 48980, 36811, 35296, 17206]),\n", - " mask_key=np.array([29575, 43518, 44373, 62063, 37651, 31671, 31663, 65514, 36454, 47325]),\n", - " combined_set_key=np.array(combined_set_key),\n", - " max_nkode_len=customer.nkode_policy.max_nkode_len,\n", - ")\n", - "\n", - "ordered_customer_prop_key = customer.cipher.prop_key[user_passcode] # [int(customer.cipher.prop_key[idx]) for idx in user_passcode]\n", - "ordered_customer_set_key = [int(customer.cipher.get_prop_set_val(prop)) for prop in ordered_customer_prop_key]\n", - "print(f\"Passcode Set Vals: {ordered_customer_set_key}\")\n", - "print(f\"Passcode Prop Vals: {ordered_customer_prop_key}\")" + "user_cipher = UserCipher.create(keypad_size, customer.cipher.position_key, customer.nkode_policy.max_nkode_len)\n", + "user_prop_key_keypad = user_cipher.prop_key.reshape(-1, keypad_size.props_per_key)" + ], + "outputs": [], + "execution_count": 204 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-19T14:18:49.056184Z", + "start_time": "2025-03-19T14:18:49.053919Z" + } + }, + "cell_type": "code", + "source": "print(f\"Property Key:\\n{user_prop_key_keypad}\")", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Property Key:\n", + "[[42472 31697 42349 63196 42777 61068]\n", + " [ 7243 387 55065 19589 60418 22963]\n", + " [26541 59081 11622 22333 35608 42306]\n", + " [58621 57412 35828 19293 16394 53334]\n", + " [ 5908 43761 45282 44085 8881 21753]]\n" + ] + } + ], + "execution_count": 205 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-19T14:18:49.080744Z", + "start_time": "2025-03-19T14:18:49.078469Z" + } + }, + "cell_type": "code", + "source": "print(f\"Passcode Key: {user_cipher.pass_key}\")", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Passcode Key: [15958 16933 12810 19682 61534 54403 52645 54893 63261 22611]\n" + ] + } + ], + "execution_count": 206 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-19T14:18:49.125439Z", + "start_time": "2025-03-19T14:18:49.123Z" + } + }, + "cell_type": "code", + "source": "print(f\"Mask Key: {user_cipher.mask_key}\")", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mask Key: [11511 36348 3693 57612 7883 59516 54039 57361 15218 43846]\n" + ] + } + ], + "execution_count": 207 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-19T14:18:49.154768Z", + "start_time": "2025-03-19T14:18:49.152444Z" + } + }, + "cell_type": "code", + "source": "print(f\"Combined Set Key: {user_cipher.combined_set_key}\")", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Combined Set Key: [16302 28740 39642 59999 35588 576]\n" + ] + } + ], + "execution_count": 208 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-19T14:18:49.188943Z", + "start_time": "2025-03-19T14:18:49.186648Z" + } + }, + "cell_type": "code", + "source": "print(f\"User Set Key = combined_set_key XOR customer_set_key: {user_cipher.combined_set_key ^ customer.cipher.position_key}\")", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User Set Key = combined_set_key XOR customer_set_key: [62370 29747 52653 65461 59944 52180]\n" + ] + } + ], + "execution_count": 209 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-03-19T14:18:49.217942Z", + "start_time": "2025-03-19T14:18:49.215165Z" + } + }, + "cell_type": "code", + "source": [ + "set_properties_dict = dict(zip(user_cipher.combined_set_key, user_prop_key_keypad.T))\n", + "print(f\"Combined Set to Properties Map:\")\n", + "for set_val, props in set_properties_dict.items():\n", + " print(f\"{set_val}: {props}\")" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Passcode Set Vals: [15749, 12291, 52872, 49860]\n", - "Passcode Prop Vals: [43822 25626 49203 20824]\n" + "Combined Set to Properties Map:\n", + "16302: [42472 7243 26541 58621 5908]\n", + "28740: [31697 387 59081 57412 43761]\n", + "39642: [42349 55065 11622 35828 45282]\n", + "59999: [63196 19589 22333 19293 44085]\n", + "35588: [42777 60418 35608 16394 8881]\n", + "576: [61068 22963 42306 53334 21753]\n" ] } ], - "execution_count": 62 + "execution_count": 210 }, { "metadata": {}, "cell_type": "markdown", "source": [ "#### Encipher Mask\n", - "Recall:\n", - "1. combined_set_key = (user_set_key ^ customer_set_key)\n", + "1. Order customer properties by user passcode.\n", + "2. Get the set indices (`set_indices`) of the ordered customer properties.\n", + "3. Pad the\n", + "1. combined_set_key = user_set_key ^ customer_set_key\n", "2. padded_ordered_customer_set = customer_set_key # ordered by user passcode and padded with extra set key values to be equal to max_nkode_len\n", "3. len(set_key) == len(mask_key) == len(padded_ordered_customer_set) == max_nkode_len == 10\n", "where i is the index\n", @@ -425,20 +550,21 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:55.260751Z", - "start_time": "2025-03-17T14:56:55.258152Z" + "end_time": "2025-03-19T14:18:49.244375Z", + "start_time": "2025-03-19T14:18:49.241861Z" } }, "cell_type": "code", "source": [ - "padded_ordered_customer_set = user_keys.pad_user_mask(ordered_customer_set_key, customer.cipher.set_key)\n", - "set_idx = [customer.cipher.get_set_index(set_val) for set_val in padded_ordered_customer_set]\n", - "ordered_set_key = user_keys.combined_set_key[set_idx]\n", - "mask = ordered_set_key ^ padded_ordered_customer_set ^ user_keys.mask_key\n", - "encoded_mask = user_keys.encode_base64_str(mask)" + "set_indices = customer.cipher.get_passcode_position_indices_padded(list(user_passcode_indices), customer.nkode_policy.max_nkode_len)\n", + "ordered_combined_set_key = user_cipher.combined_set_key[set_indices]\n", + "ordered_customer_set_key = customer.cipher.position_key[set_indices]\n", + "ordered_user_set_key = ordered_customer_set_key ^ ordered_combined_set_key\n", + "mask = ordered_user_set_key ^ user_cipher.mask_key\n", + "encoded_mask = user_cipher.encode_base64_str(mask)" ], "outputs": [], - "execution_count": 63 + "execution_count": 211 }, { "metadata": {}, @@ -455,33 +581,44 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:55.590495Z", - "start_time": "2025-03-17T14:56:55.285097Z" + "end_time": "2025-03-19T14:18:49.575573Z", + "start_time": "2025-03-19T14:18:49.268784Z" } }, "cell_type": "code", "source": [ - "ciphered_customer_props = np.bitwise_xor(customer.cipher.prop_key, user_keys.prop_key)\n", - "passcode_ciphered_props = [ciphered_customer_props[idx] for idx in user_passcode]\n", + "combined_prop_key = customer.cipher.property_key ^ user_cipher.prop_key\n", + "user_passcode = combined_prop_key[user_passcode_indices]\n", "pad_len = customer.nkode_policy.max_nkode_len - passcode_len\n", - "\n", - "passcode_ciphered_props.extend([0 for _ in range(pad_len)])\n", - "\n", - "ciphered_code = np.bitwise_xor(passcode_ciphered_props, user_keys.pass_key)\n", - "\n", - "passcode_bytes = ciphered_code.tobytes()\n", - "passcode_digest = user_keys.prehash_passcode(user_passcode, customer.cipher)# base64.b64encode(hashlib.sha256(passcode_bytes).digest())\n", - "hashed_data = bcrypt.hashpw(passcode_digest, bcrypt.gensalt(rounds=12))\n", - "code = hashed_data.decode(\"utf-8\")" + "user_passcode_padded = np.concatenate((user_passcode, np.zeros(pad_len, dtype=user_passcode.dtype)))\n", + "ciphered_passcode = user_passcode_padded ^ user_cipher.pass_key\n", + "passcode_prehash = base64.b64encode(hashlib.sha256(ciphered_passcode.tobytes()).digest())\n", + "passcode_hash = bcrypt.hashpw(passcode_prehash, bcrypt.gensalt(rounds=12)).decode(\"utf-8\")" ], "outputs": [], - "execution_count": 64 + "execution_count": 212 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### Enciphered nKode\n", + "An encipher passcode has two parts:\n", + "1. Code: the enciphered and hashed passcode\n", + "2. Mask: the mask is used to recover the passcode sets. The mask and the users key select are used to recover the property values of the user's passcode\n", + "The method UserCipherKeys.encipher_nkode secures a users nKode in the database. This method is called in api.confirm_nkode\n", + "```\n", + "class EncipheredNKode(BaseModel):\n", + " code: str\n", + " mask: str\n", + "```\n" + ] }, { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:55.595860Z", - "start_time": "2025-03-17T14:56:55.593975Z" + "end_time": "2025-03-19T14:18:49.585161Z", + "start_time": "2025-03-19T14:18:49.583132Z" } }, "cell_type": "code", @@ -490,11 +627,11 @@ "\n", "enciphered_nkode = EncipheredNKode(\n", " mask=encoded_mask,\n", - " code=code,\n", + " code=passcode_hash,\n", ")" ], "outputs": [], - "execution_count": 65 + "execution_count": 213 }, { "metadata": {}, @@ -508,16 +645,16 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:55.910640Z", - "start_time": "2025-03-17T14:56:55.605472Z" + "end_time": "2025-03-19T14:18:49.901534Z", + "start_time": "2025-03-19T14:18:49.594710Z" } }, "cell_type": "code", "source": [ "login_keypad = api.get_login_keypad(username, customer_id)\n", "keypad_view(login_keypad, keypad_size.props_per_key)\n", - "selected_keys_login = select_keys_with_passcode_values(user_passcode, login_keypad, keypad_size.props_per_key)\n", - "print(f\"User Passcode: {user_passcode}\\n\")\n", + "selected_keys_login = select_keys_with_passcode_values(user_passcode_indices, login_keypad, keypad_size.props_per_key)\n", + "print(f\"User Passcode: {user_passcode_indices}\\n\")\n", "print(f\"Selected Keys:\\n {selected_keys_login}\\n\")\n", "success = api.login(customer_id, username, selected_keys_login)\n", "assert success" @@ -527,22 +664,20 @@ "name": "stdout", "output_type": "stream", "text": [ - "\n", - "Keypad View\n", - "Key 0: [24 7 2 3 28 29]\n", - "Key 1: [12 13 26 9 16 11]\n", - "Key 2: [ 6 1 8 15 10 23]\n", - "Key 3: [ 0 25 20 21 22 5]\n", - "Key 4: [18 19 14 27 4 17]\n", - "User Passcode: [24 7 2 3]\n", + "Key 0: [ 6 19 8 3 4 23]\n", + "Key 1: [18 13 2 21 28 5]\n", + "Key 2: [12 25 14 15 16 11]\n", + "Key 3: [24 7 26 9 22 17]\n", + "Key 4: [ 0 1 20 27 10 29]\n", + "User Passcode: [15, 1, 19, 23]\n", "\n", "Selected Keys:\n", - " [0, 0, 0, 0]\n", + " [2, 4, 0, 0]\n", "\n" ] } ], - "execution_count": 66 + "execution_count": 214 }, { "metadata": {}, @@ -560,78 +695,37 @@ "source": [ "### Decipher Mask\n", "Recall:\n", - "- set_key = (set_key_rand_numb ^ set_val)\n", - "- mask = mask_key_rand_num ^ set_key_rand_numb\n", + "- combined_set_key = user_set_key ^ customer_set_key\n", + "- mask = mask_key ^ ordered_user_set_key\n", "\n", "Recover nKode set values: \n", "- decode mask from base64 to int\n", - "- deciphered_mask = mask ^ mask_key\n", - "- deciphered_mask = set_key_rand_numb # mask_key_rand_num is cancelled out\n", - "- set_key_rand_component = set_key ^ set_values\n", - "- deduce the set value" + "- ordered_user_set_key = mask ^ mask_key\n", + "- ordered_combined_set_key = ordered_customer_set_key ^ ordered_user_set_key\n", + "- deduce the set indices" ] }, { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:55.922451Z", - "start_time": "2025-03-17T14:56:55.919172Z" - } - }, - "cell_type": "code", - "source": [ - "user = api.customers[customer_id].users[username]\n", - "mask = user.cipher.decode_base64_str(user.enciphered_passcode.mask)\n", - "deciphered_mask = mask ^ user.cipher.mask_key\n", - "set_key = customer.cipher.set_key ^ user.cipher.combined_set_key\n", - "passcode_sets = []\n", - "for set_cipher in deciphered_mask[:passcode_len]:\n", - " set_idx = np.where(set_key == set_cipher)[0][0]\n", - " passcode_sets.append(int(customer.cipher.set_key[set_idx]))\n", - "print(passcode_sets)" - ], - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[15749, 12291, 52872, 49860]\n" - ] - } - ], - "execution_count": 67 - }, - { - "metadata": {}, - "cell_type": "markdown", - "source": "### Get Presumed Properties\n" - }, - { - "metadata": { - "ExecuteTime": { - "end_time": "2025-03-17T14:56:55.943804Z", - "start_time": "2025-03-17T14:56:55.940721Z" + "end_time": "2025-03-19T14:18:49.914512Z", + "start_time": "2025-03-19T14:18:49.911338Z" } }, "cell_type": "code", "source": [ "login_keypad = api.get_login_keypad(username, customer_id)\n", - "selected_keys_login = select_keys_with_passcode_values(user_passcode, login_keypad, keypad_size.props_per_key)\n", - "set_vals_idx = [customer.cipher.get_set_index(set_val) for set_val in passcode_sets]\n", - "presumed_selected_properties_idx = customer.users[username].user_keypad.get_prop_idxs_by_keynumb_setidx(selected_keys_login, set_vals_idx)\n", - "assert user_passcode.tolist() == presumed_selected_properties_idx" + "selected_keys_login = select_keys_with_passcode_values(user_passcode_indices, login_keypad, keypad_size.props_per_key)\n", + "user = api.customers[customer_id].users[username]\n", + "mask = user.cipher.decode_base64_str(user.enciphered_passcode.mask)\n", + "deciphered_mask = mask ^ user.cipher.mask_key\n", + "set_key = customer.cipher.position_key ^ user.cipher.combined_set_key\n", + "passcode_set_index = [int(np.where(set_key == set_cipher)[0][0]) for set_cipher in deciphered_mask[:passcode_len]]\n", + "presumed_selected_properties_idx = customer.users[username].user_keypad.get_prop_idxs_by_keynumb_setidx(selected_keys_login, passcode_set_index)\n", + "assert user_passcode_indices == presumed_selected_properties_idx\n" ], - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[24, 7, 2, 3]\n", - "[24, 7, 2, 3]\n" - ] - } - ], - "execution_count": 68 + "outputs": [], + "execution_count": 215 }, { "metadata": {}, @@ -641,17 +735,17 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:56.297835Z", - "start_time": "2025-03-17T14:56:55.992586Z" + "end_time": "2025-03-19T14:18:50.236818Z", + "start_time": "2025-03-19T14:18:49.932427Z" } }, "cell_type": "code", "source": [ - "enciphered_nkode = user.cipher.compare_nkode(presumed_selected_properties_idx, customer.cipher, user.enciphered_passcode.code)\n", - "assert enciphered_nkode\n" + "valid_nkode = user.cipher.compare_nkode(presumed_selected_properties_idx, customer.cipher, user.enciphered_passcode.code)\n", + "assert valid_nkode\n" ], "outputs": [], - "execution_count": 69 + "execution_count": 216 }, { "metadata": {}, @@ -667,8 +761,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:56.923156Z", - "start_time": "2025-03-17T14:56:56.306667Z" + "end_time": "2025-03-19T14:18:50.851576Z", + "start_time": "2025-03-19T14:18:50.240709Z" } }, "cell_type": "code", @@ -676,14 +770,14 @@ "def print_user_enciphered_code():\n", " mask = api.customers[customer_id].users[username].enciphered_passcode.mask\n", " code = api.customers[customer_id].users[username].enciphered_passcode.code\n", - " print(f\"mask: {mask}, code: {code}\")\n", + " print(f\"mask: {mask}, code: {code}\\n\")\n", "\n", "print_user_enciphered_code() \n", "api.renew_keys(customer_id)\n", "print_user_enciphered_code()\n", "\n", "login_keypad = api.get_login_keypad(username, customer_id)\n", - "selected_keys_login = select_keys_with_passcode_values(user_passcode, login_keypad, keypad_size.props_per_key)\n", + "selected_keys_login = select_keys_with_passcode_values(user_passcode_indices, login_keypad, keypad_size.props_per_key)\n", "success = api.login(customer_id, username, selected_keys_login)\n", "assert success\n", "print_user_enciphered_code()" @@ -693,13 +787,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "mask: h7xMhd4kK3faZ6C1Ofi+oVjSI4c=, code: $2b$12$/wwEjbTk90oCnD2Ny6KTWeNJ6YPEyc9yJgY00FGddkQ8cE4LNkSvK\n", - "mask: h7xMhd4kK3faZ6C1Ofi+oVjSI4c=, code: $2b$12$/wwEjbTk90oCnD2Ny6KTWeNJ6YPEyc9yJgY00FGddkQ8cE4LNkSvK\n", - "mask: nzxGInaKD4KfAP9Djc4tz6ROuko=, code: $2b$12$PZ6j.Ccq8/LEWpht4Bnw1.JLXS0dMrgpzBLsnPA33ND7BjsT3CqxW\n" + "mask: tBqaWnmsGlsclLRq7qdFeuZ/Krg=, code: $2b$12$iBdtB4bRMx9VnWKwAInkwecUx5wwiL1C0DVWO.sk9EG1syVVe2CaS\n", + "\n", + "mask: tBqaWnmsGlsclLRq7qdFeuZ/Krg=, code: $2b$12$iBdtB4bRMx9VnWKwAInkwecUx5wwiL1C0DVWO.sk9EG1syVVe2CaS\n", + "\n", + "mask: DpuEwDyPrOF4pAxxozyqUHigaak=, code: $2b$12$ngd6PgpZo/UhIANnrRimlOliRIGwAaS6zbe5gqTzYHPBaeAa3vJsy\n", + "\n" ] } ], - "execution_count": 70 + "execution_count": 217 }, { "metadata": {}, @@ -713,20 +810,20 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:56.935444Z", - "start_time": "2025-03-17T14:56:56.931244Z" + "end_time": "2025-03-19T14:18:50.865243Z", + "start_time": "2025-03-19T14:18:50.861260Z" } }, "cell_type": "code", "source": [ - "old_props = customer.cipher.prop_key.copy()\n", - "old_sets = customer.cipher.set_key.copy()\n", + "old_props = customer.cipher.property_key.copy()\n", + "old_sets = customer.cipher.position_key.copy()\n", "customer.cipher.renew()\n", - "new_props = customer.cipher.prop_key\n", - "new_sets = customer.cipher.set_key" + "new_props = customer.cipher.property_key\n", + "new_sets = customer.cipher.position_key" ], "outputs": [], - "execution_count": 71 + "execution_count": 218 }, { "metadata": {}, @@ -739,8 +836,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:56.954373Z", - "start_time": "2025-03-17T14:56:56.952101Z" + "end_time": "2025-03-19T14:18:50.876670Z", + "start_time": "2025-03-19T14:18:50.874092Z" } }, "cell_type": "code", @@ -753,7 +850,7 @@ " user.cipher.prop_key = np.bitwise_xor(user.cipher.prop_key, props_xor)" ], "outputs": [], - "execution_count": 72 + "execution_count": 219 }, { "metadata": {}, @@ -763,22 +860,22 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-03-17T14:56:57.298001Z", - "start_time": "2025-03-17T14:56:56.988801Z" + "end_time": "2025-03-19T14:18:51.191895Z", + "start_time": "2025-03-19T14:18:50.885862Z" } }, "cell_type": "code", "source": [ "user.cipher = UserCipher.create(\n", " customer.cipher.keypad_size,\n", - " customer.cipher.set_key,\n", + " customer.cipher.position_key,\n", " user.cipher.max_nkode_len\n", ")\n", "user.enciphered_passcode = user.cipher.encipher_nkode(presumed_selected_properties_idx, customer.cipher)\n", "user.renew = False" ], "outputs": [], - "execution_count": 73 + "execution_count": 220 } ], "metadata": { diff --git a/src/customer.py b/src/customer.py index 196d1fe..fd8b5cb 100644 --- a/src/customer.py +++ b/src/customer.py @@ -1,5 +1,8 @@ from dataclasses import dataclass from uuid import UUID, uuid4 + +import numpy as np + from src.customer_cipher import CustomerCipher from src.models import NKodePolicy from src.user import User @@ -38,8 +41,8 @@ class Customer: passcode_len = len(selected_keys) user = self.users[username] passcode_set_vals = user.cipher.decipher_mask( - user.enciphered_passcode.mask, self.cipher.set_key, passcode_len) - set_vals_idx = [self.cipher.get_set_index(set_val) for set_val in passcode_set_vals] + user.enciphered_passcode.mask, self.cipher.position_key, passcode_len) + set_vals_idx = [self.cipher.get_position_index(set_val) for set_val in passcode_set_vals] presumed_property_idxs = user.user_keypad.get_prop_idxs_by_keynumb_setidx(selected_keys, set_vals_idx) if not user.cipher.compare_nkode(presumed_property_idxs, self.cipher,user.enciphered_passcode.code): return False @@ -50,11 +53,11 @@ class Customer: return True def renew_keys(self) -> bool: - old_props = self.cipher.prop_key.copy() - old_sets = self.cipher.set_key.copy() + old_props = self.cipher.property_key.copy() + old_sets = self.cipher.position_key.copy() self.cipher.renew() - new_props = self.cipher.prop_key - new_sets = self.cipher.set_key + new_props = self.cipher.property_key + new_sets = self.cipher.position_key props_xor = new_props ^ old_props set_xor = new_sets ^ old_sets @@ -65,9 +68,10 @@ class Customer: def valid_new_nkode(self, passcode_prop_idx: list[int]) -> bool: nkode_len = len(passcode_prop_idx) - passcode_set_values = [ - self.cipher.get_prop_set_val(int(self.cipher.prop_key[prop_idx])) for prop_idx in passcode_prop_idx - ] + #passcode_set_values = [ + # self.cipher.get_prop_set_val(int(self.cipher.property_key[prop_idx])) for prop_idx in passcode_prop_idx + #] + passcode_set_values = self.cipher.get_props_position_vals(passcode_prop_idx) distinct_sets = len(set(passcode_set_values)) distinct_properties = len(set(passcode_prop_idx)) if ( @@ -77,3 +81,5 @@ class Customer: ): return True return False + + diff --git a/src/customer_cipher.py b/src/customer_cipher.py index 72ffcac..953442e 100644 --- a/src/customer_cipher.py +++ b/src/customer_cipher.py @@ -6,16 +6,13 @@ from src.models import KeypadSize @dataclass class CustomerCipher: - prop_key: np.ndarray - set_key: np.ndarray + property_key: np.ndarray + position_key: np.ndarray 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") @@ -24,28 +21,40 @@ class CustomerCipher: 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}") - # Using numpy to generate non-repeating random integers prop_key = np.random.choice(2 ** 16, size=keypad_size.total_props, replace=False) - set_key = np.random.choice(2 ** 16, size=keypad_size.props_per_key, replace=False) - + pos_key = np.random.choice(2 ** 16, size=keypad_size.props_per_key, replace=False) return cls( - prop_key=prop_key, - set_key=set_key, + property_key=prop_key, + position_key=pos_key, keypad_size=keypad_size, ) def renew(self): - self.prop_key = np.random.choice(2 ** 16, size=self.keypad_size.total_props, replace=False) - self.set_key = np.random.choice(2 ** 16, size=self.keypad_size.props_per_key, replace=False) + self.property_key = np.random.choice(2 ** 16, size=self.keypad_size.total_props, replace=False) + self.position_key = np.random.choice(2 ** 16, size=self.keypad_size.props_per_key, replace=False) - def get_prop_set_val(self, prop: int) -> int: - assert np.isin(prop, self.prop_key) - prop_idx = np.where(self.prop_key == prop)[0][0] - set_idx = prop_idx % self.keypad_size.props_per_key - return int(self.set_key[set_idx]) + def get_props_position_vals(self, props: np.ndarray | list[int]) -> np.ndarray: + if not all([prop in self.property_key for prop in props]): + raise ValueError("Property values must be within valid range") + pos_vals = [self._get_prop_position_val(prop) for prop in props] + return np.array(pos_vals) - def get_set_index(self, set_val: int) -> int: - if not np.isin(set_val, self.set_key): - raise ValueError(f"Set value {set_val} not found in set values") - return int(np.where(self.set_key == set_val)[0][0]) \ No newline at end of file + def _get_prop_position_val(self, prop: int) -> int: + assert prop in self.property_key + prop_idx = np.where(self.property_key == prop)[0][0] + pos_idx = prop_idx % self.keypad_size.props_per_key + return int(self.position_key[pos_idx]) + + def get_position_index(self, pos_val: int) -> int: + if not np.isin(pos_val, self.position_key): + raise ValueError(f"Position value {pos_val} not found in customer cipher position_key") + return int(np.where(self.position_key == pos_val)[0][0]) + + def get_passcode_position_indices_padded(self, passcode_indices: list[int], max_nkode_len: int) -> list[int]: + if not all(0 <= idx < self.keypad_size.total_props for idx in passcode_indices): + raise ValueError("invalid passcode index") + pos_indices = [idx % self.keypad_size.props_per_key for idx in passcode_indices] + pad_len = max_nkode_len - len(passcode_indices) + pad = np.random.choice(self.keypad_size.props_per_key, pad_len, replace=True) + return pos_indices + pad.tolist() \ No newline at end of file diff --git a/src/models.py b/src/models.py index b3143e4..121d29c 100644 --- a/src/models.py +++ b/src/models.py @@ -28,4 +28,4 @@ class KeypadSize: @property def is_dispersable(self) -> bool: - return self.props_per_key <= self.numb_of_keys \ No newline at end of file + return self.props_per_key <= self.numb_of_keys diff --git a/src/nkode_api.py b/src/nkode_api.py index a9f74a9..743ce3b 100644 --- a/src/nkode_api.py +++ b/src/nkode_api.py @@ -74,7 +74,7 @@ class NKodeAPI: passcode = self.signup_sessions[session_id].deduce_passcode(confirm_key_entry) new_user_keys = UserCipher.create( customer.cipher.keypad_size, - customer.cipher.set_key, + customer.cipher.position_key, customer.nkode_policy.max_nkode_len ) enciphered_passcode = new_user_keys.encipher_nkode(passcode, customer.cipher) diff --git a/src/user.py b/src/user.py index 1ee091e..0700c0a 100644 --- a/src/user.py +++ b/src/user.py @@ -24,7 +24,7 @@ class User: def refresh_passcode(self, passcode_prop_idxs: list[int], customer_cipher: CustomerCipher): self.cipher = UserCipher.create( customer_cipher.keypad_size, - customer_cipher.set_key, + customer_cipher.position_key, self.cipher.max_nkode_len ) self.enciphered_passcode = self.cipher.encipher_nkode(passcode_prop_idxs, customer_cipher) diff --git a/src/user_cipher.py b/src/user_cipher.py index da49c16..e5bd3de 100644 --- a/src/user_cipher.py +++ b/src/user_cipher.py @@ -19,9 +19,7 @@ class UserCipher: def create(cls, keypad_size: KeypadSize, customer_set_key: np.ndarray, max_nkode_len: int) -> 'UserCipher': if len(customer_set_key) != keypad_size.props_per_key: raise ValueError("Invalid set values") - user_set_key = np.random.choice(2**16,size=keypad_size.props_per_key, replace=False) - return UserCipher( prop_key=np.random.choice(2 ** 16, size=keypad_size.total_props, replace=False), pass_key=np.random.choice(2 ** 16, size=max_nkode_len, replace=False), @@ -93,7 +91,7 @@ class UserCipher: passcode_cipher[:passcode_len] = ( passcode_cipher[:passcode_len] ^ self.prop_key[passcode_prop_idx] ^ - customer_prop.prop_key[passcode_prop_idx] + customer_prop.property_key[passcode_prop_idx] ) return passcode_cipher.astype(np.uint16).tobytes() @@ -102,12 +100,10 @@ class UserCipher: passcode_prop_idx: list[int], customer_cipher: CustomerCipher ) -> str: - customer_props = customer_cipher.prop_key[passcode_prop_idx] - customer_sets = [customer_cipher.get_prop_set_val(prop) for prop in customer_props] - padded_customer_sets = self.pad_user_mask(np.array(customer_sets), customer_cipher.set_key) - set_idx = [customer_cipher.get_set_index(set_val) for set_val in padded_customer_sets] - ordered_set_key = self.combined_set_key[set_idx] - mask = ordered_set_key ^ padded_customer_sets ^ self.mask_key + set_idxs = customer_cipher.get_passcode_position_indices_padded(passcode_prop_idx, len(self.mask_key)) + ordered_set_key = self.combined_set_key[set_idxs] + ordered_customer_key = customer_cipher.position_key[set_idxs] + mask = ordered_set_key ^ ordered_customer_key ^ self.mask_key encoded_mask = self.encode_base64_str(mask) return encoded_mask diff --git a/src/user_keypad.py b/src/user_keypad.py index 65bc21e..399c092 100644 --- a/src/user_keypad.py +++ b/src/user_keypad.py @@ -83,16 +83,16 @@ class UserKeypad: 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}") if not (0 <= set_idx < self.keypad_size.props_per_key): - raise ValueError(f"set_idx must be between 0 and {self.keypad_size.props_per_key - 1}") + raise ValueError(f"set_indices must be between 0 and {self.keypad_size.props_per_key - 1}") keypad_prop_idx = self.keypad_matrix() return int(keypad_prop_idx[key_numb][set_idx]) def get_prop_idxs_by_keynumb_setidx(self, key_numb: list[int], set_idx: list[int]) -> list[int]: if len(key_numb) != len(set_idx): - raise ValueError("key_numb and set_idx must be the same length") + raise ValueError("key_numb and set_indices must be the same length") if not all(0 <= kn < self.keypad_size.numb_of_keys for kn in key_numb): raise ValueError(f"All key_numb must be between 0 and {self.keypad_size.numb_of_keys - 1}") if not all(0 <= si < self.keypad_size.props_per_key for si in set_idx): - raise ValueError(f"All set_idx must be between 0 and {self.keypad_size.props_per_key - 1}") + raise ValueError(f"All set_indices must be between 0 and {self.keypad_size.props_per_key - 1}") keypad_matrix = self.keypad_matrix() return keypad_matrix[key_numb, set_idx].reshape(-1).tolist() diff --git a/test/test_user_cipher_keys.py b/test/test_user_cipher_keys.py index 80e6a0b..26845d5 100644 --- a/test/test_user_cipher_keys.py +++ b/test/test_user_cipher_keys.py @@ -11,7 +11,6 @@ from src.user_cipher import UserCipher, CustomerCipher ] ) def test_encode_decode_base64(passcode_len): - #data = generate_random_nonrepeating_list(passcode_len) data = np.random.choice(2**16, passcode_len, replace=False) encoded = UserCipher.encode_base64_str(data) decoded = UserCipher.decode_base64_str(encoded) @@ -28,14 +27,12 @@ def test_encode_decode_base64(passcode_len): ]) def test_decode_mask(keypad_size, max_nkode_len): customer = CustomerCipher.create(keypad_size) - #passcode_entry = generate_random_nonrepeating_list(keypad_size.numb_of_props,max_val=keypad_size.numb_of_props)[:4] passcode_entry = np.random.choice(keypad_size.total_props, 4, replace=False) - passcode_values = [customer.prop_key[idx] for idx in passcode_entry] - set_vals = customer.set_key + passcode_values = [customer.property_key[idx] for idx in passcode_entry] + set_vals = customer.position_key user_keys = UserCipher.create(keypad_size, set_vals, max_nkode_len) passcode = user_keys.encipher_nkode(passcode_entry, customer) - - orig_passcode_set_vals = [customer.get_prop_set_val(prop) for prop in passcode_values] + orig_passcode_set_vals = customer.get_props_position_vals(passcode_values) 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 (all(orig_passcode_set_vals[idx] == passcode_set_vals[idx] for idx in range(len(passcode_set_vals))))