924 lines
29 KiB
Plaintext
924 lines
29 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "code",
|
|
"source": [
|
|
"import sys\n",
|
|
"import os\n",
|
|
"sys.path.append(os.path.abspath('..')) # Adds the parent directory to path\n",
|
|
"from src.nkode_api import NKodeAPI\n",
|
|
"from src.models import NKodePolicy, KeypadSize\n",
|
|
"from src.utils import select_keys_with_passcode_values\n",
|
|
"from secrets import choice\n",
|
|
"from string import ascii_lowercase\n",
|
|
"import numpy as np\n",
|
|
"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 keypad_view(keypad: np.ndarray, props_per_key: int):\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"
|
|
],
|
|
"metadata": {
|
|
"collapsed": false,
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:08.637548Z",
|
|
"start_time": "2025-03-24T20:25:08.607886Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"execution_count": 1
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:08.642553Z",
|
|
"start_time": "2025-03-24T20:25:08.640602Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"api = NKodeAPI()\n",
|
|
"user_icons = np.array([\n",
|
|
" \"😀\", \"😂\", \"🥳\", \"😍\", \"🤓\",\n",
|
|
" \"😎\", \"🥺\", \"😡\", \"😱\", \"🤯\",\n",
|
|
" \"🥰\", \"😴\", \"🤔\", \"🙃\", \"😇\",\n",
|
|
" \"🤖\", \"👽\", \"👾\", \"🐱\", \"🐶\",\n",
|
|
" \"🦁\", \"🐻\", \"🐸\", \"🐙\", \"🦄\",\n",
|
|
" \"🌟\", \"⚡\", \"🔥\", \"🍕\", \"🎉\"\n",
|
|
"])"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": 2
|
|
},
|
|
{
|
|
"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`)"
|
|
]
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"#### Customer Cipher Keys\n",
|
|
"Each customer has unique cipher keys.\n",
|
|
"These keys are used to encipher and decipher a user's 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.\n",
|
|
"2. position key: Combined with the user position key to get the server-side representation the position in each key.\n"
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:08.679087Z",
|
|
"start_time": "2025-03-24T20:25:08.667792Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"policy = NKodePolicy(\n",
|
|
" max_nkode_len=10,\n",
|
|
" min_nkode_len=4,\n",
|
|
" distinct_positions=0,\n",
|
|
" distinct_properties=4,\n",
|
|
")\n",
|
|
"keypad_size = KeypadSize(\n",
|
|
" numb_of_keys = 5,\n",
|
|
" props_per_key = 6\n",
|
|
")\n",
|
|
"customer_id = api.create_new_customer(keypad_size, policy)\n",
|
|
"customer = api.customers[customer_id]\n",
|
|
"print(f\"Customer Position Key: {customer.cipher.position_key}\")\n",
|
|
"print(f\"Customer Properties 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}\")\n",
|
|
"position_properties_dict = dict(zip(customer.cipher.position_key, customer_prop_keypad.T))\n",
|
|
"print(f\"Position to Properties Map:\")\n",
|
|
"for pos_val, props in position_properties_dict.items():\n",
|
|
" print(f\"{pos_val}: {props}\")"
|
|
],
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Customer Position Key: [36587 51243 16045 24580 51231 48943]\n",
|
|
"Customer Properties Key:\n",
|
|
"[23910 10306 19502 5449 54702 12273]\n",
|
|
"[53013 18581 4421 45433 39661 27006]\n",
|
|
"[16680 54596 31667 35220 1865 8499]\n",
|
|
"[37220 26796 20234 3387 44239 47346]\n",
|
|
"[55497 7967 5622 1002 13135 4901]\n",
|
|
"Position to Properties Map:\n",
|
|
"36587: [23910 53013 16680 37220 55497]\n",
|
|
"51243: [10306 18581 54596 26796 7967]\n",
|
|
"16045: [19502 4421 31667 20234 5622]\n",
|
|
"24580: [ 5449 45433 35220 3387 1002]\n",
|
|
"51231: [54702 39661 1865 44239 13135]\n",
|
|
"48943: [12273 27006 8499 47346 4901]\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 3
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:08.687137Z",
|
|
"start_time": "2025-03-24T20:25:08.684437Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"user_icon_keypad = user_icons.reshape(-1, keypad_size.props_per_key)\n",
|
|
"pos_icons_dict = dict(zip(customer.cipher.position_key, user_icon_keypad.T))\n",
|
|
"print(\"Position Value to Icons Map:\")\n",
|
|
"for pos_val, icons in pos_icons_dict.items():\n",
|
|
" print(f\"{pos_val}: {icons}\")\n"
|
|
],
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Position Value to Icons Map:\n",
|
|
"36587: ['😀' '🥺' '🤔' '🐱' '🦄']\n",
|
|
"51243: ['😂' '😡' '🙃' '🐶' '🌟']\n",
|
|
"16045: ['🥳' '😱' '😇' '🦁' '⚡']\n",
|
|
"24580: ['😍' '🤯' '🤖' '🐻' '🔥']\n",
|
|
"51231: ['🤓' '🥰' '👽' '🐸' '🍕']\n",
|
|
"48943: ['😎' '😴' '👾' '🐙' '🎉']\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 4
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"### User Signup\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",
|
|
"\n",
|
|
"#### Generate Keypad\n",
|
|
" For the server to determine the users nKode, the user's keypad must be dispersable.\n",
|
|
" To make the keypad dispersable, the server will randomly drop key positions so the number of properties per key is equal to the number of keys.\n",
|
|
" In our case, the server drops 1 key position 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 their server-side properties"
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:08.705881Z",
|
|
"start_time": "2025-03-24T20:25:08.699280Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"username = random_username()\n",
|
|
"signup_session_id, set_signup_keypad = api.generate_signup_keypad(customer_id, username)\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": [
|
|
"<IPython.core.display.Markdown object>"
|
|
],
|
|
"text/markdown": "### Icon Keypad"
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Key 0: ['🥺' '😂' '😱' '🔥' '👽']\n",
|
|
"Key 1: ['🐱' '🙃' '😇' '🐻' '🐸']\n",
|
|
"Key 2: ['😀' '🌟' '🥳' '🤖' '🤓']\n",
|
|
"Key 3: ['🦄' '🐶' '🦁' '🤯' '🥰']\n",
|
|
"Key 4: ['🤔' '😡' '⚡' '😍' '🍕']\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<IPython.core.display.Markdown object>"
|
|
],
|
|
"text/markdown": "### Index Keypad"
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Key 0: [ 6 1 8 27 16]\n",
|
|
"Key 1: [18 13 14 21 22]\n",
|
|
"Key 2: [ 0 25 2 15 4]\n",
|
|
"Key 3: [24 19 20 9 10]\n",
|
|
"Key 4: [12 7 26 3 28]\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<IPython.core.display.Markdown object>"
|
|
],
|
|
"text/markdown": "### Customer Properties Keypad"
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Key 0: [53013 10306 4421 1002 1865]\n",
|
|
"Key 1: [37220 54596 31667 3387 44239]\n",
|
|
"Key 2: [23910 7967 19502 35220 54702]\n",
|
|
"Key 3: [55497 26796 20234 45433 39661]\n",
|
|
"Key 4: [16680 18581 5622 5449 13135]\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 5
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"### Set nKode\n",
|
|
"The client receives `user_icons`, `set_signup_keypad`\n"
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:08.729881Z",
|
|
"start_time": "2025-03-24T20:25:08.726837Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"passcode_len = 4\n",
|
|
"passcode_property_indices = np.random.choice(set_signup_keypad.reshape(-1), size=passcode_len, replace=False).tolist()\n",
|
|
"selected_keys_set = select_keys_with_passcode_values(passcode_property_indices, set_signup_keypad, keypad_size.numb_of_keys)\n",
|
|
"print(f\"User Passcode Indices: {passcode_property_indices}\")\n",
|
|
"print(f\"User Passcode Icons: {user_icons[passcode_property_indices]}\")\n",
|
|
"print(f\"User Passcode Server-side properties: {customer.cipher.property_key[passcode_property_indices]}\")\n",
|
|
"print(f\"Selected Keys: {selected_keys_set}\")"
|
|
],
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"User Passcode Indices: [22, 8, 14, 12]\n",
|
|
"User Passcode Icons: ['🐸' '😱' '😇' '🤔']\n",
|
|
"User Passcode Server-side properties: [44239 4421 31667 16680]\n",
|
|
"Selected Keys: [1, 0, 1, 4]\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 6
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"### Confirm nKode\n",
|
|
"Submit the set key entry to render the confirm keypad."
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.048762Z",
|
|
"start_time": "2025-03-24T20:25:08.811165Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"confirm_keypad = api.set_nkode(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(passcode_property_indices, confirm_keypad, keypad_size.numb_of_keys)\n",
|
|
"print(f\"Selected Keys\\n{selected_keys_confirm}\")\n",
|
|
"success = api.confirm_nkode(customer_id, selected_keys_confirm, signup_session_id)\n",
|
|
"assert success"
|
|
],
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Key 0: [24 25 26 27 22]\n",
|
|
"Key 1: [18 19 2 3 16]\n",
|
|
"Key 2: [ 6 13 20 15 28]\n",
|
|
"Key 3: [12 1 14 9 4]\n",
|
|
"Key 4: [ 0 7 8 21 10]\n",
|
|
"Selected Keys\n",
|
|
"[0, 4, 3, 3]\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 7
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": "### Inferring an nKode selection"
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.058216Z",
|
|
"start_time": "2025-03-24T20:25:09.055315Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"for idx in range(passcode_len):\n",
|
|
" selected_key_set = selected_keys_set[idx]\n",
|
|
" selected_set_key_idx = set_signup_keypad.reshape(-1, keypad_size.numb_of_keys)[selected_key_set]\n",
|
|
" print(f\"Set Key {idx}: {user_icons[selected_set_key_idx]}\")\n",
|
|
" selected_key_confirm = selected_keys_confirm[idx]\n",
|
|
" selected_confirm_key_idx = confirm_keypad.reshape(-1, keypad_size.numb_of_keys)[selected_key_confirm]\n",
|
|
" print(f\"Confirm Key {idx}: {user_icons[selected_confirm_key_idx]}\")\n",
|
|
" print(f\"Overlapping icon {user_icons[passcode_property_indices[idx]]}\")"
|
|
],
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Set Key 0: ['🐱' '🙃' '😇' '🐻' '🐸']\n",
|
|
"Confirm Key 0: ['🦄' '🌟' '⚡' '🔥' '🐸']\n",
|
|
"Overlapping icon 🐸\n",
|
|
"Set Key 1: ['🥺' '😂' '😱' '🔥' '👽']\n",
|
|
"Confirm Key 1: ['😀' '😡' '😱' '🐻' '🥰']\n",
|
|
"Overlapping icon 😱\n",
|
|
"Set Key 2: ['🐱' '🙃' '😇' '🐻' '🐸']\n",
|
|
"Confirm Key 2: ['🤔' '😂' '😇' '🤯' '🤓']\n",
|
|
"Overlapping icon 😇\n",
|
|
"Set Key 3: ['🤔' '😡' '⚡' '😍' '🍕']\n",
|
|
"Confirm Key 3: ['🤔' '😂' '😇' '🤯' '🤓']\n",
|
|
"Overlapping icon 🤔\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 8
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"## User Cipher\n",
|
|
"\n",
|
|
"Users have 4 cipher keys:\n",
|
|
"1. property_key: The counterpart to the `customer_prop_key`. A user's server-side passcode is composed of elements in `user_prop_key XOR customer_prop_key`.\n",
|
|
"2. pass_key: The passcode key is used to encipher user passcode\n",
|
|
"3. combined_position_key: The combined position key is `user_pos_key XOR customer_pos_key`.\n",
|
|
"4. mask_key: The mask key used to encipher user nKode\n",
|
|
"\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.069308Z",
|
|
"start_time": "2025-03-24T20:25:09.064490Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"from src.user_cipher import UserCipher\n",
|
|
"user_cipher = UserCipher.create(keypad_size, customer.cipher.position_key, customer.nkode_policy.max_nkode_len)\n",
|
|
"user_prop_key_keypad = user_cipher.property_key.reshape(-1, keypad_size.props_per_key)"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": 9
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.087623Z",
|
|
"start_time": "2025-03-24T20:25:09.085495Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": "print(f\"Property Key:\\n{user_prop_key_keypad}\")",
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Property Key:\n",
|
|
"[[ 7202 17463 46638 52425 1136 48374]\n",
|
|
" [13320 30423 16460 16440 54741 60051]\n",
|
|
" [ 7080 35309 40115 5709 22652 59355]\n",
|
|
" [62863 16450 3293 2809 14186 52151]\n",
|
|
" [49175 8694 16139 52942 5446 1365]]\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 10
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.115003Z",
|
|
"start_time": "2025-03-24T20:25:09.112867Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": "print(f\"Passcode Key: {user_cipher.pass_key}\")",
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Passcode Key: [64689 33923 20489 20542 33540 51906 6128 40137 14040 24585]\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 11
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.138929Z",
|
|
"start_time": "2025-03-24T20:25:09.136632Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": "print(f\"Mask Key: {user_cipher.mask_key}\")",
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Mask Key: [57275 32944 34918 33126 19845 13409 47088 47492 20658 16069]\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 12
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.171323Z",
|
|
"start_time": "2025-03-24T20:25:09.169122Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": "print(f\"Combined Position Key: {user_cipher.combined_position_key}\")",
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Combined Position Key: [ 6982 56074 5098 60427 26358 45400]\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 13
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.192115Z",
|
|
"start_time": "2025-03-24T20:25:09.189931Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": "print(f\"User Position Key = combined_pos_key XOR customer_pos_key: {user_cipher.combined_position_key ^ customer.cipher.position_key}\")",
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"User Position Key = combined_pos_key XOR customer_pos_key: [38317 4897 11591 35855 44777 3703]\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 14
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.213042Z",
|
|
"start_time": "2025-03-24T20:25:09.210890Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"position_properties_dict = dict(zip(user_cipher.combined_position_key, user_prop_key_keypad.T))\n",
|
|
"print(f\"Combined Position to Properties Map:\")\n",
|
|
"for pos_val, props in position_properties_dict.items():\n",
|
|
" print(f\"{pos_val}: {props}\")"
|
|
],
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Combined Position to Properties Map:\n",
|
|
"6982: [ 7202 13320 7080 62863 49175]\n",
|
|
"56074: [17463 30423 35309 16450 8694]\n",
|
|
"5098: [46638 16460 40115 3293 16139]\n",
|
|
"60427: [52425 16440 5709 2809 52942]\n",
|
|
"26358: [ 1136 54741 22652 14186 5446]\n",
|
|
"45400: [48374 60051 59355 52151 1365]\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 15
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"#### Encipher Mask\n",
|
|
"1. Get the `padded_passcode_position_indices`; padded with random position indices to equal length `max_nkode_len`.\n",
|
|
"2. Recover the `user_position_key`. Recall `user.cipher.combined_position_key = user_position_key XOR customer.cipher.positon_key`\n",
|
|
"3. Order the `user_position_key` by the `padded_passcode_position_indices`\n",
|
|
"4. Mask the `ordered_user_position_key`\n",
|
|
"5. Base 64 encode the mask"
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.238466Z",
|
|
"start_time": "2025-03-24T20:25:09.236286Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"padded_passcode_position_indices = customer.cipher.get_passcode_position_indices_padded(list(passcode_property_indices), customer.nkode_policy.max_nkode_len)\n",
|
|
"user_position_key = user_cipher.combined_position_key ^ customer.cipher.position_key\n",
|
|
"ordered_user_position_key = user_position_key[padded_passcode_position_indices]\n",
|
|
"mask = ordered_user_position_key ^ user_cipher.mask_key\n",
|
|
"encoded_mask = user_cipher.encode_base64_str(mask)"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": 16
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"#### Encipher Passcode\n",
|
|
"1. Compute `combined_property_key`\n",
|
|
"2. Recover `user_passcode = ordered_combined_proptery_key`; order by passcode_property_indices\n",
|
|
"3. Zero pad `user_pascode`\n",
|
|
"4. Encipher `user_passcode` with `user.cipher.pass_key`\n",
|
|
"5. Hash `ciphered_passcode`"
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.492093Z",
|
|
"start_time": "2025-03-24T20:25:09.259155Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"combined_prop_key = customer.cipher.property_key ^ user_cipher.property_key\n",
|
|
"user_passcode = combined_prop_key[passcode_property_indices]\n",
|
|
"pad_len = customer.nkode_policy.max_nkode_len - passcode_len\n",
|
|
"padded_passcode = np.concatenate((user_passcode, np.zeros(pad_len, dtype=user_passcode.dtype)))\n",
|
|
"ciphered_passcode = padded_passcode ^ 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": 17
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"### User Login\n",
|
|
"1. Get login keypad\n",
|
|
"2. Select keys with passcode icons (in our case, passcode property indices)\n"
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.730838Z",
|
|
"start_time": "2025-03-24T20:25:09.495722Z"
|
|
}
|
|
},
|
|
"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(passcode_property_indices, login_keypad, keypad_size.props_per_key)\n",
|
|
"print(f\"User Passcode: {passcode_property_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"
|
|
],
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Key 0: [ 6 1 8 27 16 11]\n",
|
|
"Key 1: [18 13 14 21 22 17]\n",
|
|
"Key 2: [ 0 25 2 15 4 23]\n",
|
|
"Key 3: [24 19 20 9 10 29]\n",
|
|
"Key 4: [12 7 26 3 28 5]\n",
|
|
"User Passcode: [22, 8, 14, 12]\n",
|
|
"\n",
|
|
"Selected Keys:\n",
|
|
" [1, 0, 1, 4]\n",
|
|
"\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 18
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"## Validate Login Key Entry\n",
|
|
"- decipher user mask and recover nkode position values\n",
|
|
"- get presumed properties from key selection and position values\n",
|
|
"- compare with hash"
|
|
]
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"### Decipher Mask\n",
|
|
"Recover nKode position values:\n",
|
|
"- decode mask from base64 to int\n",
|
|
"- ordered_user_position_key = mask ^ mask_key\n",
|
|
"- user_position_key = user.cipher.co\n",
|
|
"- deduce the set indices"
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.740211Z",
|
|
"start_time": "2025-03-24T20:25:09.737938Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"login_keypad = api.get_login_keypad(username, customer_id)\n",
|
|
"selected_keys_login = select_keys_with_passcode_values(passcode_property_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",
|
|
"ordered_user_position_key = mask ^ user.cipher.mask_key\n",
|
|
"user_position_key = customer.cipher.position_key ^ user.cipher.combined_position_key"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": 19
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"#### Get Presumed Properties\n",
|
|
"- Get the passcode position indices (within the keys)\n",
|
|
"- Get the presumed property indices from the key and position within the key"
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:09.753175Z",
|
|
"start_time": "2025-03-24T20:25:09.751006Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"passcode_position_indices = [int(np.where(user_position_key == pos)[0][0]) for pos in ordered_user_position_key[:passcode_len]]\n",
|
|
"presumed_property_indices = customer.users[username].user_keypad.get_prop_idxs_by_keynumb_setidx(selected_keys_login, passcode_position_indices)\n",
|
|
"assert passcode_property_indices == presumed_property_indices\n"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": 20
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": "### Compare Enciphered Passcodes\n"
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:10.000010Z",
|
|
"start_time": "2025-03-24T20:25:09.768227Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"valid_nkode = user.cipher.compare_nkode(presumed_property_indices, customer.cipher, user.enciphered_passcode.code)\n",
|
|
"assert valid_nkode"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": 21
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"## Renew Properties\n",
|
|
"1. Renew Customer Properties\n",
|
|
"2. Renew User Keys\n",
|
|
"3. Refresh User on Login\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:10.476203Z",
|
|
"start_time": "2025-03-24T20:25:10.003562Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"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\")\n",
|
|
"\n",
|
|
"print(\"Old User Cipher and Mask\")\n",
|
|
"print_user_enciphered_code()\n",
|
|
"api.renew_keys(customer_id) # Steps 1 and 2\n",
|
|
"login_keypad = api.get_login_keypad(username, customer_id)\n",
|
|
"selected_keys_login = select_keys_with_passcode_values(passcode_property_indices, login_keypad, keypad_size.props_per_key)\n",
|
|
"success = api.login(customer_id, username, selected_keys_login) # Step 3\n",
|
|
"print(\"New User Cipher and Mask\")\n",
|
|
"print_user_enciphered_code()\n",
|
|
"assert success"
|
|
],
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Old User Cipher and Mask\n",
|
|
"mask: CAB5nFpQ1LM59xYy2OREr9b7Gcc=, code: $2b$12$oy6qiM687DO5qPkEBTy/V.GXIXYFkfiTmRp1oQEBXbZ10MZMV3V.6\n",
|
|
"\n",
|
|
"New User Cipher and Mask\n",
|
|
"mask: 1oEiOc7ZYxkUkKlVlzNUmbvoc7k=, code: $2b$12$BAKICUuJ.gx39r29krEiu./lWS18zm60dKzfZvpSTDp3LEOzHQGN2\n",
|
|
"\n"
|
|
]
|
|
}
|
|
],
|
|
"execution_count": 22
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"### Renew Customer Keys\n",
|
|
"The customer cipher keys are replaced."
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:10.488540Z",
|
|
"start_time": "2025-03-24T20:25:10.484314Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"old_props = customer.cipher.property_key.copy()\n",
|
|
"old_pos = customer.cipher.position_key.copy()\n",
|
|
"customer.cipher.property_key = np.random.choice(2 ** 16, size=keypad_size.total_props, replace=False)\n",
|
|
"customer.cipher.position_key = np.random.choice(2 ** 16, size=keypad_size.props_per_key, replace=False)\n",
|
|
"new_props = customer.cipher.property_key\n",
|
|
"new_pos = customer.cipher.position_key"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": 23
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"### Renew User\n",
|
|
"User property and position keys go through an intermediate phase.\n",
|
|
"#### user.cipher.combined_position_key\n",
|
|
"- user_combined_position_key = user_combined_position_key XOR pos_xor\n",
|
|
"- user_combined_position_key = (user_position_key XOR old_customer_position_key) XOR (old_customer_position_key XOR new_customer_position_key)\n",
|
|
"- user_combined_position_key = user_position_key XOR new_customer_position_key\n",
|
|
"#### user.cipher.combined_position_key\n",
|
|
"- user_property_key = user_property_key XOR props_xor\n",
|
|
"- user_property_key = user_property_key XOR old_customer_property_key XOR new_customer_property_key\n"
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:10.504982Z",
|
|
"start_time": "2025-03-24T20:25:10.502608Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"props_xor = new_props ^ old_props\n",
|
|
"pos_xor = new_pos ^ old_pos\n",
|
|
"for user in customer.users.values():\n",
|
|
" user.renew = True\n",
|
|
" user.cipher.combined_position_key = user.cipher.combined_position_key ^ pos_xor\n",
|
|
" user.cipher.property_key = user.cipher.property_key ^ props_xor"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": 24
|
|
},
|
|
{
|
|
"metadata": {},
|
|
"cell_type": "markdown",
|
|
"source": [
|
|
"### Refresh User Keys\n",
|
|
"After a user's first successful login, the renew flag is checked. If it's true, the user's cipher is replaced with a new cipher."
|
|
]
|
|
},
|
|
{
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2025-03-24T20:25:10.762626Z",
|
|
"start_time": "2025-03-24T20:25:10.523023Z"
|
|
}
|
|
},
|
|
"cell_type": "code",
|
|
"source": [
|
|
"if user.renew:\n",
|
|
" user.cipher = UserCipher.create(\n",
|
|
" customer.cipher.keypad_size,\n",
|
|
" customer.cipher.position_key,\n",
|
|
" user.cipher.max_nkode_len\n",
|
|
" )\n",
|
|
" user.enciphered_passcode = user.cipher.encipher_nkode(presumed_property_indices, customer.cipher)\n",
|
|
" user.renew = False"
|
|
],
|
|
"outputs": [],
|
|
"execution_count": 25
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 2
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython2",
|
|
"version": "2.7.6"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 0
|
|
}
|