1 Commits

Author SHA1 Message Date
ccf1bac337 update documentation 2025-12-03 11:36:43 -06:00
14 changed files with 1235 additions and 266 deletions

1
.obsidian/app.json vendored Normal file
View File

@@ -0,0 +1 @@
{}

1
.obsidian/appearance.json vendored Normal file
View File

@@ -0,0 +1 @@
{}

31
.obsidian/core-plugins.json vendored Normal file
View File

@@ -0,0 +1,31 @@
{
"file-explorer": true,
"global-search": true,
"switcher": true,
"graph": true,
"backlink": true,
"canvas": true,
"outgoing-link": true,
"tag-pane": true,
"properties": false,
"page-preview": true,
"daily-notes": true,
"templates": true,
"note-composer": true,
"command-palette": true,
"slash-command": false,
"editor-status": true,
"bookmarks": true,
"markdown-importer": false,
"zk-prefixer": false,
"random-note": false,
"outline": true,
"word-count": true,
"slides": false,
"audio-recorder": false,
"workspaces": false,
"file-recovery": true,
"publish": false,
"sync": true,
"webviewer": false
}

166
.obsidian/workspace.json vendored Normal file
View File

@@ -0,0 +1,166 @@
{
"main": {
"id": "2d317d71045425f3",
"type": "split",
"children": [
{
"id": "51f85f0ef7594aef",
"type": "tabs",
"children": [
{
"id": "60d702f2c5c78820",
"type": "leaf",
"state": {
"type": "empty",
"state": {},
"icon": "lucide-file",
"title": "New tab"
}
}
]
}
],
"direction": "vertical"
},
"left": {
"id": "933955ad281257d5",
"type": "split",
"children": [
{
"id": "348366d432902e79",
"type": "tabs",
"children": [
{
"id": "58e50c4fbb960df6",
"type": "leaf",
"state": {
"type": "file-explorer",
"state": {
"sortOrder": "alphabetical",
"autoReveal": false
},
"icon": "lucide-folder-closed",
"title": "Files"
}
},
{
"id": "db056d2cd1bd4a5b",
"type": "leaf",
"state": {
"type": "search",
"state": {
"query": "",
"matchingCase": false,
"explainSearch": false,
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical"
},
"icon": "lucide-search",
"title": "Search"
}
},
{
"id": "766ff21acbbfb952",
"type": "leaf",
"state": {
"type": "bookmarks",
"state": {},
"icon": "lucide-bookmark",
"title": "Bookmarks"
}
}
]
}
],
"direction": "horizontal",
"width": 300
},
"right": {
"id": "666e4843944fb168",
"type": "split",
"children": [
{
"id": "7fae59c3dd546dfc",
"type": "tabs",
"children": [
{
"id": "5a06b50e5b947981",
"type": "leaf",
"state": {
"type": "backlink",
"state": {
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
"showSearch": false,
"searchQuery": "",
"backlinkCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-coming-in",
"title": "Backlinks"
}
},
{
"id": "57089c89592d341c",
"type": "leaf",
"state": {
"type": "outgoing-link",
"state": {
"linksCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-going-out",
"title": "Outgoing links"
}
},
{
"id": "648806ec4584fa8d",
"type": "leaf",
"state": {
"type": "tag",
"state": {
"sortOrder": "frequency",
"useHierarchy": true,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-tags",
"title": "Tags"
}
},
{
"id": "490ac34f60740511",
"type": "leaf",
"state": {
"type": "outline",
"state": {
"followCursor": false,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-list",
"title": "Outline"
}
}
]
}
],
"direction": "horizontal",
"width": 300,
"collapsed": true
},
"left-ribbon": {
"hiddenItems": {
"switcher:Open quick switcher": false,
"graph:Open graph view": false,
"canvas:Create new canvas": false,
"daily-notes:Open today's daily note": false,
"templates:Insert template": false,
"command-palette:Open command palette": false
}
},
"active": "60d702f2c5c78820",
"lastOpenFiles": []
}

View File

@@ -0,0 +1,207 @@
# Encipher, Decipher and Renew nKode
## Tenant Policy
- max nkode length: 10
- number of keys: 6
- properties per key: 9
- total number of properties: 54
---
## Deterministic CSPRNG
- AES-CTR DRBG
- ChaCha20
## User Cipher Keys
Derive keys in memory from PRNG above:
- property key: [ 6890 54130 42240 40467 46502 31074 10598 63689 27697 54461 21116 31999
10698 14870 50779 48637 29314 33075 52993 42014 2837 1935 34274 63380
36021 26329 20788 39848 7335 2619 61516 61122 39878 32506 19151 6611
2803 10730 53682 39987 11998 42378 6081 8624 34336 15222 35632 33233
4072 53750 54671 63845 2770 43728]
- passcode key: [10632 49355 48031 9925 15082 24190 5137 14304 24524 16141]
- user position key: [45632 9012 27470 28203 15901 7554 16974 54240 53827]
- mask key: [ 8177 54825 26281 51895 8940 16695 19756 63041 7376 54396]
---
## User Keypad
- keypad example:<br/>
- user passcode indices: [48 12 7 36]
---
## nKode Cipher
### Passcode Hash
```mermaid
block-beta
columns 2
prop["user_property_key\n[ 6890 54130 42240 40467 46502 31074 10598 63689 27697 54461 21116 31999
10698 14870 50779 48637 29314 33075 52993 42014 2837 1935 34274 63380
36021 26329 20788 39848 7335 2619 61516 61122 39878 32506 19151 6611
2803 10730 53682 39987 11998 42378 6081 8624 34336 15222 35632 33233
4072 53750 54671 63845 2770 43728]"]
pass["user_passcode_indices\n[48 12 7 36]"]
space:2
sel(("select\nproperties")):2
pass --> sel
prop --> sel
space:2
passcode["user passcode properties:\n[ 4072 10698 63689 2803]"]:2
sel --> passcode
space:2
pad["zero pad to\nmax nkode length: 10"]:2
passcode -->pad
space:2
paddedpasscode["padded passcode:\n[ 4072 10698 63689 2803 0 0 0 0 0 0]"]
pad --> paddedpasscode
passkey["passcode key:\n[10632 49355 48031 9925 15082 24190 5137 14304 24524 16141]"]
space:2
xor2(("XOR")):2
passkey --> xor2
paddedpasscode --> xor2
space:2
cipheredpass["ciphered passcode:\n[ 9824 59649 17238 11318 15082 24190 5137 14304 24524 16141]"]:2
xor2 --> cipheredpass
space:2
hash(("hash")):2
cipheredpass --> hash
space:2
cipheredhashed["hashed ciphered passcode:\n$2b$12$XcXlcNKMyXQziv.kQWKngO88KUcm.xrn4YRZOOhGgIyMmpMw7NPJa"]:2
hash --> cipheredhashed
```
### Mask Encipher
```mermaid
block-beta
columns 2
passcode_idx["passcode indices:\n[48 12 7 36]"]
space:3
propidx(["Get Position Idx:\nmap each to element mod props_per_key"])
passcode_idx-->propidx
space:3
passcode_position_idx["passcode poition indices:\n[3, 3, 7, 0]"]
propidx --> passcode_position_idx
space:3
pad1(("Pad with\nrandom indices"))
passcode_position_idx --> pad1
space:3
posidx["Padded Passcode Position Indices:\n[3, 3, 7, 0, 0, 0, 5, 8, 8, 8]"]
pad1 --> posidx
user_pos["user position key:\n[45632 9012 27470 28203 15901 7554 16974 54240 53827]"]
space:2
sel(("select positions"))
user_pos --> sel
posidx --> sel
space:3
passcode_pos["ordered user passcode positions:\n[28203 28203 54240 45632 45632 45632 7554 53827 53827 53827]"]
sel --> passcode_pos
mask_key["mask key\n[ 8177 54825 26281 51895 8940 16695 19756 63041 7376 54396]"]
space:2
xor2(("XOR"))
mask_key --> xor2
passcode_pos --> xor2
space:3
mask["enciphered mask:\n cdq4ArVJePcB2PN3D2LrwyLN6mE="]
xor2 --> mask
```
### Validate nKode
```mermaid
block-beta
columns 3
selected_keys["keys selected by user during login:\n[1, 3, 1, 4]"]
login_keypad["login keypad:\nKey 0: [ 9 28 2 39 49 32 42 34 44]
Key 1: [ 0 1 20 48 40 50 51 7 35]
Key 2: [18 19 47 21 13 14 24 25 8]
Key 3: [27 10 11 12 4 5 6 16 17]
Key 4: [36 37 38 3 22 23 33 43 53]
Key 5: [45 46 29 30 31 41 15 52 26]
"]
space:4
selectkeys(("filter keys"))
mask["enciphered mask:\n cdq4ArVJePcB2PN3D2LrwyLN6mE="]
mask_key["mask key:\n[ 8177 54825 26281 51895 8940 16695 19756 63041 7376 54396]"]
space:2
xor1(("XOR"))
mask --> xor1
mask_key --> xor1
selected_keys --> selectkeys
login_keypad --> selectkeys
space:3
ordered_keys["ordered keys:\n[[ 0 1 20 48 40 50 51 7 35]
[27 10 11 12 4 5 6 16 17]
[ 0 1 20 48 40 50 51 7 35]
[36 37 38 3 22 23 33 43 53]]"]
user_position_key["user position key:\n[45632 9012 27470 28203 15901 7554 16974 54240 53827]"]
passcode_pos["ordered user passcode positions:\n[28203 28203 54240 45632 45632 45632 7554 53827 53827 53827]"]
selectkeys --> ordered_keys
xor1 --> passcode_pos
space:8
get_passcode_idxs(("recover passcode\nposition indices"))
user_position_key --> get_passcode_idxs
passcode_pos --> get_passcode_idxs
space:8
passcode_pos_idxs["padded passcode position indices:\n[3, 3, 7, 0, 0, 0, 5, 8, 8, 8]"]
get_passcode_idxs --> passcode_pos_idxs
space:3
get_presumed_idxs(("recover passcode\nproperty indices"))
ordered_keys --> get_presumed_idxs
passcode_pos_idxs --> get_presumed_idxs
space:5
passcode_prop_idxs["presumed passcode property indices:\n[48 12 7 36]"]
prop["user_property_key\n[ 6890 54130 42240 40467 46502 31074 10598 63689 27697 54461 21116 31999
10698 14870 50779 48637 29314 33075 52993 42014 2837 1935 34274 63380
36021 26329 20788 39848 7335 2619 61516 61122 39878 32506 19151 6611
2803 10730 53682 39987 11998 42378 6081 8624 34336 15222 35632 33233
4072 53750 54671 63845 2770 43728]"]
cipheredhashed["hashed ciphered passcode:\n$2b$12$XcXlcNKMyXQziv.kQWKngO88KUcm.xrn4YRZOOhGgIyMmpMw7NPJa"]
get_presumed_idxs --> passcode_prop_idxs
space:3
sel(("select\nproperties"))
passcode_prop_idxs --> sel
prop --> sel
space:5
passcode_prop["presumed passcode properties:\n[ 4072 10698 63689 2803]"]
sel --> passcode_prop
space:5
cipher(("encipher"))
passcode_prop --> cipher
space:5
cipheredpass["ciphered passcode:\n[ 9824 59649 17238 11318 15082 24190 5137 14304 24524 16141]"]
cipher --> cipheredpass
space:7
comp{"compare"}
cipheredpass --> comp
cipheredhashed --> comp
space:5
suc(("success"))
comp --"Equal"--> suc
```
## Renew
A renewal happens every login
### Nonce Renew
With ChaCha20, we can renew the keys and hash every login with a new nonce
### Secret Renew
The secret is renewed less frequently. It's stored securely using a service like AWS Secrets Manager.

View File

@@ -0,0 +1,69 @@
from pathlib import Path
import numpy as np
from docs.scripts.utils import render_markdown_template
from src.models import NKodePolicy, KeypadSize
from src.user_keypad import UserKeypad
from src.nkode_cipher_v2.nkode_cipher import NKodeCipher
from src.utils import select_keys_with_passcode_values
def display_keypad(icons_array: np.ndarray, props_per_key: int) -> str:
icons = ""
for idx, row in enumerate(icons_array.reshape(-1, props_per_key)):
icons += f"Key {idx}: "
icons += str(row)
icons += "\n"
return icons
if __name__ == "__main__":
policy = NKodePolicy(
max_nkode_len=10,
min_nkode_len=4,
distinct_positions=0,
distinct_properties=4,
)
keypad_size = KeypadSize(
numb_of_keys=6,
props_per_key=9
)
user_keys = NKodeCipher.create(keypad_size=keypad_size, max_nkode_len=policy.max_nkode_len)
passcode_len = 4
passcode_property_indices = np.random.choice([i for i in range(keypad_size.total_props)], size=passcode_len,
replace=False)
user_passcode = user_keys.property_key[passcode_property_indices]
pad_len = policy.max_nkode_len - passcode_len
padded_passcode = np.concatenate((user_passcode, np.zeros(pad_len, dtype=user_passcode.dtype)))
ciphered_passcode = padded_passcode ^ user_keys.pass_key
cipher = user_keys.encipher_nkode(passcode_property_indices.tolist())
padded_passcode_position_indices = user_keys.get_passcode_position_indices_padded(
passcode_property_indices.tolist())
ordered_user_position_key = user_keys.position_key[padded_passcode_position_indices]
login_keypad = UserKeypad.create(keypad_size)
selected_keys_login = select_keys_with_passcode_values(passcode_property_indices, login_keypad.keypad,
keypad_size.props_per_key)
context = {
"max_nkode_len": policy.max_nkode_len,
"numb_of_keys": keypad_size.numb_of_keys,
"props_per_key": keypad_size.props_per_key,
"user_property_key": user_keys.property_key,
"pass_key": user_keys.pass_key,
"user_position_key": user_keys.position_key,
"mask_key": user_keys.mask_key,
"user_passcode_idxs": passcode_property_indices,
"user_passcode_props": user_passcode,
"padded_passcode": padded_passcode,
"ciphered_passcode": ciphered_passcode,
"code": cipher.code,
"passcode_position_idxs": padded_passcode_position_indices[:passcode_len],
"pad_user_passcode_idxs": padded_passcode_position_indices,
"ordered_user_position_key": ordered_user_position_key,
"mask": cipher.mask,
"login_keypad": display_keypad(login_keypad.keypad, keypad_size.props_per_key),
"selected_keys": selected_keys_login,
"ordered_keys": login_keypad.keypad.reshape(-1, keypad_size.props_per_key)[selected_keys_login],
}
render_markdown_template(Path("../templates/encipher_decipher_renew_nkode_v2.template.md"),
Path("../encipher_decipher_renew_nkode_v2.md"), context)

View File

@@ -0,0 +1,70 @@
from pathlib import Path
import numpy as np
from docs.scripts.utils import render_markdown_template
from src.models import NKodePolicy, KeypadSize
from src.user_keypad import UserKeypad
from src.nkode_cipher_v2.nkode_cipher import NKodeCipher
from src.utils import select_keys_with_passcode_values
def display_keypad(icons_array: np.ndarray, props_per_key: int) -> str:
icons = ""
for idx, row in enumerate(icons_array.reshape(-1, props_per_key)):
icons += f"Key {idx}: "
icons += str(row)
icons += "\n"
return icons
if __name__ == "__main__":
policy = NKodePolicy(
max_nkode_len=10,
min_nkode_len=4,
distinct_positions=0,
distinct_properties=4,
)
keypad_size = KeypadSize(
numb_of_keys=6,
props_per_key=9
)
user_keys = NKodeCipher.create(keypad_size=keypad_size, max_nkode_len=policy.max_nkode_len)
passcode_len = 4
passcode_property_indices = np.random.choice([i for i in range(keypad_size.total_props)], size=passcode_len,
replace=False)
user_passcode = user_keys.property_key[passcode_property_indices]
pad_len = policy.max_nkode_len - passcode_len
padded_passcode = np.concatenate((user_passcode, np.zeros(pad_len, dtype=user_passcode.dtype)))
ciphered_passcode = padded_passcode ^ user_keys.pass_key
cipher = user_keys.encipher_nkode(passcode_property_indices.tolist())
padded_passcode_position_indices = user_keys.get_passcode_position_indices_padded(
passcode_property_indices.tolist())
ordered_user_position_key = user_keys.position_key[padded_passcode_position_indices]
login_keypad = UserKeypad.create(keypad_size)
selected_keys_login = select_keys_with_passcode_values(passcode_property_indices, login_keypad.keypad,
keypad_size.props_per_key)
context = {
"max_nkode_len": policy.max_nkode_len,
"numb_of_keys": keypad_size.numb_of_keys,
"props_per_key": keypad_size.props_per_key,
"user_property_key": user_keys.property_key,
"pass_key": user_keys.pass_key,
"user_position_key": user_keys.position_key,
"mask_key": user_keys.mask_key,
"user_passcode_idxs": passcode_property_indices,
"user_passcode_props": user_passcode,
"padded_passcode": padded_passcode,
"ciphered_passcode": ciphered_passcode,
"code": cipher.code,
"passcode_position_idxs": padded_passcode_position_indices[:passcode_len],
"pad_user_passcode_idxs": padded_passcode_position_indices,
"ordered_user_position_key": ordered_user_position_key,
"mask": cipher.mask,
"login_keypad": display_keypad(login_keypad.keypad, keypad_size.props_per_key),
"selected_keys": selected_keys_login,
"ordered_keys": login_keypad.keypad.reshape(-1, keypad_size.props_per_key)[selected_keys_login],
}
render_markdown_template(Path("../templates/zero_trust_nkode.template.md"),
Path("../zero_trust_nkode.md"), context)

View File

@@ -0,0 +1,187 @@
# Encipher, Decipher and Renew nKode
## Tenant Policy
- max nkode length: {{ max_nkode_len }}
- number of keys: {{ numb_of_keys }}
- properties per key: {{ props_per_key }}
- total number of properties: {{ numb_of_keys * props_per_key }}
---
## Deterministic CSPRNG
- AES-CTR DRBG
- ChaCha20
## User Cipher Keys
Derive keys in memory from PRNG above:
- property key: {{ user_property_key }}
- passcode key: {{ pass_key }}
- user position key: {{ user_position_key }}
- mask key: {{ mask_key }}
---
## User Keypad
- keypad example:<br/>{{ login_keypad_md }}
- user passcode indices: {{ user_passcode_idxs}}
---
## nKode Cipher
### Passcode Hash
```mermaid
block-beta
columns 2
prop["user_property_key\n{{user_property_key}}"]
pass["user_passcode_indices\n{{user_passcode_idxs}}"]
space:2
sel(("select\nproperties")):2
pass --> sel
prop --> sel
space:2
passcode["user passcode properties:\n{{user_passcode_props}}"]:2
sel --> passcode
space:2
pad["zero pad to\nmax nkode length: {{max_nkode_len}}"]:2
passcode -->pad
space:2
paddedpasscode["padded passcode:\n{{padded_passcode}}"]
pad --> paddedpasscode
passkey["passcode key:\n{{pass_key}}"]
space:2
xor2(("XOR")):2
passkey --> xor2
paddedpasscode --> xor2
space:2
cipheredpass["ciphered passcode:\n{{ciphered_passcode}}"]:2
xor2 --> cipheredpass
space:2
hash(("hash")):2
cipheredpass --> hash
space:2
cipheredhashed["hashed ciphered passcode:\n{{code}}"]:2
hash --> cipheredhashed
```
### Mask Encipher
```mermaid
block-beta
columns 2
passcode_idx["passcode indices:\n{{user_passcode_idxs}}"]
space:3
propidx(["Get Position Idx:\nmap each to element mod props_per_key"])
passcode_idx-->propidx
space:3
passcode_position_idx["passcode poition indices:\n{{passcode_position_idxs}}"]
propidx --> passcode_position_idx
space:3
pad1(("Pad with\nrandom indices"))
passcode_position_idx --> pad1
space:3
posidx["Padded Passcode Position Indices:\n{{pad_user_passcode_idxs}}"]
pad1 --> posidx
user_pos["user position key:\n{{user_position_key}}"]
space:2
sel(("select positions"))
user_pos --> sel
posidx --> sel
space:3
passcode_pos["ordered user passcode positions:\n{{ordered_user_position_key}}"]
sel --> passcode_pos
mask_key["mask key\n{{mask_key}}"]
space:2
xor2(("XOR"))
mask_key --> xor2
passcode_pos --> xor2
space:3
mask["enciphered mask:\n {{mask}}"]
xor2 --> mask
```
### Validate nKode
```mermaid
block-beta
columns 3
selected_keys["keys selected by user during login:\n{{selected_keys}}"]
login_keypad["login keypad:\n{{login_keypad}}"]
space:4
selectkeys(("filter keys"))
mask["enciphered mask:\n {{mask}}"]
mask_key["mask key:\n{{mask_key}}"]
space:2
xor1(("XOR"))
mask --> xor1
mask_key --> xor1
selected_keys --> selectkeys
login_keypad --> selectkeys
space:3
ordered_keys["ordered keys:\n{{ordered_keys}}"]
user_position_key["user position key:\n{{user_position_key}}"]
passcode_pos["ordered user passcode positions:\n{{ordered_user_position_key}}"]
selectkeys --> ordered_keys
xor1 --> passcode_pos
space:8
get_passcode_idxs(("recover passcode\nposition indices"))
user_position_key --> get_passcode_idxs
passcode_pos --> get_passcode_idxs
space:8
passcode_pos_idxs["padded passcode position indices:\n{{pad_user_passcode_idxs}}"]
get_passcode_idxs --> passcode_pos_idxs
space:3
get_presumed_idxs(("recover passcode\nproperty indices"))
ordered_keys --> get_presumed_idxs
passcode_pos_idxs --> get_presumed_idxs
space:5
passcode_prop_idxs["presumed passcode property indices:\n{{user_passcode_idxs}}"]
prop["user_property_key\n{{user_property_key}}"]
cipheredhashed["hashed ciphered passcode:\n{{code}}"]
get_presumed_idxs --> passcode_prop_idxs
space:3
sel(("select\nproperties"))
passcode_prop_idxs --> sel
prop --> sel
space:5
passcode_prop["presumed passcode properties:\n{{user_passcode_props}}"]
sel --> passcode_prop
space:5
cipher(("encipher"))
passcode_prop --> cipher
space:5
cipheredpass["ciphered passcode:\n{{ciphered_passcode}}"]
cipher --> cipheredpass
space:7
comp{"compare"}
cipheredpass --> comp
cipheredhashed --> comp
space:5
suc(("success"))
comp --"Equal"--> suc
```
## Renew
A renewal happens every login
### Nonce Renew
With ChaCha20, we can renew the keys and hash every login with a new nonce
### Secret Renew
The secret is renewed less frequently. It's stored securely using a service like AWS Secrets Manager.

View File

@@ -0,0 +1,43 @@
# Zero Trust nKode with aPAKE (OPAQUE)
```mermaid
sequenceDiagram
participant Client
participant Server
Note over Client, Server: Enrollment
Client ->> Server: Signup Session: email
Client ->> Client: Create 128-bit Secret Key
Note left of Client: Request user stores Secret Key in a safe place
Client ->> Server: OPAQUE Register with Secret Key<br/>https://github.com/facebook/opaque-ke
Client ->> Server: OPAQUE Login with email + Secret Key
opt Secret Key OPAQUE tunnel
Client ->> Server: Get New Icons
Server -->> Client: icons
Note left of Client: Icons are stored on Client
Note left of Client: well-known nonce: 0x1 (or any number)
Client ->> Client: Assign random names to icons from<br/>secret_key and well known nonce
Client ->> Server: list of random icon names
Note right of Server: Only a client with the secret key can request these icons.<br/>Server doesn't know the owner
loop assign icons
Client ->> Client: Regenerate 4-6 icons until user accepts them
end
Client ->> Client: Create new nonce
Client ->> Client: ChaCha20 key derivation (pass_key, mask_key, prop_key, pos_key)
Client ->> Client: Compute Mask
Note left of Client: User Password is concat([list of assigned icon values])
Client ->> Server: OPAQUE Register with User Password + nonce, mask
end
Note over Client, Server: Login
Client ->> Server: OPAQUE Login with email + Secret Key
opt Secret Key OPAQUE tunnel
Server ->> Client: nonce, mask
Client ->> Client: Display Keypad to User<br/>User makes key selection
Client ->> Client: recover user password
Client ->> Server: OPAQUE Password Login
end
Note over Client, Server: User Session
opt Secret Key PAKE Key XOR nKode PAKE Key tunnel
Client ->> Server: all communication goes through this double PAKE
end
```

View File

@@ -2,6 +2,18 @@
"cells": [ "cells": [
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-27T19:17:57.439685Z",
"start_time": "2025-03-27T19:17:57.405237Z"
},
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [ "source": [
"import sys\n", "import sys\n",
"import os\n", "import os\n",
@@ -25,25 +37,18 @@
" interface_keypad = keypad.reshape(-1, props_per_key)\n", " interface_keypad = keypad.reshape(-1, props_per_key)\n",
" for idx, key_vals in enumerate(interface_keypad):\n", " for idx, key_vals in enumerate(interface_keypad):\n",
" print(f\"Key {idx}: {key_vals}\")\n" " print(f\"Key {idx}: {key_vals}\")\n"
], ]
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2025-03-27T19:17:57.439685Z",
"start_time": "2025-03-27T19:17:57.405237Z"
}
},
"outputs": [],
"execution_count": 1
}, },
{ {
"cell_type": "code",
"execution_count": 2,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:57.446190Z", "end_time": "2025-03-27T19:17:57.446190Z",
"start_time": "2025-03-27T19:17:57.443952Z" "start_time": "2025-03-27T19:17:57.443952Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"api = NKodeAPI()\n", "api = NKodeAPI()\n",
"user_icons = np.array([\n", "user_icons = np.array([\n",
@@ -54,13 +59,11 @@
" \"🦁\", \"🐻\", \"🐸\", \"🐙\", \"🦄\",\n", " \"🦁\", \"🐻\", \"🐸\", \"🐙\", \"🦄\",\n",
" \"🌟\", \"⚡\", \"🔥\", \"🍕\", \"🎉\"\n", " \"🌟\", \"⚡\", \"🔥\", \"🍕\", \"🎉\"\n",
"])" "])"
], ]
"outputs": [],
"execution_count": 2
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"### nKode Customer\n", "### nKode Customer\n",
"An nKode customer is business has employees (users). An nKode API can service many customers each with their own users.\n", "An nKode customer is business has employees (users). An nKode API can service many customers each with their own users.\n",
@@ -69,8 +72,8 @@
] ]
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"#### Customer Cipher Keys\n", "#### Customer Cipher Keys\n",
"Each customer has unique cipher keys.\n", "Each customer has unique cipher keys.\n",
@@ -81,36 +84,14 @@
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 3,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:57.487136Z", "end_time": "2025-03-27T19:17:57.487136Z",
"start_time": "2025-03-27T19:17:57.475079Z" "start_time": "2025-03-27T19:17:57.475079Z"
} }
}, },
"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": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@@ -133,23 +114,39 @@
] ]
} }
], ],
"execution_count": 3 "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}\")"
]
}, },
{ {
"cell_type": "code",
"execution_count": 4,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:57.507904Z", "end_time": "2025-03-27T19:17:57.507904Z",
"start_time": "2025-03-27T19:17:57.505187Z" "start_time": "2025-03-27T19:17:57.505187Z"
} }
}, },
"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": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@@ -165,11 +162,17 @@
] ]
} }
], ],
"execution_count": 4 "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"
]
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"### User Signup\n", "### User Signup\n",
"Users can create an nKode with these steps:\n", "Users can create an nKode with these steps:\n",
@@ -187,30 +190,23 @@
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 5,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:57.541997Z", "end_time": "2025-03-27T19:17:57.541997Z",
"start_time": "2025-03-27T19:17:57.534379Z" "start_time": "2025-03-27T19:17:57.534379Z"
} }
}, },
"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": [ "outputs": [
{ {
"data": { "data": {
"text/markdown": [
"### Icon Keypad"
],
"text/plain": [ "text/plain": [
"<IPython.core.display.Markdown object>" "<IPython.core.display.Markdown object>"
], ]
"text/markdown": "### Icon Keypad"
}, },
"metadata": {}, "metadata": {},
"output_type": "display_data" "output_type": "display_data"
@@ -228,10 +224,12 @@
}, },
{ {
"data": { "data": {
"text/markdown": [
"### Index Keypad"
],
"text/plain": [ "text/plain": [
"<IPython.core.display.Markdown object>" "<IPython.core.display.Markdown object>"
], ]
"text/markdown": "### Index Keypad"
}, },
"metadata": {}, "metadata": {},
"output_type": "display_data" "output_type": "display_data"
@@ -249,10 +247,12 @@
}, },
{ {
"data": { "data": {
"text/markdown": [
"### Customer Properties Keypad"
],
"text/plain": [ "text/plain": [
"<IPython.core.display.Markdown object>" "<IPython.core.display.Markdown object>"
], ]
"text/markdown": "### Customer Properties Keypad"
}, },
"metadata": {}, "metadata": {},
"output_type": "display_data" "output_type": "display_data"
@@ -269,33 +269,34 @@
] ]
} }
], ],
"execution_count": 5 "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)"
]
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"### Set nKode\n", "### Set nKode\n",
"The client receives `user_icons`, `set_signup_keypad`\n" "The client receives `user_icons`, `set_signup_keypad`\n"
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 6,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:57.582109Z", "end_time": "2025-03-27T19:17:57.582109Z",
"start_time": "2025-03-27T19:17:57.578783Z" "start_time": "2025-03-27T19:17:57.578783Z"
} }
}, },
"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": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@@ -308,32 +309,33 @@
] ]
} }
], ],
"execution_count": 6 "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}\")"
]
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"### Confirm nKode\n", "### Confirm nKode\n",
"Submit the set key entry to render the confirm keypad." "Submit the set key entry to render the confirm keypad."
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 7,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:57.846195Z", "end_time": "2025-03-27T19:17:57.846195Z",
"start_time": "2025-03-27T19:17:57.599899Z" "start_time": "2025-03-27T19:17:57.599899Z"
} }
}, },
"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": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@@ -349,31 +351,31 @@
] ]
} }
], ],
"execution_count": 7 "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"
]
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"source": "### Inferring an nKode selection" "metadata": {},
"source": [
"### Inferring an nKode selection"
]
}, },
{ {
"cell_type": "code",
"execution_count": 8,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:57.858315Z", "end_time": "2025-03-27T19:17:57.858315Z",
"start_time": "2025-03-27T19:17:57.855013Z" "start_time": "2025-03-27T19:17:57.855013Z"
} }
}, },
"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": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@@ -394,11 +396,20 @@
] ]
} }
], ],
"execution_count": 8 "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]]}\")"
]
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## User Cipher\n", "## User Cipher\n",
"\n", "\n",
@@ -412,30 +423,30 @@
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 9,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:57.879460Z", "end_time": "2025-03-27T19:17:57.879460Z",
"start_time": "2025-03-27T19:17:57.873816Z" "start_time": "2025-03-27T19:17:57.873816Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"from src.user_cipher import UserCipher\n", "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_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)" "user_prop_key_keypad = user_cipher.property_key.reshape(-1, keypad_size.props_per_key)"
], ]
"outputs": [],
"execution_count": 9
}, },
{ {
"cell_type": "code",
"execution_count": 10,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:57.904749Z", "end_time": "2025-03-27T19:17:57.904749Z",
"start_time": "2025-03-27T19:17:57.902224Z" "start_time": "2025-03-27T19:17:57.902224Z"
} }
}, },
"cell_type": "code",
"source": "print(f\"Property Key:\\n{user_prop_key_keypad}\")",
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@@ -450,17 +461,19 @@
] ]
} }
], ],
"execution_count": 10 "source": [
"print(f\"Property Key:\\n{user_prop_key_keypad}\")"
]
}, },
{ {
"cell_type": "code",
"execution_count": 11,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:57.951642Z", "end_time": "2025-03-27T19:17:57.951642Z",
"start_time": "2025-03-27T19:17:57.949347Z" "start_time": "2025-03-27T19:17:57.949347Z"
} }
}, },
"cell_type": "code",
"source": "print(f\"Passcode Key: {user_cipher.pass_key}\")",
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@@ -470,17 +483,19 @@
] ]
} }
], ],
"execution_count": 11 "source": [
"print(f\"Passcode Key: {user_cipher.pass_key}\")"
]
}, },
{ {
"cell_type": "code",
"execution_count": 12,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:57.989194Z", "end_time": "2025-03-27T19:17:57.989194Z",
"start_time": "2025-03-27T19:17:57.986687Z" "start_time": "2025-03-27T19:17:57.986687Z"
} }
}, },
"cell_type": "code",
"source": "print(f\"Mask Key: {user_cipher.mask_key}\")",
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@@ -490,17 +505,19 @@
] ]
} }
], ],
"execution_count": 12 "source": [
"print(f\"Mask Key: {user_cipher.mask_key}\")"
]
}, },
{ {
"cell_type": "code",
"execution_count": 13,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:58.019886Z", "end_time": "2025-03-27T19:17:58.019886Z",
"start_time": "2025-03-27T19:17:58.017350Z" "start_time": "2025-03-27T19:17:58.017350Z"
} }
}, },
"cell_type": "code",
"source": "print(f\"Combined Position Key: {user_cipher.combined_position_key}\")",
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@@ -510,17 +527,19 @@
] ]
} }
], ],
"execution_count": 13 "source": [
"print(f\"Combined Position Key: {user_cipher.combined_position_key}\")"
]
}, },
{ {
"cell_type": "code",
"execution_count": 14,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:58.050384Z", "end_time": "2025-03-27T19:17:58.050384Z",
"start_time": "2025-03-27T19:17:58.047868Z" "start_time": "2025-03-27T19:17:58.047868Z"
} }
}, },
"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": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@@ -530,22 +549,19 @@
] ]
} }
], ],
"execution_count": 14 "source": [
"print(f\"User Position Key = combined_pos_key XOR customer_pos_key: {user_cipher.combined_position_key ^ customer.cipher.position_key}\")"
]
}, },
{ {
"cell_type": "code",
"execution_count": 15,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:58.138360Z", "end_time": "2025-03-27T19:17:58.138360Z",
"start_time": "2025-03-27T19:17:58.135782Z" "start_time": "2025-03-27T19:17:58.135782Z"
} }
}, },
"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": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@@ -561,11 +577,16 @@
] ]
} }
], ],
"execution_count": 15 "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}\")"
]
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"#### Encipher Mask\n", "#### Encipher Mask\n",
"1. Get the `padded_passcode_position_indices`; padded with random position indices to equal length `max_nkode_len`.\n", "1. Get the `padded_passcode_position_indices`; padded with random position indices to equal length `max_nkode_len`.\n",
@@ -576,26 +597,26 @@
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 16,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:58.179247Z", "end_time": "2025-03-27T19:17:58.179247Z",
"start_time": "2025-03-27T19:17:58.176595Z" "start_time": "2025-03-27T19:17:58.176595Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"padded_passcode_position_indices = customer.cipher.get_passcode_position_indices_padded(list(passcode_property_indices), customer.nkode_policy.max_nkode_len)\n", "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", "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", "ordered_user_position_key = user_position_key[padded_passcode_position_indices]\n",
"mask = ordered_user_position_key ^ user_cipher.mask_key\n", "mask = ordered_user_position_key ^ user_cipher.mask_key\n",
"encoded_mask = user_cipher.encode_base64_str(mask)" "encoded_mask = user_cipher.encode_base64_str(mask)"
], ]
"outputs": [],
"execution_count": 16
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"#### Encipher Passcode\n", "#### Encipher Passcode\n",
"1. Compute `combined_property_key`\n", "1. Compute `combined_property_key`\n",
@@ -606,13 +627,15 @@
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 17,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:58.455536Z", "end_time": "2025-03-27T19:17:58.455536Z",
"start_time": "2025-03-27T19:17:58.212205Z" "start_time": "2025-03-27T19:17:58.212205Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"combined_prop_key = customer.cipher.property_key ^ user_cipher.property_key\n", "combined_prop_key = customer.cipher.property_key ^ user_cipher.property_key\n",
"user_passcode = combined_prop_key[passcode_property_indices]\n", "user_passcode = combined_prop_key[passcode_property_indices]\n",
@@ -621,13 +644,11 @@
"ciphered_passcode = padded_passcode ^ user_cipher.pass_key\n", "ciphered_passcode = padded_passcode ^ user_cipher.pass_key\n",
"passcode_prehash = base64.b64encode(hashlib.sha256(ciphered_passcode.tobytes()).digest())\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\")" "passcode_hash = bcrypt.hashpw(passcode_prehash, bcrypt.gensalt(rounds=12)).decode(\"utf-8\")"
], ]
"outputs": [],
"execution_count": 17
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"### User Login\n", "### User Login\n",
"1. Get login keypad\n", "1. Get login keypad\n",
@@ -635,22 +656,14 @@
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 18,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:58.702891Z", "end_time": "2025-03-27T19:17:58.702891Z",
"start_time": "2025-03-27T19:17:58.461555Z" "start_time": "2025-03-27T19:17:58.461555Z"
} }
}, },
"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": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
@@ -669,11 +682,19 @@
] ]
} }
], ],
"execution_count": 18 "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"
]
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## Validate Login Key Entry\n", "## Validate Login Key Entry\n",
"- decipher user mask and recover nkode position values\n", "- decipher user mask and recover nkode position values\n",
@@ -682,8 +703,8 @@
] ]
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"### Decipher Mask\n", "### Decipher Mask\n",
"Recover nKode position values:\n", "Recover nKode position values:\n",
@@ -694,13 +715,15 @@
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 19,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:58.713231Z", "end_time": "2025-03-27T19:17:58.713231Z",
"start_time": "2025-03-27T19:17:58.710458Z" "start_time": "2025-03-27T19:17:58.710458Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"login_keypad = api.get_login_keypad(username, customer_id)\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", "selected_keys_login = select_keys_with_passcode_values(passcode_property_indices, login_keypad, keypad_size.props_per_key)\n",
@@ -708,13 +731,11 @@
"mask = user.cipher.decode_base64_str(user.enciphered_passcode.mask)\n", "mask = user.cipher.decode_base64_str(user.enciphered_passcode.mask)\n",
"ordered_user_position_key = mask ^ user.cipher.mask_key\n", "ordered_user_position_key = mask ^ user.cipher.mask_key\n",
"user_position_key = customer.cipher.position_key ^ user.cipher.combined_position_key" "user_position_key = customer.cipher.position_key ^ user.cipher.combined_position_key"
], ]
"outputs": [],
"execution_count": 19
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"#### Get Presumed Properties\n", "#### Get Presumed Properties\n",
"- Get the passcode position indices (within the keys)\n", "- Get the passcode position indices (within the keys)\n",
@@ -722,44 +743,46 @@
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 20,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:58.729425Z", "end_time": "2025-03-27T19:17:58.729425Z",
"start_time": "2025-03-27T19:17:58.727025Z" "start_time": "2025-03-27T19:17:58.727025Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"passcode_position_indices = [int(np.where(user_position_key == pos)[0][0]) for pos in ordered_user_position_key[:passcode_len]]\n", "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", "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" "assert passcode_property_indices == presumed_property_indices\n"
], ]
"outputs": [],
"execution_count": 20
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"source": "### Compare Enciphered Passcodes\n" "metadata": {},
"source": [
"### Compare Enciphered Passcodes\n"
]
}, },
{ {
"cell_type": "code",
"execution_count": 21,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:58.983936Z", "end_time": "2025-03-27T19:17:58.983936Z",
"start_time": "2025-03-27T19:17:58.739679Z" "start_time": "2025-03-27T19:17:58.739679Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"valid_nkode = user.cipher.compare_nkode(presumed_property_indices, customer.cipher, user.enciphered_passcode.code)\n", "valid_nkode = user.cipher.compare_nkode(presumed_property_indices, customer.cipher, user.enciphered_passcode.code)\n",
"assert valid_nkode" "assert valid_nkode"
], ]
"outputs": [],
"execution_count": 21
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## Renew Properties\n", "## Renew Properties\n",
"1. Renew Customer Keys\n", "1. Renew Customer Keys\n",
@@ -769,13 +792,28 @@
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 22,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:59.477909Z", "end_time": "2025-03-27T19:17:59.477909Z",
"start_time": "2025-03-27T19:17:58.990632Z" "start_time": "2025-03-27T19:17:58.990632Z"
} }
}, },
"cell_type": "code", "outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Old User Cipher and Mask\n",
"mask: DUY6OIJHwzgG5ajVEGKHARHM6So=, code: $2b$12$/Za40kT7mC0quZxMUWCDs.4cF.3r2meCUBEoz0EWlSKkJAMOPiJTy\n",
"\n",
"New User Cipher and Mask\n",
"mask: GGBaMjr+zlPhS+pmfstihUeFt6A=, code: $2b$12$wE7bq7sYd8Q58j.qKS5ASO2IMzaJ71UW/0vOYAhx7zUhURDJYIaZi\n",
"\n"
]
}
],
"source": [ "source": [
"def print_user_enciphered_code():\n", "def print_user_enciphered_code():\n",
" mask = api.customers[customer_id].users[username].enciphered_passcode.mask\n", " mask = api.customers[customer_id].users[username].enciphered_passcode.mask\n",
@@ -791,39 +829,26 @@
"print(\"New User Cipher and Mask\")\n", "print(\"New User Cipher and Mask\")\n",
"print_user_enciphered_code()\n", "print_user_enciphered_code()\n",
"assert success" "assert success"
],
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Old User Cipher and Mask\n",
"mask: DUY6OIJHwzgG5ajVEGKHARHM6So=, code: $2b$12$/Za40kT7mC0quZxMUWCDs.4cF.3r2meCUBEoz0EWlSKkJAMOPiJTy\n",
"\n",
"New User Cipher and Mask\n",
"mask: GGBaMjr+zlPhS+pmfstihUeFt6A=, code: $2b$12$wE7bq7sYd8Q58j.qKS5ASO2IMzaJ71UW/0vOYAhx7zUhURDJYIaZi\n",
"\n"
] ]
}
],
"execution_count": 22
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"### Renew Customer Keys\n", "### Renew Customer Keys\n",
"The customer cipher keys are replaced." "The customer cipher keys are replaced."
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 23,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:59.490134Z", "end_time": "2025-03-27T19:17:59.490134Z",
"start_time": "2025-03-27T19:17:59.486082Z" "start_time": "2025-03-27T19:17:59.486082Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"old_props = customer.cipher.property_key.copy()\n", "old_props = customer.cipher.property_key.copy()\n",
"old_pos = customer.cipher.position_key.copy()\n", "old_pos = customer.cipher.position_key.copy()\n",
@@ -831,13 +856,11 @@
"customer.cipher.position_key = np.random.choice(2 ** 16, size=keypad_size.props_per_key, 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_props = customer.cipher.property_key\n",
"new_pos = customer.cipher.position_key" "new_pos = customer.cipher.position_key"
], ]
"outputs": [],
"execution_count": 23
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"### Intermediate Renew User\n", "### Intermediate Renew User\n",
"User property and position keys go through an intermediate phase.\n", "User property and position keys go through an intermediate phase.\n",
@@ -851,13 +874,15 @@
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 24,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:59.500256Z", "end_time": "2025-03-27T19:17:59.500256Z",
"start_time": "2025-03-27T19:17:59.497839Z" "start_time": "2025-03-27T19:17:59.497839Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"props_xor = new_props ^ old_props\n", "props_xor = new_props ^ old_props\n",
"pos_xor = new_pos ^ old_pos\n", "pos_xor = new_pos ^ old_pos\n",
@@ -865,26 +890,26 @@
" user.renew = True\n", " user.renew = True\n",
" user.cipher.combined_position_key = user.cipher.combined_position_key ^ pos_xor\n", " user.cipher.combined_position_key = user.cipher.combined_position_key ^ pos_xor\n",
" user.cipher.property_key = user.cipher.property_key ^ props_xor" " user.cipher.property_key = user.cipher.property_key ^ props_xor"
], ]
"outputs": [],
"execution_count": 24
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"### New User Keys\n", "### New 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." "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."
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 25,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-27T19:17:59.752960Z", "end_time": "2025-03-27T19:17:59.752960Z",
"start_time": "2025-03-27T19:17:59.508826Z" "start_time": "2025-03-27T19:17:59.508826Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"if user.renew:\n", "if user.renew:\n",
" user.cipher = UserCipher.create(\n", " user.cipher = UserCipher.create(\n",
@@ -894,30 +919,28 @@
" )\n", " )\n",
" user.enciphered_passcode = user.cipher.encipher_nkode(presumed_property_indices, customer.cipher)\n", " user.enciphered_passcode = user.cipher.encipher_nkode(presumed_property_indices, customer.cipher)\n",
" user.renew = False" " user.renew = False"
], ]
"outputs": [],
"execution_count": 25
} }
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
"language_info": { "language_info": {
"codemirror_mode": { "codemirror_mode": {
"name": "ipython", "name": "ipython",
"version": 2 "version": 3
}, },
"file_extension": ".py", "file_extension": ".py",
"mimetype": "text/x-python", "mimetype": "text/x-python",
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython2", "pygments_lexer": "ipython3",
"version": "2.7.6" "version": "3.10.14"
} }
}, },
"nbformat": 4, "nbformat": 4,
"nbformat_minor": 0 "nbformat_minor": 4
} }

