Files
pynkode/notebooks/nkode_tutorial.ipynb
2025-03-20 06:40:54 -05:00

885 lines
28 KiB
Plaintext

{
"cells": [
{
"cell_type": "code",
"source": [
"from src.nkode_api import NKodeAPI\n",
"from src.models import NKodePolicy, KeypadSize\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 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",
" 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-20T10:38:44.577863Z",
"start_time": "2025-03-20T10:38:44.572040Z"
}
},
"outputs": [],
"execution_count": 75
},
{
"cell_type": "code",
"source": [
"api = NKodeAPI()\n",
"user_icons = np.array([\n",
" \"😀\", \"😂\", \"🥳\", \"😍\", \"🤓\",\n",
" \"😎\", \"🥺\", \"😡\", \"😱\", \"🤯\",\n",
" \"🥰\", \"😴\", \"🤔\", \"🙃\", \"😇\",\n",
" \"🤖\", \"👽\", \"👾\", \"🐱\", \"🐶\",\n",
" \"🦁\", \"🐻\", \"🐸\", \"🐙\", \"🦄\",\n",
" \"🌟\", \"⚡\", \"🔥\", \"🍕\", \"🎉\"\n",
"])"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2025-03-20T10:38:44.585583Z",
"start_time": "2025-03-20T10:38:44.582961Z"
}
},
"outputs": [],
"execution_count": 76
},
{
"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": [
"#### 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.\n",
"2. position key: Combined with the user position key to the server-side representation the position in each key.\n"
],
"metadata": {
"collapsed": false
}
},
{
"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}\")"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2025-03-20T10:38:44.601079Z",
"start_time": "2025-03-20T10:38:44.595017Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Customer Position Key: [10895 31772 47823 53466 56263 49352]\n",
"Customer Properties Key:\n",
"[32913 31208 39571 1116 2737 19900]\n",
"[ 4026 23392 64571 25864 56877 34756]\n",
"[56837 8582 51951 34890 37611 61978]\n",
"[55074 11623 3931 21342 53702 21700]\n",
"[26922 1472 49420 42668 7254 41918]\n",
"Position to Properties Map:\n",
"10895: [32913 4026 56837 55074 26922]\n",
"31772: [31208 23392 8582 11623 1472]\n",
"47823: [39571 64571 51951 3931 49420]\n",
"53466: [ 1116 25864 34890 21342 42668]\n",
"56263: [ 2737 56877 37611 53702 7254]\n",
"49352: [19900 34756 61978 21700 41918]\n"
]
}
],
"execution_count": 77
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-20T10:38:44.612692Z",
"start_time": "2025-03-20T10:38:44.610267Z"
}
},
"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",
"10895: ['😀' '🥺' '🤔' '🐱' '🦄']\n",
"31772: ['😂' '😡' '🙃' '🐶' '🌟']\n",
"47823: ['🥳' '😱' '😇' '🦁' '⚡']\n",
"53466: ['😍' '🤯' '🤖' '🐻' '🔥']\n",
"56263: ['🤓' '🥰' '👽' '🐸' '🍕']\n",
"49352: ['😎' '😴' '👾' '🐙' '🎉']\n"
]
}
],
"execution_count": 78
},
{
"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 what their server-side properties"
]
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-20T10:38:44.636348Z",
"start_time": "2025-03-20T10:38:44.630660Z"
}
},
"cell_type": "code",
"source": [
"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": [
"<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: [18 25 15 4 29]\n",
"Key 1: [ 6 13 27 28 5]\n",
"Key 2: [24 19 9 16 23]\n",
"Key 3: [ 0 1 3 10 11]\n",
"Key 4: [12 7 21 22 17]\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: [55074 1472 34890 2737 41918]\n",
"Key 1: [ 4026 8582 42668 7254 19900]\n",
"Key 2: [26922 11623 25864 37611 21700]\n",
"Key 3: [32913 31208 1116 56877 34756]\n",
"Key 4: [56837 23392 21342 53702 61978]\n"
]
}
],
"execution_count": 79
},
{
"metadata": {},
"cell_type": "markdown",
"source": [
"## Set nKode\n",
"The client receives `user_icons`, `set_signup_keypad`\n"
]
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-20T10:38:44.674025Z",
"start_time": "2025-03-20T10:38:44.671302Z"
}
},
"cell_type": "code",
"source": [
"username = random_username()\n",
"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: [28, 13, 9, 10]\n",
"User Passcode Icons: ['🍕' '🙃' '🤯' '🥰']\n",
"User Passcode Server-side properties: [ 7254 8582 25864 56877]\n",
"Selected Keys: [1, 1, 2, 3]\n"
]
}
],
"execution_count": 80
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-20T10:38:44.700525Z",
"start_time": "2025-03-20T10:38:44.697597Z"
}
},
"cell_type": "code",
"source": [
"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(passcode_property_indices, confirm_keypad, keypad_size.numb_of_keys)\n",
"print(f\"Selected Keys\\n{selected_keys_confirm}\")"
],
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Key 0: [ 6 7 3 4 23]\n",
"Key 1: [24 1 15 28 17]\n",
"Key 2: [12 25 27 16 11]\n",
"Key 3: [ 0 13 9 22 29]\n",
"Key 4: [18 19 21 10 5]\n",
"Selected Keys\n",
"[1, 3, 3, 4]\n"
]
}
],
"execution_count": 81
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-20T10:38:44.947744Z",
"start_time": "2025-03-20T10:38:44.709897Z"
}
},
"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, signup_session_id)\n",
"assert success"
],
"outputs": [],
"execution_count": 82
},
{
"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-20T10:38:44.959614Z",
"start_time": "2025-03-20T10:38:44.954840Z"
}
},
"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": 83
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-20T10:38:44.968863Z",
"start_time": "2025-03-20T10:38:44.966571Z"
}
},
"cell_type": "code",
"source": "print(f\"Property Key:\\n{user_prop_key_keypad}\")",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Property Key:\n",
"[[53739 14349 64971 50952 51008 37461]\n",
" [45744 17444 29958 27397 60694 8069]\n",
" [43064 47943 49025 5623 3145 4890]\n",
" [ 1128 11012 53093 14232 26108 5391]\n",
" [59341 47378 48497 6840 34437 29587]]\n"
]
}
],
"execution_count": 84
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-20T10:38:44.985451Z",
"start_time": "2025-03-20T10:38:44.983449Z"
}
},
"cell_type": "code",
"source": "print(f\"Passcode Key: {user_cipher.pass_key}\")",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Passcode Key: [16245 48001 4534 55258 54613 15211 33171 56565 33961 50654]\n"
]
}
],
"execution_count": 85
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-20T10:38:45.007753Z",
"start_time": "2025-03-20T10:38:45.005712Z"
}
},
"cell_type": "code",
"source": "print(f\"Mask Key: {user_cipher.mask_key}\")",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Mask Key: [52084 24514 63626 6657 19669 39430 35626 25229 14824 63798]\n"
]
}
],
"execution_count": 86
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-20T10:38:45.026885Z",
"start_time": "2025-03-20T10:38:45.024649Z"
}
},
"cell_type": "code",
"source": "print(f\"Combined Position Key: {user_cipher.combined_position_key}\")",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Combined Position Key: [10235 12456 898 54650 50445 20719]\n"
]
}
],
"execution_count": 87
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-20T10:38:45.041101Z",
"start_time": "2025-03-20T10:38:45.038985Z"
}
},
"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: [ 3444 19636 47437 1440 7882 36903]\n"
]
}
],
"execution_count": 88
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-20T10:38:45.059433Z",
"start_time": "2025-03-20T10:38:45.056942Z"
}
},
"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",
"10235: [53739 45744 43064 1128 59341]\n",
"12456: [14349 17444 47943 11012 47378]\n",
"898: [64971 29958 49025 53093 48497]\n",
"54650: [50952 27397 5623 14232 6840]\n",
"50445: [51008 60694 3145 26108 34437]\n",
"20719: [37461 8069 4890 5391 29587]\n"
]
}
],
"execution_count": 89
},
{
"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-20T10:38:45.070036Z",
"start_time": "2025-03-20T10:38:45.068009Z"
}
},
"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": 90
},
{
"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-20T10:38:45.320480Z",
"start_time": "2025-03-20T10:38:45.087096Z"
}
},
"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": 91
},
{
"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-20T10:38:45.559945Z",
"start_time": "2025-03-20T10:38:45.326214Z"
}
},
"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: [18 25 14 15 4 29]\n",
"Key 1: [ 6 13 20 27 28 5]\n",
"Key 2: [24 19 26 9 16 23]\n",
"Key 3: [ 0 1 8 3 10 11]\n",
"Key 4: [12 7 2 21 22 17]\n",
"User Passcode: [28, 13, 9, 10]\n",
"\n",
"Selected Keys:\n",
" [1, 1, 2, 3]\n",
"\n"
]
}
],
"execution_count": 92
},
{
"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-20T10:38:45.570140Z",
"start_time": "2025-03-20T10:38:45.567396Z"
}
},
"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\n"
],
"outputs": [],
"execution_count": 93
},
{
"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": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"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"
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "### Compare Enciphered Passcodes\n"
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-20T10:38:45.809575Z",
"start_time": "2025-03-20T10:38:45.576610Z"
}
},
"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": 94
},
{
"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-20T10:38:46.286081Z",
"start_time": "2025-03-20T10:38:45.816688Z"
}
},
"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: y8aMd+5qYcPVLETTcs2aHqx2hhk=, code: $2b$12$wMi7WGmlch8kWMYJ2v.FHOne1.YSQPqKU/itpBuycwSFyasryF/2u\n",
"\n",
"New User Cipher and Mask\n",
"mask: aln5Su79utoVmqQXNCjYiUdwVYw=, code: $2b$12$gQf3UVa3cWMBy0CO0sBLyuJzdXGzg3qpNFMTD6MvycuR6N3gLFdgC\n",
"\n"
]
}
],
"execution_count": 95
},
{
"metadata": {},
"cell_type": "markdown",
"source": [
"### Renew Customer Keys\n",
"The customer cipher keys are replaced."
]
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-20T10:38:46.296985Z",
"start_time": "2025-03-20T10:38:46.293584Z"
}
},
"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": 96
},
{
"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-20T10:38:46.312031Z",
"start_time": "2025-03-20T10:38:46.310019Z"
}
},
"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": 97
},
{
"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-20T10:38:46.553807Z",
"start_time": "2025-03-20T10:38:46.318482Z"
}
},
"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": 98
}
],
"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
}