delete docs

This commit is contained in:
2025-03-21 06:32:45 -05:00
parent ba12ae69f4
commit 5031062e87
3 changed files with 0 additions and 626 deletions

View File

View File

@@ -1,422 +0,0 @@
# README
Play around with the code in /notebooks
## Customer Creation
Before creating a user, a customer generates random properties and set
values. The customers manage users. They define an nKode policy, keypad's dimensions,
properties/sets in the keypad, and the frequency of property renewal.
### nKode Policy and Keypad Size
An nKode policy defines:
<ul>
<li>the maximum length of a user's nKode</li>
<li>the minimum length of a user's nKode</li>
<li>the number of unique set values in a user's nKode</li>
<li>the number of unique values in a user's nKode</li>
<li>the number of bytes in an property and set</li>
</ul>
The keypad size defines:
<ul>
<li>the number of keys in the keypad displayed to the user</li>
<li>properties per key</li>
</ul>
To be [dispersion](nkode_concepts.md/#dispersion-resistant-interface) resistant, the number of properties must be greater than the number of keys.
```
api = NKodeAPI()
policy = NKodePolicy(
max_nkode_len=10,
min_nkode_len=4,
distinct_sets=0,
distinct_properties=4,
byte_len=2
)
keypad_size = KeypadSize(
numb_of_keys = {{ keypad_size.numb_of_keys }},
props_per_key = {{ keypad_size.props_per_key }} # aka number of sets
)
customer_id = api.create_new_customer(keypad_size, policy)
customer = api.customers[customer_id]
```
### Customer properties and Sets
A customer has users and defines the properties and set values for all its users.
Since our customer has {{ keypad_size.numb_of_keys }} keys and {{ keypad_size.props_per_key }} properties per key,
this gives a customer interface of {{ keypad_size.numb_of_props }} distinct properties and {{ keypad_size.props_per_key }} distinct property sets.
Each property belongs to one of the {{ keypad_size.props_per_key }} sets. Each property and set value is a unique 2-byte integer in this example.
```
set_vals = customer.cipher.set_key
Customer Sets: {{ customer_set_vals }}
```
```
prop_vals = customer.cipher.prop_key
keypad_view(prop_vals, keypad_size.props_per_key)
Customer properties:
{% for props in customer_prop_view -%}
{{ props }}
{% endfor %}
```
properties organized by set:
```
prop_set_view = matrix_transpose(prop_keypad_view)
set_property_dict = dict(zip(set_vals, prop_set_view))
Set to property Map:
{% for set_val, props in set_property_dict.items() -%}
{{ set_val }} : {{ props }}
{% endfor %}
```
## User Signup
Now that we have a customer, we can create users. To create a new user:
1. Generate a random interface
2. The user sets their nKode and sends their selection to the server
3. The user confirms their nKode. If the user's nKode matches the policy, the server creates the user.
### Random Interface Generation
The user's interface must be dispersable so the server can determine the user's nkode.
The server randomly drops property sets until
the number of properties equals the number of keys, making the interface dispersable.
In our case, the server randomly drops {{ keypad_size.props_per_key - keypad_size.numb_of_keys }} property {{ "sets" if keypad_size.props_per_key - keypad_size.numb_of_keys > 1 else "set" }}.
to give us a {{ keypad_size.numb_of_keys }} X {{ keypad_size.numb_of_keys }} keypad with possible index values ranging from 0-{{ keypad_size.numb_of_props - 1 }}.
Each value in the interface is the index value of a customer property.
The user never learns what their "real" property is. They do not see the index value representing their nKode or
the customer server-side value.
```
session_id, signup_interface = api.generate_index_interface(customer_id)
signup_interface_keypad = list_to_matrix(signup_interface, keypad_size.props_per_key)
Signup Keypad:
{% for key in signup_keypad -%}
Key {{ loop.index }}: {{ key }}
{% endfor %}
```
### Set nKode
The user identifies properties in the interface they want in their nkode. Each property has an index value.
Below, the user has selected `{{ user_passcode }}`. These index values can be represented by anything in the GUI.
The only requirement is that the GUI properties be associated with the same index every time the user logs in.
If users want to change anything about their interface, they must also change their nkode.
```
username = {{ username }}
user_passcode = {{ user_passcode }}
selected_keys_set = select_keys_with_passcode_values(user_passcode, signup_interface, keypad_size.props_per_key)
Selected Keys
{{ selected_keys_set }}
```
The user's passcode server side properties are:
```
server_side_prop = [customer.cipher.prop_key[idx] for idx in user_passcode]
User Passcode Server-side properties: {{ server_side_prop }}
```
### Confirm nKode
The user submits the set interface to the server and receives the _confirm interface_ as a response.
The user finds their nKode again.
```
confirm_interface = api.set_nkode(username, customer_id, selected_keys_set, session_id)
keypad_view(confirm_interface, keypad_size.numb_of_keys)
selected_keys_confirm = select_keys_with_passcode_values(user_passcode, confirm_interface, keypad_size.numb_of_keys)
Confirm Keypad:
{% for key in confirm_keypad -%}
Key {{ loop.index }}: {{ key }}
{% endfor %}
Selected Keys:
{{ selected_keys_confirm }}
```
The user submits their confirmation key selection and the user is created
```
success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id)
```
### Passcode Enciphering, Hashing, and Salting
When a new user creates an nKode, the server caches its set and confirms the interface and the user's key selection.
On the last api.confirm_nkode, the server:
1. Deduces the user's properties
2. Validates the Passcode against the nKodePolicy
3. Creates new User Cipher Keys
4. Enciphers the user's mask
5. Enciphers, salts, and hashes the user's passcode
Steps 1-2 are straightforward. For a better idea of how they work, see pyNKode.
#### User Cipher Keys
##### User Cipher Keys Data Structure
```
set_key = generate_random_nonrepeating_list(keypad_size.props_per_key, max_numb=2**(8*numb_of_bytes))
set_key = xor_lists(set_key, customer_prop.set_vals)
UserCipherKeys(
prop_key=generate_random_nonrepeating_list(keypad_size.props_per_key * keypad_size.numb_of_keys, max_numb=2**(8*numb_of_bytes)),
pass_key=generate_random_nonrepeating_list(max_nkode_len, max_numb=2**(8*numb_of_bytes)),
mask_key=generate_random_nonrepeating_list(max_nkode_len, max_numb=2**(8*numb_of_bytes)),
set_key=set_key,
salt=bcrypt.gensalt(),
max_nkode_len=max_nkode_len
)
```
##### User Cipher Keys Values
```
user_cipher = UserCipherKeys(
prop_key = {{ user_cipher.prop_key }},
pass_key = {{ user_cipher.pass_key }},
mask_key = {{ user_cipher.mask_key }},
set_key = {{ user_cipher.set_key }},
salt = {{ user_cipher.salt }},
max_nkode_len = {{ user_cipher.max_nkode_len }}
)
```
The method UserCipherKeys.encipher_nkode secures a user's nKode in the database. This method is called in api.confirm_nkode
```
class EncipheredNKode(BaseModel):
code: str
mask: str
```
#### Mask Enciphering
Recall:
- set_key_i = (set_rand_numb_i ^ set_val_i)
- mask_key_i = mask_rand_numb_i
- padded_passcode_server_set_i = set_val_i
- len(set_key) == len(mask_key) == (padded_passcode_server_set) == max_nkode_len == 10
where i is the index
- mask_i = mask_key_i ^ padded_passcode_server_set_i ^ set_key_i
- mask_i = mask_rand_num_i ^ set_val_i ^ set_rand_numb_i ^ set_val_i
- mask_i = mask_rand_num_i ^ set_rand_numb_i # set_val_i is cancelled out
```
passcode = {{ user_passcode }}
passcode_server_prop = [customer.cipher.prop_key[idx] for idx in passcode]
passcode_server_set = [customer.cipher.get_prop_set_val(prop) for prop in passcode_server_prop]
Passcode Set Vals: {{ passcode_server_prop }}
Passcode prop Vals: {{ passcode_server_set }}
```
```
padded_passcode_server_set = user_cipher.pad_user_mask(passcode_server_set, customer.nkode_policy.max_nkode_len)
set_idx = [customer.cipher.get_set_index(set_val) for set_val in padded_passcode_server_set]
mask_set_keys = [user_cipher.set_key[idx] for idx in set_idx]
ciphered_mask = xor_lists(mask_set_keys, padded_passcode_server_set)
ciphered_mask = xor_lists(ciphered_mask, user_cipher.mask_key)
mask = user_cipher.encode_base64_str(ciphered_mask)
Mask: {{ enciphered_nkode.mask }}
```
#### Passcode Enciphering and Hashing
- ciphered_customer_prop = prop_key ^ customer_prop
- ciphered_passcode_i = pass_key_i ^ ciphered_customer_prop_i
- code = hash(ciphered_passcode, salt)
```
ciphered_customer_props = xor_lists(customer.cipher.prop_key, user_cipher.prop_key)
passcode_ciphered_props = [ciphered_customer_props[idx] for idx in passcode]
pad_len = customer.nkode_policy.max_nkode_len - passcode_len
passcode_ciphered_props.extend([0 for _ in range(pad_len)])
ciphered_code = xor_lists(passcode_ciphered_props, user_cipher.pass_key)
passcode_bytes = int_array_to_bytes(ciphered_code)
passcode_digest = base64.b64encode(hashlib.sha256(passcode_bytes).digest())
hashed_data = bcrypt.hashpw(passcode_digest, user_cipher.salt)
code = hashed_data.decode("utf-8")
Code: {{ enciphered_nkode.code }}
```
## User Login
To login, a user:
1. Gets login interface
2. Submits key entry
### Get Login Interface
The client requests the user's login interface.
```
login_interface = api.get_login_interface(username, customer_id)
keypad_view(login_interface, keypad_size.props_per_key)
```
The server returns a randomly shuffled interface. Learn more about how the [User Interface Shuffle](nkode_concepts.md/#user-interface-shuffle) works
```
Login Interface Keypad View:
{% for key in login_keypad -%}
Key {{ loop.index }}: {{ key }}
{% endfor %}
```
Recall the user's passcode is `user_passcode = {{ user_passcode }}` so the user selects keys ` selected_keys_login = {{ selected_login_keys }}`
```
success = api.login(customer_id, username, selected_keys_login)
```
### Validate Login Key Entry
- decipher user mask and recover nkode set values
- get presumed property from key selection and set values
- encipher, salt, and hash presumed property values and compare them to the users hashed code
#### Decipher Mask
Recall:
- set_key_i = (set_key_rand_numb_i ^ set_val_i)
- mask_i = mask_key_rand_num_i ^ set_key_rand_numb_i
Recover nKode set values:
- decode mask from base64 to int
- deciphered_mask = mask ^ mask_key
- deciphered_mask_i = set_key_rand_numb # mask_key_rand_num_i is cancelled out
- set_key_rand_component = set_key ^ set_values
- deduce the set value
```
user = customer.users[username]
user_cipher = user.user_cipher
user_mask = user.enciphered_passcode.mask
decoded_mask = user_cipher.decode_base64_str(user_mask)
deciphered_mask = xor_lists(decoded_mask, user_cipher.mask_key)
set_key_rand_component = xor_lists(set_vals, user_cipher.set_key)
passcode_sets = []
for set_cipher in deciphered_mask[:passcode_len]:
set_idx = set_key_rand_component.index(set_cipher)
passcode_sets.append(set_vals[set_idx])
Passcode Sets: {{ login_passcode_sets }}
```
### Get Presumed properties
```
set_vals_idx = [customer.cipher.get_set_index(set_val) for set_val in passcode_sets]
presumed_selected_properties_idx = []
for idx in range(passcode_len):
key_numb = selected_keys_login[idx]
set_idx = set_vals_idx[idx]
selected_prop_idx = customer.users[username].user_interface.get_prop_idx_by_keynumb_setidx(key_numb, set_idx)
presumed_selected_properties_idx.append(selected_prop_idx)
Presumped Passcode: {{ presumed_selected_properties_idx }}
Recall User Passcode: {{ user_passcode }}
```
### Compare Enciphered Passcodes
```
enciphered_nkode = user_cipher.encipher_salt_hash_code(presumed_selected_properties_idx, customer.cipher)
```
If `enciphered_nkode == user.enciphered_passcode.code`, the user's key selection is valid, and the login is successful.
## Renew properties
properties renew is invoked with the renew_properties method: `api.renew_properties(customer_id)`
The renew properties process has three steps:
1. Renew Customer properties
2. Renew User Keys
3. Refresh User on Login
When the customer calls the `renew_properties` method, the method replaces the customer's properties and set values. All its users go through an intermediate
renewal step. The users fully renew after their first successful login. This first login refreshes their keys, salt, and hash with new values.
### Customer Renew
Old Customer properties and set values are cached and copied to variables before renewal.
```
old_sets = customer.cipher.set_key
Customer Sets: {{ customer_set_vals }}
```
```
old_prop = customer.cipher.prop_key
Customer properties:
{% for props in customer_prop_view -%}
{{ props }}
{% endfor %}
```
After the renewal, the customer properties and sets are new randomly generated values.
```
api.renew_properties(customer_id)
set_vals = customer.cipher.set_key
Customer Sets: {{ customer_new_set_vals }}
```
```
prop_vals = customer.cipher.prop_key
Customer properties:
{% for props in customer_new_prop_view -%}
{{ props }}
{% endfor %}
```
### Renew User
During the renewal, each user goes through a temporary transition period.
```
props_xor = xor_lists(new_props, old_props)
sets_xor = xor_lists(new_sets, old_sets)
for user in customer.users.values():
user.renew = True
user.user_cipher.set_key = xor_lists(user.user_cipher.set_key, sets_xor)
user.user_cipher.prop_key = xor_lists(user.user_cipher.prop_key, props_xor)
```
##### User prop Key
The user's prop key was a randomly generated list of length `numb_of_keys * prop_per_key`.
Now each value in the prop key is `prop_key_i = old_prop_key_i ^ new_prop_i ^ old_prop_i`.
Recall in the login process, `ciphered_customer_props = prop_key ^ customer_prop`.
Since the customer_prop is now the new value, it gets canceled out, leaving:
```
new_prop_key = old_prop_key ^ old_prop ^ new_prop
ciphered_customer_props = new_prop_key ^ new_prop
ciphered_customer_props = old_prop_key ^ old_prop # since new_prop cancel out
```
Using the new customer properties, we can validate the user's login attempt with the same hash.
##### User Set Key
The user's set key was a randomly generated list of length `prop_per_key` xor `customer_set_vals`.
The `old_set_vals` have been replaced with the new `new_set_vals`. The deciphering process described above
remains the same.
### User Refresh
Once the user has a successful login, they get a new salt and cipher keys, and their `enciphered_passcode` is recomputed
with the new values.
```
user.user_cipher = UserCipherKeys.new(
customer.cipher.keypad_size,
customer.cipher.set_key,
user.user_cipher.max_nkode_len
)
user.enciphered_passcode = user.user_cipher.encipher_nkode(presumed_selected_properties_idx, customer.cipher)
user.renew = False
```

View File

@@ -1,204 +0,0 @@
import numpy as np
from jinja2 import Environment, FileSystemLoader
import os
from src.nkode_api import NKodeAPI
from src.models import NKodePolicy, KeypadSize, EncipheredNKode
from src.user_cipher import UserCipher
from secrets import choice
from string import ascii_lowercase
import bcrypt
import hashlib
import base64
def random_username() -> str:
return "test_username" + "".join([choice(ascii_lowercase) for _ in range(6)])
def select_keys_with_passcode_values(user_passcode: list[int], keypad: np.ndarray, props_per_key: int) -> list[int]:
indices = [np.where(keypad == prop)[0][0] for prop in user_passcode]
return [int(index // props_per_key) for index in indices]
def visualize_keypad(keypad_list: np.ndarray, props_per_key: int):
print("Keypad View")
keypad_mat = keypad_list.reshape(-1, props_per_key)
for idx, key_vals in enumerate(keypad_mat):
print(f"Key {idx}: {key_vals}")
def render_nkode_authentication(data: dict):
# Set up the Jinja2 environment and template loader
file_loader = FileSystemLoader('')
env = Environment(loader=file_loader)
# Load the template
template = env.get_template('readme_template.md')
print(os.getcwd())
# Render the template with the data
output = template.render(data)
# Print or save the output
# output_file = os.path.expanduser("~/Desktop/nkode_authentication.md")
output_file = '../README.md'
with open(output_file, 'w') as fp:
fp.write(output)
print("File written successfully")
if __name__ == "__main__":
api = NKodeAPI()
policy = NKodePolicy(
max_nkode_len=10,
min_nkode_len=4,
distinct_positions=0,
distinct_properties=4,
byte_len=2,
)
keypad_size = KeypadSize(
numb_of_keys=5,
props_per_key=6 # aka number of sets
)
customer_id = api.create_new_customer(keypad_size, policy)
customer = api.customers[customer_id]
set_vals = customer.cipher.position_key
prop_vals = customer.cipher.property_key
customer_prop_view = prop_vals.reshape(-1, keypad_size.props_per_key)
prop_keypad_view = prop_vals.reshape(-1, keypad_size.props_per_key)
prop_set_view = prop_keypad_view.T
set_property_dict = dict(zip(set_vals, prop_set_view))
session_id, signup_interface = api.generate_signup_keypad(customer_id)
signup_keypad = signup_interface.reshape(-1, keypad_size.numb_of_keys)
username = random_username()
passcode_len = 4
user_passcode = signup_interface[:passcode_len].tolist()
selected_keys_set = select_keys_with_passcode_values(user_passcode, signup_interface, keypad_size.numb_of_keys)
server_side_prop = [customer.cipher.property_key[idx] for idx in user_passcode]
confirm_interface = api.set_nkode(username, customer_id, selected_keys_set, session_id)
confirm_keypad = confirm_interface.reshape(-1, keypad_size.numb_of_keys)
selected_keys_confirm = select_keys_with_passcode_values(user_passcode, confirm_interface, keypad_size.numb_of_keys)
success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id)
assert success
passcode_server_prop = [customer.cipher.property_key[idx] for idx in user_passcode]
passcode_server_set = customer.cipher.get_props_position_vals(user_passcode)
user_keys = customer.users[username].cipher
# TODO: pad_user_mask is deprecated
padded_passcode_server_set = user_keys.pad_user_mask(np.array(passcode_server_set), customer.cipher.position_key)
set_idx = [customer.cipher.get_position_index(set_val) for set_val in padded_passcode_server_set]
mask_set_keys = [user_keys.combined_position_key[idx] for idx in set_idx]
ciphered_mask = mask_set_keys ^ padded_passcode_server_set ^ user_keys.mask_key
mask = user_keys.encode_base64_str(ciphered_mask)
ciphered_customer_props = customer.cipher.property_key ^ user_keys.property_key
passcode_ciphered_props = [ciphered_customer_props[idx] for idx in user_passcode]
pad_len = customer.nkode_policy.max_nkode_len - passcode_len
passcode_ciphered_props.extend([0 for _ in range(pad_len)])
ciphered_code = np.bitwise_xor(passcode_ciphered_props, user_keys.pass_key)
passcode_bytes = ciphered_code.tobytes()
passcode_digest = base64.b64encode(hashlib.sha256(passcode_bytes).digest())
hashed_data = bcrypt.hashpw(passcode_digest, bcrypt.gensalt(rounds=12))
code = hashed_data.decode("utf-8")
enciphered_nkode = EncipheredNKode(
mask=mask,
code=code,
)
"""
USER LOGIN
"""
login_interface = api.get_login_keypad(username, customer_id)
login_keypad = login_interface.reshape(-1, keypad_size.props_per_key)
selected_keys_login = select_keys_with_passcode_values(user_passcode, login_interface, keypad_size.props_per_key)
success = api.login(customer_id, username, selected_keys_login)
assert success
"""
VALIDATE LOGIN KEY ENTRY
DECIPHER MASK
"""
user = customer.users[username]
set_vals = customer.cipher.position_key
user_keys = user.cipher
user_mask = user.enciphered_passcode.mask
decoded_mask = user_keys.decode_base64_str(user_mask)
deciphered_mask = np.bitwise_xor(decoded_mask, user_keys.mask_key)
set_key_rand_component = np.bitwise_xor(set_vals, user_keys.combined_position_key)
login_passcode_sets = []
for set_cipher in deciphered_mask[:passcode_len]:
set_idx = np.where(set_key_rand_component == set_cipher)[0][0]
login_passcode_sets.append(int(set_vals[set_idx]))
"""
GET PRESUMED properties
"""
set_vals_idx = [customer.cipher.get_position_index(set_val) for set_val in login_passcode_sets]
presumed_selected_properties_idx = customer.users[username].user_keypad.get_prop_idxs_by_keynumb_setidx(selected_keys_login, set_vals_idx)
"""
RENEW KEYS
"""
old_props = customer.cipher.property_key.copy()
old_sets = customer.cipher.position_key.copy()
customer.cipher.renew()
new_props = customer.cipher.property_key
new_sets = customer.cipher.position_key
customer_new_prop_view = new_props.reshape(-1, keypad_size.props_per_key)
"""
RENEW USER
"""
props_xor = np.bitwise_xor(new_props, old_props)
sets_xor = np.bitwise_xor(new_sets, old_sets)
for user in customer.users.values():
user.renew = True
user.cipher.combined_position_key = np.bitwise_xor(user.cipher.combined_position_key, sets_xor)
user.cipher.property_key = np.bitwise_xor(user.cipher.property_key, props_xor)
"""
REFRESH USER KEYS
"""
user.cipher = UserCipher.create(
customer.cipher.keypad_size,
customer.cipher.position_key,
user.cipher.max_nkode_len
)
user.enciphered_passcode = user.cipher.encipher_nkode(presumed_selected_properties_idx, customer.cipher)
user.renew = False
# Define some data to pass to the template
data = {
'keypad_size': keypad_size,
'customer_set_vals': set_vals,
'customer_prop_view': customer_prop_view,
'set_property_dict': set_property_dict,
'set_signup_keypad': signup_keypad,
'username': 'test_user',
'passcode_property_indices': user_passcode,
'selected_keys_set': selected_keys_set,
'server_side_prop': server_side_prop,
'confirm_keypad': confirm_keypad,
'selected_keys_confirm': selected_keys_confirm,
'user_cipher': user_keys,
'ordered_customer_prop_key': passcode_server_prop,
'ordered_customer_set_key': passcode_server_set,
'enciphered_nkode': enciphered_nkode,
'login_keypad': login_keypad,
'selected_login_keys': selected_keys_login,
'login_passcode_sets': login_passcode_sets,
'presumed_selected_properties_idx': presumed_selected_properties_idx,
'customer_new_prop_view': customer_new_prop_view,
'customer_new_set_vals': new_sets,
}
render_nkode_authentication(data)