View File

@@ -2,6 +2,18 @@
"cells": [ "cells": [
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 30,
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-28T15:06:18.878127Z",
"start_time": "2025-03-28T15:06:18.874618Z"
},
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [ "source": [
"import sys\n", "import sys\n",
"import os\n", "import os\n",
@@ -15,20 +27,11 @@
"\n", "\n",
"def random_username() -> str:\n", "def random_username() -> str:\n",
" return \"test_username\" + \"\".join([choice(ascii_lowercase) for _ in range(6)])\n" " return \"test_username\" + \"\".join([choice(ascii_lowercase) for _ in range(6)])\n"
], ]
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2025-03-28T15:06:18.878127Z",
"start_time": "2025-03-28T15:06:18.874618Z"
}
},
"outputs": [],
"execution_count": 30
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## Initialize nKode API and Create Customer\n", "## Initialize nKode API and Create Customer\n",
"#### nKode Customer\n", "#### nKode Customer\n",
@@ -39,6 +42,18 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 31,
"metadata": {
"ExecuteTime": {
"end_time": "2025-03-28T15:06:18.896461Z",
"start_time": "2025-03-28T15:06:18.891125Z"
},
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [ "source": [
"api = NKodeAPI()\n", "api = NKodeAPI()\n",
"policy = NKodePolicy(\n", "policy = NKodePolicy(\n",
@@ -53,20 +68,11 @@
")\n", ")\n",
"customer_id = api.create_new_customer(keypad_size, policy)\n", "customer_id = api.create_new_customer(keypad_size, policy)\n",
"customer = api.get_customer(customer_id)" "customer = api.get_customer(customer_id)"
], ]
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2025-03-28T15:06:18.896461Z",
"start_time": "2025-03-28T15:06:18.891125Z"
}
},
"outputs": [],
"execution_count": 31
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## nKode Enrollment\n", "## nKode Enrollment\n",
"Users enroll in three steps:\n", "Users enroll in three steps:\n",
@@ -79,72 +85,72 @@
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 32,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-28T15:06:18.914254Z", "end_time": "2025-03-28T15:06:18.914254Z",
"start_time": "2025-03-28T15:06:18.911798Z" "start_time": "2025-03-28T15:06:18.911798Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"username = random_username()\n", "username = random_username()\n",
"signup_session_id, set_keypad = api.generate_signup_keypad(customer_id, username)" "signup_session_id, set_keypad = api.generate_signup_keypad(customer_id, username)"
], ]
"outputs": [],
"execution_count": 32
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"### Set nKode\n", "### Set nKode\n",
"The client receives `user_icons`, `set_signup_keypad`\n" "The client receives `user_icons`, `set_signup_keypad`\n"
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 33,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-28T15:06:18.931791Z", "end_time": "2025-03-28T15:06:18.931791Z",
"start_time": "2025-03-28T15:06:18.929028Z" "start_time": "2025-03-28T15:06:18.929028Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"passcode_len = 4\n", "passcode_len = 4\n",
"passcode_property_indices = np.random.choice(set_keypad.reshape(-1), size=passcode_len, replace=False).tolist()\n", "passcode_property_indices = np.random.choice(set_keypad.reshape(-1), size=passcode_len, replace=False).tolist()\n",
"selected_keys_set = select_keys_with_passcode_values(passcode_property_indices, set_keypad, keypad_size.numb_of_keys)" "selected_keys_set = select_keys_with_passcode_values(passcode_property_indices, set_keypad, keypad_size.numb_of_keys)"
], ]
"outputs": [],
"execution_count": 33
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"### Confirm nKode\n", "### Confirm nKode\n",
"The user enter then submits their key entry. The server returns the confirm_keypad, another index array and dispersion of the set_keypad." "The user enter then submits their key entry. The server returns the confirm_keypad, another index array and dispersion of the set_keypad."
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 34,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-28T15:06:19.247638Z", "end_time": "2025-03-28T15:06:19.247638Z",
"start_time": "2025-03-28T15:06:18.938601Z" "start_time": "2025-03-28T15:06:18.938601Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"confirm_keypad = api.set_nkode(customer_id, selected_keys_set, signup_session_id)\n", "confirm_keypad = api.set_nkode(customer_id, selected_keys_set, signup_session_id)\n",
"selected_keys_confirm = select_keys_with_passcode_values(passcode_property_indices, 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",
"success = api.confirm_nkode(customer_id, selected_keys_confirm, signup_session_id)\n", "success = api.confirm_nkode(customer_id, selected_keys_confirm, signup_session_id)\n",
"assert success" "assert success"
], ]
"outputs": [],
"execution_count": 34
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"### User Login\n", "### User Login\n",
"1. Get login keypad\n", "1. Get login keypad\n",
@@ -152,25 +158,25 @@
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 35,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-28T15:06:19.559753Z", "end_time": "2025-03-28T15:06:19.559753Z",
"start_time": "2025-03-28T15:06:19.254675Z" "start_time": "2025-03-28T15:06:19.254675Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"login_keypad = api.get_login_keypad(username, customer_id)\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", "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)\n", "success = api.login(customer_id, username, selected_keys_login)\n",
"assert success" "assert success"
], ]
"outputs": [],
"execution_count": 35
}, },
{ {
"metadata": {},
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {},
"source": [ "source": [
"## Renew Properties\n", "## Renew Properties\n",
"Replace server-side ciphers keys and nkode hash with new values.\n", "Replace server-side ciphers keys and nkode hash with new values.\n",
@@ -180,60 +186,60 @@
] ]
}, },
{ {
"cell_type": "code",
"execution_count": 36,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-28T15:06:20.181548Z", "end_time": "2025-03-28T15:06:20.181548Z",
"start_time": "2025-03-28T15:06:19.568067Z" "start_time": "2025-03-28T15:06:19.568067Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"api.renew_keys(customer_id) # Steps 1 and 2\n", "api.renew_keys(customer_id) # Steps 1 and 2\n",
"login_keypad = api.get_login_keypad(username, customer_id)\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", "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", "success = api.login(customer_id, username, selected_keys_login) # Step 3\n",
"assert success" "assert success"
], ]
"outputs": [],
"execution_count": 36
}, },
{ {
"cell_type": "code",
"execution_count": 37,
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-03-28T15:06:20.500050Z", "end_time": "2025-03-28T15:06:20.500050Z",
"start_time": "2025-03-28T15:06:20.194912Z" "start_time": "2025-03-28T15:06:20.194912Z"
} }
}, },
"cell_type": "code", "outputs": [],
"source": [ "source": [
"login_keypad = api.get_login_keypad(username, customer_id)\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", "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)\n", "success = api.login(customer_id, username, selected_keys_login)\n",
"assert success" "assert success"
], ]
"outputs": [],
"execution_count": 37
} }
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
"language_info": { "language_info": {
"codemirror_mode": { "codemirror_mode": {
"name": "ipython", "name": "ipython",
"version": 2 "version": 3
}, },
"file_extension": ".py", "file_extension": ".py",
"mimetype": "text/x-python", "mimetype": "text/x-python",
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython2", "pygments_lexer": "ipython3",
"version": "2.7.6" "version": "3.10.14"
} }
}, },
"nbformat": 4, "nbformat": 4,
"nbformat_minor": 0 "nbformat_minor": 4
} }

