Compare commits
13 Commits
NKodeLowBa
...
UpdateDocs
| Author | SHA1 | Date | |
|---|---|---|---|
| ccf1bac337 | |||
|
|
64ddd9f348 | ||
| 10c84e4535 | |||
| 437d8b0f31 | |||
| 81829c81b8 | |||
| 13a1a64772 | |||
| d22ec80ee7 | |||
| 6777a19f5b | |||
| 6ea7486d76 | |||
| 1e5fd26464 | |||
| d1b6f192af | |||
| 9a12b3b5e4 | |||
|
|
203973effa |
1
.obsidian/app.json
vendored
Normal file
1
.obsidian/app.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
1
.obsidian/appearance.json
vendored
Normal file
1
.obsidian/appearance.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
31
.obsidian/core-plugins.json
vendored
Normal file
31
.obsidian/core-plugins.json
vendored
Normal 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
166
.obsidian/workspace.json
vendored
Normal 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": []
|
||||
}
|
||||
207
docs/encipher_decipher_renew_nkode_v2.md
Normal file
207
docs/encipher_decipher_renew_nkode_v2.md
Normal 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.
|
||||
@@ -1,10 +1,10 @@
|
||||
# nKode Authentication Over Unencrypted Channel in Low-Bandwidth Environments
|
||||
# nKode Authentication Over Unsecured and Low-Bandwidth Network
|
||||
|
||||
## Low-Bandwidth Architecture
|
||||
|
||||
The standard nKode architecture will not work in low-bandwidth environments.
|
||||
Keypad icons are too large to send from the sever to the client.
|
||||
To over come this issue, we can move the nKode icons from the serve to the users mobile device.
|
||||
Keypad icons are too large to send from the server to the client.
|
||||
To over come this issue, we can move the nKode icons from the server to the users mobile device.
|
||||
The server only sends the indices in which the icons need to be arranged.
|
||||
|
||||
```mermaid
|
||||
@@ -15,8 +15,9 @@ sequenceDiagram
|
||||
Note over User,Server: Enrollment
|
||||
User ->> Server: Initiate Enrollment
|
||||
Server ->> Server: Generate Keypad Icons
|
||||
Note right of Server: Ideally the icons are generated on the users device.<br/>Since current ML models are too compute intense,<br/>a GPU enabled server must run the models during enrollment.
|
||||
Server -->> Mobile Client: Store Icons On Device
|
||||
Note right of Server: Server does not store the icons and does not know what they are
|
||||
Note right of Server: The Server does not store the icons
|
||||
Server ->> Mobile Client: Keypad Index Array
|
||||
Mobile Client ->> User: Render Keypad
|
||||
User ->> Server: Set nKode
|
||||
@@ -37,48 +38,35 @@ A ChaCha20 Deterministic CSPRNG is a cryptographically secure pseudorandom numbe
|
||||
|
||||
## Secure Low-Bandwidth Architecture
|
||||
|
||||
We can modify the architecture above to allow secure authentication over an unencrypted network
|
||||
We can modify the architecture above to allow secure authentication over an unencrypted network using ChaCha20.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Mobile Client
|
||||
participant Server
|
||||
Note over User,Server: Enrollment
|
||||
Note over User,Server: Enrollment (assume secure network)
|
||||
User ->> Server: Initiate Enrollment
|
||||
Server ->> Server: Generate Keypad Icons
|
||||
Server -->> Mobile Client: Store Icons On Device
|
||||
Note right of Server: Server does not store the icons and does not know what they are
|
||||
rect rgb(191, 223, 255)
|
||||
Server -->> Mobile Client: Store ChaCha20 256-bit key
|
||||
end
|
||||
rect rgb(191, 223, 255)
|
||||
Server ->> Server: Ciphered Keypad Index Array =<br/>ChaCha20FisherYates(Keypad Index Array, SharedKey, Nonce)
|
||||
Server ->> Mobile Client: Ciphered Keypad Index Array + Nonce
|
||||
end
|
||||
Note right of Server: Server also sends the 96-bit nonce in plain-text.<br/>The Serve must never use the same nonce twice.<br/>It must be randonly generated for every authentication.<br/>The only additional overhead is the 96-bit nonce.
|
||||
rect rgb(191, 223, 255)
|
||||
Mobile Client ->> Mobile Client: Keypad Index Array =<br/>Reverse(Ciphered Keypad Index Array, SharedKey, Nonce)
|
||||
end
|
||||
Server ->> Mobile Client: Keypad Index Array
|
||||
Mobile Client ->> User: Render Keypad
|
||||
User ->> Server: Set nKode
|
||||
Server ->> Server: Disperse Keypad
|
||||
rect rgb(191, 223, 255)
|
||||
Server ->> Server: Ciphered Keypad Index Array =<br/>ChaCha20FisherYates(Keypad Index Array, SharedKey, Nonce)
|
||||
Server ->> Mobile Client: Ciphered Keypad Index Array + Nonce
|
||||
end
|
||||
rect rgb(191, 223, 255)
|
||||
Mobile Client ->> Mobile Client: Keypad Index Array =<br/>Reverse(Ciphered Keypad Index Array, SharedKey, Nonce)
|
||||
end
|
||||
Server ->> Mobile Client: Keypad Index Array
|
||||
Mobile Client ->> User: Render Keypad
|
||||
User ->> Server: Confirm nKode
|
||||
Note over User,Server: Login
|
||||
Note over User,Server: Login (assume unsecure network)
|
||||
rect rgb(191, 223, 255)
|
||||
Server ->> Server: Ciphered Keypad Index Array =<br/>ChaCha20FisherYates(Keypad Index Array, SharedKey, Nonce)
|
||||
Server ->> Mobile Client: Ciphered Keypad Index Array + Nonce
|
||||
Server ->> Server: Shuffled Keypad Index Array =<br/>ChaCha20FisherYates(Keypad Index Array, SharedKey, Nonce)
|
||||
Server ->> Mobile Client: Shuffled Keypad Index Array + Nonce
|
||||
end
|
||||
Note right of Server: Server also sends the 96-bit nonce in plain-text.<br/>The Server must never use the same nonce twice.<br/>It must be randonly generated for every authentication.<br/>The only additional overhead is the 96-bit nonce.
|
||||
rect rgb(191, 223, 255)
|
||||
Mobile Client ->> Mobile Client: Keypad Index Array =<br/>Reverse(Ciphered Keypad Index Array, SharedKey, Nonce)
|
||||
Mobile Client ->> Mobile Client: Keypad Index Array =<br/>Unshuffle(Shuffled Keypad Index Array, SharedKey, Nonce)
|
||||
end
|
||||
Mobile Client ->> User: Render Keypad
|
||||
User ->> Server: Successful Login
|
||||
69
docs/scripts/render_encipher_decipher_diagrams_v2.py
Normal file
69
docs/scripts/render_encipher_decipher_diagrams_v2.py
Normal 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)
|
||||
70
docs/scripts/render_zero_trust_nkode.py
Normal file
70
docs/scripts/render_zero_trust_nkode.py
Normal 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)
|
||||
187
docs/templates/encipher_decipher_renew_nkode_v2.template.md
vendored
Normal file
187
docs/templates/encipher_decipher_renew_nkode_v2.template.md
vendored
Normal 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.
|
||||
|
||||
43
docs/templates/zero_trust_nkode.template.md
vendored
Normal file
43
docs/templates/zero_trust_nkode.template.md
vendored
Normal 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
|
||||
```
|
||||
@@ -2,6 +2,18 @@
|
||||
"cells": [
|
||||
{
|
||||
"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": [
|
||||
"import sys\n",
|
||||
"import os\n",
|
||||
@@ -25,25 +37,18 @@
|
||||
" interface_keypad = keypad.reshape(-1, props_per_key)\n",
|
||||
" for idx, key_vals in enumerate(interface_keypad):\n",
|
||||
" print(f\"Key {idx}: {key_vals}\")\n"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.439685Z",
|
||||
"start_time": "2025-03-27T19:17:57.405237Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": 1
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.446190Z",
|
||||
"start_time": "2025-03-27T19:17:57.443952Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"api = NKodeAPI()\n",
|
||||
"user_icons = np.array([\n",
|
||||
@@ -54,13 +59,11 @@
|
||||
" \"🦁\", \"🐻\", \"🐸\", \"🐙\", \"🦄\",\n",
|
||||
" \"🌟\", \"⚡\", \"🔥\", \"🍕\", \"🎉\"\n",
|
||||
"])"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 2
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### nKode Customer\n",
|
||||
"An nKode customer is business has employees (users). An nKode API can service many customers each with their own users.\n",
|
||||
@@ -69,8 +72,8 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Customer Cipher Keys\n",
|
||||
"Each customer has unique cipher keys.\n",
|
||||
@@ -81,36 +84,14 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.487136Z",
|
||||
"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": [
|
||||
{
|
||||
"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": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.507904Z",
|
||||
"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": [
|
||||
{
|
||||
"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",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### User Signup\n",
|
||||
"Users can create an nKode with these steps:\n",
|
||||
@@ -187,30 +190,23 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.541997Z",
|
||||
"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": [
|
||||
{
|
||||
"data": {
|
||||
"text/markdown": [
|
||||
"### Icon Keypad"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
],
|
||||
"text/markdown": "### Icon Keypad"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
@@ -228,10 +224,12 @@
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/markdown": [
|
||||
"### Index Keypad"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
],
|
||||
"text/markdown": "### Index Keypad"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
@@ -249,10 +247,12 @@
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/markdown": [
|
||||
"### Customer Properties Keypad"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
],
|
||||
"text/markdown": "### Customer Properties Keypad"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"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",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Set nKode\n",
|
||||
"The client receives `user_icons`, `set_signup_keypad`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.582109Z",
|
||||
"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": [
|
||||
{
|
||||
"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",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Confirm nKode\n",
|
||||
"Submit the set key entry to render the confirm keypad."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.846195Z",
|
||||
"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": [
|
||||
{
|
||||
"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",
|
||||
"source": "### Inferring an nKode selection"
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Inferring an nKode selection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.858315Z",
|
||||
"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": [
|
||||
{
|
||||
"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",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## User Cipher\n",
|
||||
"\n",
|
||||
@@ -412,30 +423,30 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.879460Z",
|
||||
"start_time": "2025-03-27T19:17:57.873816Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from src.user_cipher import UserCipher\n",
|
||||
"user_cipher = UserCipher.create(keypad_size, customer.cipher.position_key, customer.nkode_policy.max_nkode_len)\n",
|
||||
"user_prop_key_keypad = user_cipher.property_key.reshape(-1, keypad_size.props_per_key)"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 9
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.904749Z",
|
||||
"start_time": "2025-03-27T19:17:57.902224Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": "print(f\"Property Key:\\n{user_prop_key_keypad}\")",
|
||||
"outputs": [
|
||||
{
|
||||
"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": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.951642Z",
|
||||
"start_time": "2025-03-27T19:17:57.949347Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": "print(f\"Passcode Key: {user_cipher.pass_key}\")",
|
||||
"outputs": [
|
||||
{
|
||||
"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": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.989194Z",
|
||||
"start_time": "2025-03-27T19:17:57.986687Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": "print(f\"Mask Key: {user_cipher.mask_key}\")",
|
||||
"outputs": [
|
||||
{
|
||||
"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": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.019886Z",
|
||||
"start_time": "2025-03-27T19:17:58.017350Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": "print(f\"Combined Position Key: {user_cipher.combined_position_key}\")",
|
||||
"outputs": [
|
||||
{
|
||||
"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": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.050384Z",
|
||||
"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": [
|
||||
{
|
||||
"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": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.138360Z",
|
||||
"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": [
|
||||
{
|
||||
"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",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Encipher Mask\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": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.179247Z",
|
||||
"start_time": "2025-03-27T19:17:58.176595Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"padded_passcode_position_indices = customer.cipher.get_passcode_position_indices_padded(list(passcode_property_indices), customer.nkode_policy.max_nkode_len)\n",
|
||||
"user_position_key = user_cipher.combined_position_key ^ customer.cipher.position_key\n",
|
||||
"ordered_user_position_key = user_position_key[padded_passcode_position_indices]\n",
|
||||
"mask = ordered_user_position_key ^ user_cipher.mask_key\n",
|
||||
"encoded_mask = user_cipher.encode_base64_str(mask)"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 16
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Encipher Passcode\n",
|
||||
"1. Compute `combined_property_key`\n",
|
||||
@@ -606,13 +627,15 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.455536Z",
|
||||
"start_time": "2025-03-27T19:17:58.212205Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"combined_prop_key = customer.cipher.property_key ^ user_cipher.property_key\n",
|
||||
"user_passcode = combined_prop_key[passcode_property_indices]\n",
|
||||
@@ -621,13 +644,11 @@
|
||||
"ciphered_passcode = padded_passcode ^ user_cipher.pass_key\n",
|
||||
"passcode_prehash = base64.b64encode(hashlib.sha256(ciphered_passcode.tobytes()).digest())\n",
|
||||
"passcode_hash = bcrypt.hashpw(passcode_prehash, bcrypt.gensalt(rounds=12)).decode(\"utf-8\")"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 17
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### User Login\n",
|
||||
"1. Get login keypad\n",
|
||||
@@ -635,22 +656,14 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.702891Z",
|
||||
"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": [
|
||||
{
|
||||
"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",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Validate Login Key Entry\n",
|
||||
"- decipher user mask and recover nkode position values\n",
|
||||
@@ -682,8 +703,8 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Decipher Mask\n",
|
||||
"Recover nKode position values:\n",
|
||||
@@ -694,13 +715,15 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.713231Z",
|
||||
"start_time": "2025-03-27T19:17:58.710458Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"login_keypad = api.get_login_keypad(username, customer_id)\n",
|
||||
"selected_keys_login = select_keys_with_passcode_values(passcode_property_indices, login_keypad, keypad_size.props_per_key)\n",
|
||||
@@ -708,13 +731,11 @@
|
||||
"mask = user.cipher.decode_base64_str(user.enciphered_passcode.mask)\n",
|
||||
"ordered_user_position_key = mask ^ user.cipher.mask_key\n",
|
||||
"user_position_key = customer.cipher.position_key ^ user.cipher.combined_position_key"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 19
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Get Presumed Properties\n",
|
||||
"- Get the passcode position indices (within the keys)\n",
|
||||
@@ -722,44 +743,46 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.729425Z",
|
||||
"start_time": "2025-03-27T19:17:58.727025Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"passcode_position_indices = [int(np.where(user_position_key == pos)[0][0]) for pos in ordered_user_position_key[:passcode_len]]\n",
|
||||
"presumed_property_indices = customer.users[username].user_keypad.get_prop_idxs_by_keynumb_setidx(selected_keys_login, passcode_position_indices)\n",
|
||||
"assert passcode_property_indices == presumed_property_indices\n"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 20
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"source": "### Compare Enciphered Passcodes\n"
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Compare Enciphered Passcodes\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.983936Z",
|
||||
"start_time": "2025-03-27T19:17:58.739679Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"valid_nkode = user.cipher.compare_nkode(presumed_property_indices, customer.cipher, user.enciphered_passcode.code)\n",
|
||||
"assert valid_nkode"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 21
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Renew Properties\n",
|
||||
"1. Renew Customer Keys\n",
|
||||
@@ -769,13 +792,28 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:59.477909Z",
|
||||
"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": [
|
||||
"def print_user_enciphered_code():\n",
|
||||
" mask = api.customers[customer_id].users[username].enciphered_passcode.mask\n",
|
||||
@@ -791,39 +829,26 @@
|
||||
"print(\"New User Cipher and Mask\")\n",
|
||||
"print_user_enciphered_code()\n",
|
||||
"assert success"
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Old User Cipher and Mask\n",
|
||||
"mask: 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",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Renew Customer Keys\n",
|
||||
"The customer cipher keys are replaced."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:59.490134Z",
|
||||
"start_time": "2025-03-27T19:17:59.486082Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"old_props = customer.cipher.property_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",
|
||||
"new_props = customer.cipher.property_key\n",
|
||||
"new_pos = customer.cipher.position_key"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 23
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Intermediate Renew User\n",
|
||||
"User property and position keys go through an intermediate phase.\n",
|
||||
@@ -851,13 +874,15 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:59.500256Z",
|
||||
"start_time": "2025-03-27T19:17:59.497839Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"props_xor = new_props ^ old_props\n",
|
||||
"pos_xor = new_pos ^ old_pos\n",
|
||||
@@ -865,26 +890,26 @@
|
||||
" user.renew = True\n",
|
||||
" user.cipher.combined_position_key = user.cipher.combined_position_key ^ pos_xor\n",
|
||||
" user.cipher.property_key = user.cipher.property_key ^ props_xor"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 24
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 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."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:59.752960Z",
|
||||
"start_time": "2025-03-27T19:17:59.508826Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if user.renew:\n",
|
||||
" user.cipher = UserCipher.create(\n",
|
||||
@@ -894,30 +919,28 @@
|
||||
" )\n",
|
||||
" user.enciphered_passcode = user.cipher.encipher_nkode(presumed_property_indices, customer.cipher)\n",
|
||||
" user.renew = False"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 25
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2",
|
||||
"version": "2.7.6"
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.14"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
"cells": [
|
||||
{
|
||||
"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": [
|
||||
"import sys\n",
|
||||
"import os\n",
|
||||
@@ -15,20 +27,11 @@
|
||||
"\n",
|
||||
"def random_username() -> str:\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",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Initialize nKode API and Create Customer\n",
|
||||
"#### nKode Customer\n",
|
||||
@@ -39,6 +42,18 @@
|
||||
},
|
||||
{
|
||||
"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": [
|
||||
"api = NKodeAPI()\n",
|
||||
"policy = NKodePolicy(\n",
|
||||
@@ -53,20 +68,11 @@
|
||||
")\n",
|
||||
"customer_id = api.create_new_customer(keypad_size, policy)\n",
|
||||
"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",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## nKode Enrollment\n",
|
||||
"Users enroll in three steps:\n",
|
||||
@@ -79,72 +85,72 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 32,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-28T15:06:18.914254Z",
|
||||
"start_time": "2025-03-28T15:06:18.911798Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"username = random_username()\n",
|
||||
"signup_session_id, set_keypad = api.generate_signup_keypad(customer_id, username)"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 32
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Set nKode\n",
|
||||
"The client receives `user_icons`, `set_signup_keypad`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 33,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-28T15:06:18.931791Z",
|
||||
"start_time": "2025-03-28T15:06:18.929028Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"passcode_len = 4\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)"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 33
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 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."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 34,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-28T15:06:19.247638Z",
|
||||
"start_time": "2025-03-28T15:06:18.938601Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"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",
|
||||
"success = api.confirm_nkode(customer_id, selected_keys_confirm, signup_session_id)\n",
|
||||
"assert success"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 34
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### User Login\n",
|
||||
"1. Get login keypad\n",
|
||||
@@ -152,25 +158,25 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 35,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-28T15:06:19.559753Z",
|
||||
"start_time": "2025-03-28T15:06:19.254675Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"login_keypad = api.get_login_keypad(username, customer_id)\n",
|
||||
"selected_keys_login = select_keys_with_passcode_values(passcode_property_indices, login_keypad, keypad_size.props_per_key)\n",
|
||||
"success = api.login(customer_id, username, selected_keys_login)\n",
|
||||
"assert success"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 35
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Renew Properties\n",
|
||||
"Replace server-side ciphers keys and nkode hash with new values.\n",
|
||||
@@ -180,60 +186,60 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 36,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-28T15:06:20.181548Z",
|
||||
"start_time": "2025-03-28T15:06:19.568067Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"api.renew_keys(customer_id) # Steps 1 and 2\n",
|
||||
"login_keypad = api.get_login_keypad(username, customer_id)\n",
|
||||
"selected_keys_login = select_keys_with_passcode_values(passcode_property_indices, login_keypad, keypad_size.props_per_key)\n",
|
||||
"success = api.login(customer_id, username, selected_keys_login) # Step 3\n",
|
||||
"assert success"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 36
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 37,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-28T15:06:20.500050Z",
|
||||
"start_time": "2025-03-28T15:06:20.194912Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"login_keypad = api.get_login_keypad(username, customer_id)\n",
|
||||
"selected_keys_login = select_keys_with_passcode_values(passcode_property_indices, login_keypad, keypad_size.props_per_key)\n",
|
||||
"success = api.login(customer_id, username, selected_keys_login)\n",
|
||||
"assert success"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 37
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2",
|
||||
"version": "2.7.6"
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.14"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
||||
0
src/nkode_cipher_v2/__init__.py
Normal file
0
src/nkode_cipher_v2/__init__.py
Normal file
128
src/nkode_cipher_v2/nkode_cipher.py
Normal file
128
src/nkode_cipher_v2/nkode_cipher.py
Normal 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
|
||||
|
||||
37
test/test_nkode_cipher_keys.py
Normal file
37
test/test_nkode_cipher_keys.py
Normal 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))
|
||||
Reference in New Issue
Block a user