View File

View File

@@ -0,0 +1,128 @@
import base64
import hashlib
from dataclasses import dataclass
import bcrypt
import numpy as np
from src.models import EncipheredNKode, KeypadSize
@dataclass
class NKodeCipher:
property_key: np.ndarray
position_key: np.ndarray
pass_key: np.ndarray
mask_key: np.ndarray
keypad_size: KeypadSize
max_nkode_len: int
@classmethod
def create(cls, keypad_size: KeypadSize, max_nkode_len: int) -> 'NKodeCipher':
return NKodeCipher(
property_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),
mask_key=np.random.choice(2**16, size=max_nkode_len, replace=False),
position_key= np.random.choice(2**16,size=keypad_size.props_per_key, replace=False),
max_nkode_len=max_nkode_len,
keypad_size=keypad_size,
)
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_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 pad_user_mask(self, user_mask: np.ndarray, pos_vals: np.ndarray) -> np.ndarray:
# TODO: replace with new method
if len(user_mask) >= self.max_nkode_len:
raise ValueError("User encoded_mask is too long")
padding_size = self.max_nkode_len - len(user_mask)
padding = np.random.choice(pos_vals, size=padding_size, replace=True).astype(np.uint16)
return np.concatenate([user_mask, padding])
@staticmethod
def encode_base64_str(data: np.ndarray) -> str:
return base64.b64encode( b"".join([int(num).to_bytes(2, byteorder='big') for num in data])).decode("utf-8")
@staticmethod
def decode_base64_str(data: str) -> np.ndarray:
byte_data = base64.b64decode(data)
int_list = []
for i in range(0, len(byte_data), 2):
int_val = int.from_bytes(byte_data[i:i + 2], byteorder='big')
int_list.append(int_val)
return np.array(int_list, dtype=np.uint16)
def encipher_nkode(
self,
passcode_prop_idx: list[int],
) -> EncipheredNKode:
mask = self.encipher_mask(passcode_prop_idx)
code = self.hash_nkode(passcode_prop_idx)
return EncipheredNKode(
code=code,
mask=mask
)
def hash_nkode(
self,
passcode_prop_idx: list[int],
) -> str:
salt = bcrypt.gensalt(rounds=12)
passcode_bytes = self.prehash_passcode(passcode_prop_idx)
passcode_digest = base64.b64encode(hashlib.sha256(passcode_bytes).digest())
hashed_data = bcrypt.hashpw(passcode_digest, salt)
return hashed_data.decode("utf-8")
def compare_nkode(
self,
passcode_prop_idx: list[int],
hashed_passcode: str
) -> bool:
passcode_bytes = self.prehash_passcode(passcode_prop_idx)
passcode_digest = base64.b64encode(hashlib.sha256(passcode_bytes).digest())
return bcrypt.checkpw(passcode_digest, hashed_passcode.encode('utf-8'))
def prehash_passcode(
self,
passcode_prop_idx: list[int],
) -> bytes:
passcode_len = len(passcode_prop_idx)
passcode_cipher = self.pass_key.copy()
passcode_cipher[:passcode_len] = (
passcode_cipher[:passcode_len] ^
self.property_key[passcode_prop_idx]
)
return passcode_cipher.astype(np.uint16).tobytes()
def get_passcode_position_indices_padded(self, passcode_indices: list[int]) -> list[int]:
pos_indices = [idx % self.keypad_size.props_per_key for idx in passcode_indices]
pad_len = self.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()
def encipher_mask(
self,
passcode_prop_idx: list[int],
) -> str:
pos_idxs = self.get_passcode_position_indices_padded(passcode_prop_idx)
ordered_pos_key = self.position_key[pos_idxs]
mask = ordered_pos_key ^ self.mask_key
encoded_mask = self.encode_base64_str(mask)
return encoded_mask
def decipher_mask(self, encoded_mask: str, passcode_len: int) -> list[int]:
mask = self.decode_base64_str(encoded_mask)
# user_pos_key ordered by the user's nkode and padded to be length max_nkode_len
ordered_user_pos_key = mask ^ self.mask_key
passcode_position = []
for position_val in ordered_user_pos_key[:passcode_len]:
position_idx = np.where(self.position_key == position_val)[0][0]
passcode_position.append(int(self.position_key[position_idx]))
return passcode_position

View File

@@ -0,0 +1,37 @@
import numpy as np
import pytest
from src.models import KeypadSize
from src.nkode_cipher_v2.nkode_cipher import NKodeCipher
@pytest.mark.parametrize(
"passcode_len",
[
6
]
)
def test_encode_decode_base64(passcode_len):
data = np.random.choice(2**16, passcode_len, replace=False)
encoded = NKodeCipher.encode_base64_str(data)
decoded = NKodeCipher.decode_base64_str(encoded)
assert (len(data) == len(decoded))
assert (all(data[idx] == decoded[idx] for idx in range(passcode_len)))
@pytest.mark.parametrize(
"keypad_size,max_nkode_len",
[
(KeypadSize(numb_of_keys=10, props_per_key=11), 10),
(KeypadSize(numb_of_keys=9, props_per_key=11), 10),
(KeypadSize(numb_of_keys=8, props_per_key=11), 12),
])
def test_decode_mask(keypad_size, max_nkode_len):
passcode_entry = np.random.choice(keypad_size.total_props, 4, replace=False)
user_keys = NKodeCipher.create(keypad_size, max_nkode_len)
passcode_values = [user_keys.property_key[idx] for idx in passcode_entry]
enciphered = user_keys.encipher_nkode(passcode_entry)
orig_passcode_pos_vals = user_keys.get_props_position_vals(passcode_values)
passcode_pos_vals = user_keys.decipher_mask(enciphered.mask, len(passcode_entry))
assert (len(passcode_pos_vals) == len(orig_passcode_pos_vals))
assert (all(orig_passcode_pos_vals[idx] == passcode_pos_vals[idx] for idx in range(len(passcode_pos_vals))))
assert(user_keys.compare_nkode(passcode_entry, enciphered.code))