Compare commits
64 Commits
NumpyRefac
...
UpdateDocs
| Author | SHA1 | Date | |
|---|---|---|---|
| ccf1bac337 | |||
|
|
64ddd9f348 | ||
| 10c84e4535 | |||
| 437d8b0f31 | |||
| 81829c81b8 | |||
| 13a1a64772 | |||
| d22ec80ee7 | |||
| 6777a19f5b | |||
| 6ea7486d76 | |||
| 1e5fd26464 | |||
| d1b6f192af | |||
| 9a12b3b5e4 | |||
|
|
203973effa | ||
| 75168955fa | |||
| 8edf291cfa | |||
| ac86d6cf20 | |||
| 021e468494 | |||
| 8728bef61a | |||
| 242d23fe65 | |||
| 843563fb0c | |||
| daed7fad6f | |||
| f7d4e4411f | |||
| b9e144243d | |||
| ccaab11f45 | |||
| 8376fe7e87 | |||
| 407916e30c | |||
| 10c83ac965 | |||
| de1edb946a | |||
| 9a7e0ea5f9 | |||
| bb784d11c1 | |||
| 8a20ffdd5b | |||
| 9cd9b76741 | |||
| ee49b6cd53 | |||
| 4f4f0bcea7 | |||
| 851c547add | |||
| 3a9fadfc03 | |||
| 1846dc1065 | |||
| 0f7bda0942 | |||
| 538dc6b17c | |||
| 1f188e82ed | |||
| 5031062e87 | |||
| ba12ae69f4 | |||
| 2f4519f5d7 | |||
| ad5fc0e695 | |||
| 192ce80598 | |||
| d6c5e56a30 | |||
| 7b1ba996ae | |||
| fdcf31948f | |||
| 36f836421d | |||
| 9651455c1a | |||
| 65f3559ef0 | |||
| 7b92a6b40b | |||
| cfef58613c | |||
| f9f7081fc6 | |||
| fb650a4086 | |||
| 2a19aa73c4 | |||
| b6ab0c1890 | |||
| daebb61e56 | |||
| 303f4a7457 | |||
| 439b706fbd | |||
| 41a7e14fb4 | |||
| 1162bd54e1 | |||
| 1e2dfa9c9c | |||
| c6bf401bc5 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,5 @@
|
||||
.idea
|
||||
__pycache__
|
||||
.ipynb_checkpoints
|
||||
.DS_Store
|
||||
|
||||
|
||||
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": []
|
||||
}
|
||||
59
README.md
Normal file
59
README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# PynKode
|
||||
|
||||
pynkode is a tutorial of how nkode works.
|
||||
There are jupyter notebooks in /notebooks covering these topics:
|
||||
- dispersion
|
||||
- nkode enrollment, login, and renewal
|
||||
- split shuffle
|
||||
|
||||
## Installation
|
||||
|
||||
- Python version 3.10 or greater is required
|
||||
- Install conda (or your preferred tool) for environment management
|
||||
|
||||
### Using conda
|
||||
```bash
|
||||
conda env create -f environment.yml
|
||||
conda activate pynkode
|
||||
```
|
||||
|
||||
## Starting a Jupyter Notebook
|
||||
|
||||
### Option 1: Using classic Jupyter Notebook
|
||||
```bash
|
||||
# Ensure your environment is activated
|
||||
# For conda: conda activate pynkode
|
||||
# For pyenv: (should be automatic if in the directory)
|
||||
|
||||
# Start the Jupyter Notebook server
|
||||
jupyter notebook
|
||||
```
|
||||
|
||||
### Option 2: Using JupyterLab
|
||||
```bash
|
||||
# Ensure your environment is activated
|
||||
# Start JupyterLab
|
||||
jupyter lab
|
||||
```
|
||||
|
||||
## Explore the Docs
|
||||
You can find documentation in the [docs](docs) folder.
|
||||
|
||||
1. [Enrollment](docs/enrollment_diagram.md)
|
||||
2. [Login](docs/login_diagram.md)
|
||||
3. [nKode Cipher](docs/encipher_decipher_renew_nkode.md#nkode-cipher)
|
||||
4. [nKode Validation](docs/encipher_decipher_renew_nkode.md#validate-nkode)
|
||||
5. [Renew](docs/encipher_decipher_renew_nkode.md#renew-nkode)
|
||||
|
||||
|
||||
### Tutorials
|
||||
You can find tutorials in the [notebooks](notebooks) directory.
|
||||
|
||||
Recommended order:
|
||||
1. [Enrollment_Login_Renewal_Simplified.ipynb](notebooks/Enrollment_Login_Renewal_Simplified.ipynb) - Learn the basics of the nKode API
|
||||
2. [Enrollment_Login_Renewal_Detailed.ipynb](notebooks/Enrollment_Login_Renewal_Detailed.ipynb) - Learn the nKode API in detail
|
||||
3. [Dispersion.ipynb](notebooks/Dispersion.ipynb)- Understand the basic concepts of dispersion in nkode
|
||||
4. [Split_Shuffle.ipynb](notebooks/Split_Shuffle.ipynb) - Explore the split shuffle functionality
|
||||
|
||||
## Tabletop Discussion
|
||||
[notes](docs/tabletop-discussion.md)
|
||||
330
docs/encipher_decipher_renew_nkode.md
Normal file
330
docs/encipher_decipher_renew_nkode.md
Normal file
@@ -0,0 +1,330 @@
|
||||
# Encipher and Decipher nKode
|
||||
|
||||
## Customer Policy
|
||||
- max nkode length: 10
|
||||
- number of keys: 6
|
||||
- properties per key: 9
|
||||
- total number of properties: 54
|
||||
|
||||
## Customer Cipher
|
||||
- property key: [58201 3855 47017 50828 14104 4268 29079 19099 12271 55135 54341 2465
|
||||
32959 3356 36643 50702 8094 11335 39310 38981 55881 6507 22508 16345
|
||||
19379 55560 52385 29733 25178 37713 59696 16347 14811 20691 101 35545
|
||||
48103 63525 408 28174 57763 21416 59610 976 40160 13681 17146 54023
|
||||
25410 42165 3856 59580 27726 13822]
|
||||
- position key: [59066 9448 56848 58798 57675 37475 8528 34148 34468]
|
||||
|
||||
|
||||
---
|
||||
|
||||
## User Cipher
|
||||
- property key: [61737 49256 8018 54927 21709 65009 3885 46808 12375 11644 35339 52787
|
||||
60805 49964 2754 34451 22775 9278 14454 37226 46352 60033 49123 21025
|
||||
6479 60885 9059 20442 64510 63443 6758 52715 61370 9158 50160 8810
|
||||
27528 11812 34328 47745 43244 9125 55623 10800 49542 55251 26390 19027
|
||||
23252 5772 30639 25220 21506 59558]
|
||||
- passcode key: [54682 39190 21777 53204 58859 47387 24762 19698 44816 55464]
|
||||
- combined position key: [52800 11537 463 51813 36017 64303 52526 57594 11887]
|
||||
- mask key: [61564 54039 23310 1396 28615 39019 39047 46821 40711 32470]
|
||||
|
||||
### Combined Postion Key
|
||||
```mermaid
|
||||
block-beta
|
||||
columns 2
|
||||
user_pos["user position key:\n[19938 59072 31217 46834 52213 218 38377 1271 54783]"]
|
||||
customer_pos["customer position key:\n[59066 9448 56848 58798 57675 37475 8528 34148 34468]"]
|
||||
space:2
|
||||
xor(("XOR")):2
|
||||
user_pos --> xor
|
||||
customer_pos --> xor
|
||||
space:2
|
||||
comb_pos["combined position key\n[52800 11537 463 51813 36017 64303 52526 57594 11887]"]:2
|
||||
xor --> comb_pos
|
||||
```
|
||||
|
||||
## User Keypad
|
||||
- keypad example:<br/>Key 0: [27 19 38 30 31 41 6 7 26]<br/>Key 1: [36 37 11 48 4 23 51 34 44]<br/>Key 2: [45 1 20 12 49 50 15 52 53]<br/>Key 3: [18 46 47 3 22 5 33 43 35]<br/>Key 4: [ 9 28 29 21 13 14 42 16 17]<br/>Key 5: [ 0 10 2 39 40 32 24 25 8]<br/>
|
||||
- user passcode indices: [30, 38, 11, 51]
|
||||
|
||||
## nKode Cipher
|
||||
|
||||
### Passcode Hash
|
||||
```mermaid
|
||||
block-beta
|
||||
columns 2
|
||||
cprop["customer_property_key\n[58201 3855 47017 50828 14104 4268 29079 19099 12271 55135 54341 2465
|
||||
32959 3356 36643 50702 8094 11335 39310 38981 55881 6507 22508 16345
|
||||
19379 55560 52385 29733 25178 37713 59696 16347 14811 20691 101 35545
|
||||
48103 63525 408 28174 57763 21416 59610 976 40160 13681 17146 54023
|
||||
25410 42165 3856 59580 27726 13822]"]
|
||||
uprop["user_property_key\n[61737 49256 8018 54927 21709 65009 3885 46808 12375 11644 35339 52787
|
||||
60805 49964 2754 34451 22775 9278 14454 37226 46352 60033 49123 21025
|
||||
6479 60885 9059 20442 64510 63443 6758 52715 61370 9158 50160 8810
|
||||
27528 11812 34328 47745 43244 9125 55623 10800 49542 55251 26390 19027
|
||||
23252 5772 30639 25220 21506 59558]"]
|
||||
space:2
|
||||
xor1(("XOR")):2
|
||||
cprop --> xor1
|
||||
uprop --> xor1
|
||||
space:2
|
||||
prop["combined_property_key\n[27145 64606 2214 956 59410 24299 43596 51759 39102 28353 20824 14874
|
||||
27994 60897 60356 49474 41290 62135 52049 26214 37014 45529 43514 40298
|
||||
25315 52116 23008 2744 3269 7285 34470 4311 21401 14127 50809 13541
|
||||
5394 30989 34092 27076 35814 63505 33710 18123 25966 61467 19353 14805
|
||||
58907 44436 41458 3440 7755 35403]"]
|
||||
xor1 --> prop
|
||||
pass["user_passcode_indices\n[30, 38, 11, 51]"]
|
||||
space:2
|
||||
sel(("select\nproperties")):2
|
||||
pass --> sel
|
||||
prop --> sel
|
||||
space:2
|
||||
passcode["user passcode properties:\n[34470 34092 14874 3440]"]:2
|
||||
sel --> passcode
|
||||
space:2
|
||||
pad["zero pad to\nmax nkode length: 10"]:2
|
||||
passcode -->pad
|
||||
space:2
|
||||
paddedpasscode["padded passcode:\n[34470 34092 14874 3440 0 0 0 0 0 0]"]
|
||||
pad --> paddedpasscode
|
||||
passkey["passcode key:\n[54682 39190 21777 53204 58859 47387 24762 19698 44816 55464]"]
|
||||
space:2
|
||||
xor2(("XOR")):2
|
||||
passkey --> xor2
|
||||
paddedpasscode --> xor2
|
||||
space:2
|
||||
cipheredpass["ciphered passcode:\n[21308 7226 28427 49828 58859 47387 24762 19698 44816 55464]"]:2
|
||||
xor2 --> cipheredpass
|
||||
space:2
|
||||
hash(("hash")):2
|
||||
cipheredpass --> hash
|
||||
space:2
|
||||
cipheredhashed["hashed ciphered passcode:\n$2b$12$uR5ilcM4r.xjbzW4kGWf2.tbGVWSwrnqp1s6sGzqUmgcE1ncQK8UW"]:2
|
||||
hash --> cipheredhashed
|
||||
```
|
||||
|
||||
### Mask Encipher
|
||||
```mermaid
|
||||
block-beta
|
||||
columns 3
|
||||
passcode_idx["passcode indices:\n[30, 38, 11, 51]"]
|
||||
comb_pos["combined position key:\n[52800 11537 463 51813 36017 64303 52526 57594 11887]"]
|
||||
cust_pos["customer position key:\n[59066 9448 56848 58798 57675 37475 8528 34148 34468]"]
|
||||
|
||||
space:3
|
||||
propidx(["Get Position Idx:\nmap each to element mod props_per_key"])
|
||||
passcode_idx-->propidx
|
||||
space:1
|
||||
xor1(("XOR"))
|
||||
comb_pos --> xor1
|
||||
cust_pos --> xor1
|
||||
|
||||
space:3
|
||||
passcode_position_idx["passcode poition indices:\n[3, 2, 2, 6]"]
|
||||
propidx --> passcode_position_idx
|
||||
|
||||
space:5
|
||||
pad1(("Pad with\nrandom indices"))
|
||||
passcode_position_idx --> pad1
|
||||
|
||||
space:5
|
||||
posidx["Padded Passcode Position Indices:\n[3, 2, 2, 6, 1, 2, 3, 6, 8, 5]"]
|
||||
pad1 --> posidx
|
||||
space:1
|
||||
user_pos["user position key:\n[19938 59072 31217 46834 52213 218 38377 1271 54783]"]
|
||||
xor1 --> user_pos
|
||||
|
||||
space:4
|
||||
sel(("select positions"))
|
||||
user_pos --> sel
|
||||
posidx --> sel
|
||||
space:5
|
||||
passcode_pos["ordered user passcode positions:\n[46834 31217 31217 38377 59072 31217 46834 38377 54783 218]"]
|
||||
sel --> passcode_pos
|
||||
mask_key["mask key\n[61564 54039 23310 1396 28615 39019 39047 46821 40711 32470]"]
|
||||
space:4
|
||||
xor2(("XOR"))
|
||||
mask_key --> xor2
|
||||
passcode_pos --> xor2
|
||||
space:5
|
||||
mask["enciphered mask:\n [18062 43750 8959 37021 35079 57754 11893 8972 19192 32268]"]
|
||||
xor2 --> mask
|
||||
```
|
||||
|
||||
### Validate nKode
|
||||
|
||||
```mermaid
|
||||
block-beta
|
||||
columns 3
|
||||
selected_keys["keys selected by user during login:\n[0, 0, 1, 1]"]
|
||||
login_keypad["login keypad:\nKey 0: [27 19 38 30 31 41 6 7 26]
|
||||
Key 1: [36 37 11 48 4 23 51 34 44]
|
||||
Key 2: [45 1 20 12 49 50 15 52 53]
|
||||
Key 3: [18 46 47 3 22 5 33 43 35]
|
||||
Key 4: [ 9 28 29 21 13 14 42 16 17]
|
||||
Key 5: [ 0 10 2 39 40 32 24 25 8]
|
||||
"]
|
||||
space:4
|
||||
|
||||
selectkeys(("filter keys"))
|
||||
mask["enciphered mask:\n [18062 43750 8959 37021 35079 57754 11893 8972 19192 32268]"]
|
||||
mask_key["mask key:\n[61564 54039 23310 1396 28615 39019 39047 46821 40711 32470]"]
|
||||
space:2
|
||||
|
||||
xor1(("XOR"))
|
||||
mask --> xor1
|
||||
mask_key --> xor1
|
||||
selected_keys --> selectkeys
|
||||
login_keypad --> selectkeys
|
||||
space:3
|
||||
|
||||
ordered_keys["ordered keys:\n[[27 19 38 30 31 41 6 7 26]
|
||||
[27 19 38 30 31 41 6 7 26]
|
||||
[36 37 11 48 4 23 51 34 44]
|
||||
[36 37 11 48 4 23 51 34 44]]"]
|
||||
user_position_key["user position key:\n[19938 59072 31217 46834 52213 218 38377 1271 54783]"]
|
||||
passcode_pos["ordered user passcode positions:\n[46834 31217 31217 38377 59072 31217 46834 38377 54783 218]"]
|
||||
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, 2, 2, 6, 1, 2, 3, 6, 8, 5]"]
|
||||
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[30, 38, 11, 51]"]
|
||||
prop["combined_property_key\n[27145 64606 2214 956 59410 24299 43596 51759 39102 28353 20824 14874
|
||||
27994 60897 60356 49474 41290 62135 52049 26214 37014 45529 43514 40298
|
||||
25315 52116 23008 2744 3269 7285 34470 4311 21401 14127 50809 13541
|
||||
5394 30989 34092 27076 35814 63505 33710 18123 25966 61467 19353 14805
|
||||
58907 44436 41458 3440 7755 35403]"]
|
||||
cipheredhashed["hashed ciphered passcode:\n$2b$12$uR5ilcM4r.xjbzW4kGWf2.tbGVWSwrnqp1s6sGzqUmgcE1ncQK8UW"]
|
||||
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[34470 34092 14874 3440]"]
|
||||
sel --> passcode_prop
|
||||
space:5
|
||||
|
||||
cipher(("encipher"))
|
||||
passcode_prop --> cipher
|
||||
space:5
|
||||
|
||||
cipheredpass["ciphered passcode:\n[21308 7226 28427 49828 58859 47387 24762 19698 44816 55464]"]
|
||||
cipher --> cipheredpass
|
||||
space:7
|
||||
|
||||
|
||||
comp{"compare"}
|
||||
cipheredpass --> comp
|
||||
cipheredhashed --> comp
|
||||
space:5
|
||||
|
||||
suc(("success"))
|
||||
comp --"Equal"--> suc
|
||||
```
|
||||
|
||||
### Renew nKode
|
||||
|
||||
nKode renewal is a three step process:
|
||||
1. Renew Customer Keys
|
||||
2. Intermediate User Keys
|
||||
3. Renew User Keys on Login
|
||||
|
||||
|
||||
```mermaid
|
||||
flowchart
|
||||
subgraph Renew Customer Keys
|
||||
old_prop["`old customer property key:<br/>[39712 15414 6132 54579 48351 41754 42337 31991 43241 17341 56147 62505
|
||||
32991 11981 57606 18385 63933 54921 62247 63244 9606 23384 5657 53067
|
||||
31660 9793 31363 17762 63291 60326 40128 56636 48163 5353 1417 5775
|
||||
32410 22313 820 54085 8970 56244 23273 27899 42216 10184 11407 29574
|
||||
48335 47896 54877 28660 19017 25325]`"]
|
||||
new_prop["`new customer property key:<br/>[58201 3855 47017 50828 14104 4268 29079 19099 12271 55135 54341 2465
|
||||
32959 3356 36643 50702 8094 11335 39310 38981 55881 6507 22508 16345
|
||||
19379 55560 52385 29733 25178 37713 59696 16347 14811 20691 101 35545
|
||||
48103 63525 408 28174 57763 21416 59610 976 40160 13681 17146 54023
|
||||
25410 42165 3856 59580 27726 13822]`"]
|
||||
old_pos["`old customer position key:<br/>[33698 52177 30782 31895 18244 64501 22727 58381 64400]`"]
|
||||
new_pos["`new customer position key:<br/>[59066 9448 56848 58798 57675 37475 8528 34148 34468]`"]
|
||||
xor1(("XOR"))
|
||||
xor2(("XOR"))
|
||||
xor_prop["`xor property key:<br/>[30841 13113 41053 5055 35783 46006 54518 13932 34566 38114 3862 64904
|
||||
96 9169 28197 33247 58915 64206 27305 28489 65487 16947 16885 61586
|
||||
12319 65353 46626 12615 38241 30967 30192 58087 34296 17466 1516 40022
|
||||
50557 44812 684 48459 49833 34844 45619 28459 14344 4793 28277 41089
|
||||
57229 8109 55629 34632 9735 22291]`"]
|
||||
xor_pos["`xor position key:<br/>[25880 61241 42542 39225 42511 27030 31127 24937 32052]`"]
|
||||
old_prop --> xor1
|
||||
new_prop --> xor1
|
||||
xor1 --> xor_prop
|
||||
old_pos --> xor2
|
||||
new_pos --> xor2
|
||||
xor2 --> xor_pos
|
||||
end
|
||||
|
||||
subgraph Intermediate User Keys
|
||||
users@{shape: procs, label: "users"}
|
||||
users --> eachuser
|
||||
subgraph eachuser [for each user]
|
||||
subgraph old user keys
|
||||
old_user_pos["`combined position key:<br/>[19938 59072 31217 46834 52213 218 38377 1271 54783]`"]
|
||||
old_user_prop["`property key:<br/>[61737 49256 8018 54927 21709 65009 3885 46808 12375 11644 35339 52787
|
||||
60805 49964 2754 34451 22775 9278 14454 37226 46352 60033 49123 21025
|
||||
6479 60885 9059 20442 64510 63443 6758 52715 61370 9158 50160 8810
|
||||
27528 11812 34328 47745 43244 9125 55623 10800 49542 55251 26390 19027
|
||||
23252 5772 30639 25220 21506 59558]`"]
|
||||
old_renew["renew: False"]
|
||||
end
|
||||
xor3(("XOR"))
|
||||
xor4(("XOR"))
|
||||
old_user_pos --> xor3
|
||||
xor_pos --> xor3
|
||||
xor3 --> inter_user_pos
|
||||
old_user_prop --> xor4
|
||||
xor_prop --> xor4
|
||||
xor4 --> inter_user_prop
|
||||
subgraph inter_user[intermediate user keys]
|
||||
inter_user_pos["`combined position key:<br/>[43864 49704 42977 21340 10942 37561 46265 33171 21339]`"]
|
||||
inter_user_prop["`property key:<br/>[35152 62289 48911 50480 57098 20039 56283 32948 46929 47518 34077 13243
|
||||
60901 57597 25831 1868 48852 57072 21215 65059 19167 43186 65046 41651
|
||||
10576 4764 38209 32413 28319 36644 28566 12044 27202 26620 50716 48700
|
||||
44789 33064 33972 1994 27205 43961 27508 17691 63886 50538 2403 60114
|
||||
34137 2337 44770 58828 29189 49077]`"]
|
||||
inter_renew["renew: True"]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
subgraph Renew User Keys on Login
|
||||
login["First login post renew"]
|
||||
inter_user --> login
|
||||
subgraph new_user [New User Keys]
|
||||
new_user_pos["`combined position key:<br/>[ 6533 37152 58679 48102 36450 2846 47432 25286 7670]`"]
|
||||
new_user_prop["`property key:<br/>[46243 7833 58428 7967 9894 32124 48453 54560 26021 20475 10880 7429
|
||||
26439 31401 37980 56964 32338 15187 9442 19302 53060 31276 34465 43143
|
||||
33088 8587 47204 47532 45177 35842 11142 47055 40284 49647 9306 58542
|
||||
63251 40940 32915 53183 16853 43890 60339 30610 6078 59395 7618 59221
|
||||
44934 37153 10149 59493 8228 59563]`"]
|
||||
new_renew["renew: False"]
|
||||
end
|
||||
login --> new_user
|
||||
end
|
||||
|
||||
```
|
||||
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.
|
||||
37
docs/enrollment_diagram.md
Normal file
37
docs/enrollment_diagram.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# nKode Enrollment
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Client
|
||||
participant Server
|
||||
Note over User,Server: Enrollment
|
||||
Client->>User: Signup Form
|
||||
Note left of User: email: user@example.com
|
||||
User->>Client: Submit Email
|
||||
Client->>+Server: Signup Session: user@example.com
|
||||
Server->>Server: Create Signup Session
|
||||
Note over User,Server: Set nKode
|
||||
Server-->>-Client: signup_session_id, set_keypad, icons
|
||||
Note left of Server: signup_session_id:<br/>1334d391-3c8a-431a-8186-e91b29d42750
|
||||
Note left of Server: set_keypad:<br/>Key 0: [18 2 39 13 23 16]<br/>Key 1: [36 20 21 49 5 43]<br/>Key 2: [ 0 47 48 4 50 25]<br/>Key 3: [27 38 12 40 32 52]<br/>Key 4: [45 29 3 31 41 34]<br/>Key 5: [ 9 11 30 22 14 7]<br/>
|
||||
Note left of Server: Icons:<br/>[🍎,🍏,🍊,🍋,🍌,🍉<br/>🍇,🍓,🍒,🍑,🥭,🍍<br/>🥥,🥝,🍅,🍆,🥑,🥕<br/>🌽,🥔,🍠,🥐,🥖,🥨<br/>🥯,🥞,🧀,🍖,🍗,🥚<br/>🍔,🍟,🍕,🌭,🥪,🌮<br/>🌯,🍣,🍤,🍙,🍚,🍜<br/>🍲,🍛,🍱,🥟,🍦,🍧<br/>🍨,🍩,🍪,🎂,🍰,🧁]
|
||||
Client->>Client: Order Icons by keypad
|
||||
Client->>User: Display Keypad
|
||||
Note left of Client: Key 0: ['🌽' '🍊' '🍙' '🥝' '🥨' '🥑']<br/>Key 1: ['🌯' '🍠' '🥐' '🍩' '🍉' '🍛']<br/>Key 2: ['🍎' '🍧' '🍨' '🍌' '🍪' '🥞']<br/>Key 3: ['🍖' '🍤' '🥥' '🍚' '🍕' '🍰']<br/>Key 4: ['🥟' '🥚' '🍋' '🍟' '🍜' '🥪']<br/>Key 5: ['🍑' '🍍' '🍔' '🥖' '🍅' '🍓']<br/>
|
||||
Note left of User: User icons: ['🍌' '🍟' '🌽' '🥝']
|
||||
User->>Client: Set Key Selection: [2, 4, 0, 0]
|
||||
Client->>+Server: Set nKode:<br/>1334d391-3c8a-431a-8186-e91b29d42750<br/>[2, 4, 0, 0]
|
||||
Server->>Server: Disperse Set Keypad
|
||||
Note over User,Server: Confirm nKode
|
||||
Server-->>-Client: signup_session_id, confirm_keypad, icons
|
||||
Note left of Server: signup_session_id:<br/>1334d391-3c8a-431a-8186-e91b29d42750
|
||||
Note left of Server: confirm_keypad:<br/>Key 0: [27 47 3 13 14 43]<br/>Key 1: [ 0 29 30 49 23 52]<br/>Key 2: [45 11 39 40 5 25]<br/>Key 3: [ 9 2 21 4 32 34]<br/>Key 4: [18 20 12 31 50 7]<br/>Key 5: [36 38 48 22 41 16]<br/>
|
||||
Client->>Client: Order Icons by keypad
|
||||
Client->>User: Display Keypad
|
||||
Note left of Client: Key 0: ['🍖' '🍧' '🍋' '🥝' '🍅' '🍛']<br/>Key 1: ['🍎' '🥚' '🍔' '🍩' '🥨' '🍰']<br/>Key 2: ['🥟' '🍍' '🍙' '🍚' '🍉' '🥞']<br/>Key 3: ['🍑' '🍊' '🥐' '🍌' '🍕' '🥪']<br/>Key 4: ['🌽' '🍠' '🥥' '🍟' '🍪' '🍓']<br/>Key 5: ['🌯' '🍤' '🍨' '🥖' '🍜' '🥑']<br/>
|
||||
Note left of User: User icons: ['🍌' '🍟' '🌽' '🥝']
|
||||
User->>Client: Key Selection: [3, 4, 4, 0]
|
||||
Client->>+Server: Confirm nKode:<br/>1334d391-3c8a-431a-8186-e91b29d42750<br/>[3, 4, 4, 0]
|
||||
Server->>Server: Create User
|
||||
Server-->>-Client: Success
|
||||
```
|
||||
22
docs/login_diagram.md
Normal file
22
docs/login_diagram.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# nKode Login
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Client
|
||||
participant Server
|
||||
Note over User,Server: Login
|
||||
Client->>User: Login Form
|
||||
Note left of User: email: user@example.com
|
||||
User->>Server: Submit Email
|
||||
Server->>Client: login_keypad, icons
|
||||
Note left of Server: Login Keypad:<br/>Key 0: [18 46 2 39 13 23 6 16 53]<br/>Key 1: [36 28 20 21 49 5 15 43 8]<br/>Key 2: [ 0 10 47 48 4 50 51 25 35]<br/>Key 3: [27 1 38 12 40 32 42 52 17]<br/>Key 4: [45 37 29 3 31 41 24 34 44]<br/>Key 5: [ 9 19 11 30 22 14 33 7 26]<br/>
|
||||
Note left of Server: Icons:<br/>[🍎,🍏,🍊,🍋,🍌,🍉<br/>🍇,🍓,🍒,🍑,🥭,🍍<br/>🥥,🥝,🍅,🍆,🥑,🥕<br/>🌽,🥔,🍠,🥐,🥖,🥨<br/>🥯,🥞,🧀,🍖,🍗,🥚<br/>🍔,🍟,🍕,🌭,🥪,🌮<br/>🌯,🍣,🍤,🍙,🍚,🍜<br/>🍲,🍛,🍱,🥟,🍦,🍧<br/>🍨,🍩,🍪,🎂,🍰,🧁]
|
||||
Client->>Client: Order Icons
|
||||
Client->>User: Display Keypad
|
||||
Note left of Client: Key 0: ['🌽' '🍦' '🍊' '🍙' '🥝' '🥨' '🍇' '🥑' '🧁']<br/>Key 1: ['🌯' '🍗' '🍠' '🥐' '🍩' '🍉' '🍆' '🍛' '🍒']<br/>Key 2: ['🍎' '🥭' '🍧' '🍨' '🍌' '🍪' '🎂' '🥞' '🌮']<br/>Key 3: ['🍖' '🍏' '🍤' '🥥' '🍚' '🍕' '🍲' '🍰' '🥕']<br/>Key 4: ['🥟' '🍣' '🥚' '🍋' '🍟' '🍜' '🥯' '🥪' '🍱']<br/>Key 5: ['🍑' '🥔' '🍍' '🍔' '🥖' '🍅' '🌭' '🍓' '🧀']<br/>
|
||||
Note left of User: User passcode icons: ['🍌' '🍟' '🌽' '🥝']
|
||||
User->>Client: Selected Keys<br/>[2, 4, 0, 0]
|
||||
Client->>Server: Login:<br/>email: user@example.com<br/>selected_keys: [2, 4, 0, 0]
|
||||
Server-->>Client: Success
|
||||
```
|
||||
@@ -1,422 +0,0 @@
|
||||
# nKode Authentication
|
||||
Play around with the code in [this](http://sesolgit/Repository/Blob/92a60227-4ef9-4196-8ebb-595581abf98c?encodedName=main&encodedPath=nkode_tutorial.ipynb) jupyter notebook.
|
||||
|
||||
## Customer Creation
|
||||
Before creating a user, a customer generates random attributes and set
|
||||
values. The customers manage users. They define an nKode policy, keypad's dimensions,
|
||||
attributes/sets in the keypad, and the frequency of attribute renewal.
|
||||
### nKode Policy and Keypad Size
|
||||
An nKode policy defines:
|
||||
<ul>
|
||||
<li>the maximum length of a user's nKode</li>
|
||||
<li>the minimum length of a user's nKode</li>
|
||||
<li>the number of unique set values in a user's nKode</li>
|
||||
<li>the number of unique values in a user's nKode</li>
|
||||
<li>the number of bytes in an attribute and set</li>
|
||||
</ul>
|
||||
|
||||
The keypad size defines:
|
||||
<ul>
|
||||
<li>the number of keys in the keypad displayed to the user</li>
|
||||
<li>attributes per key</li>
|
||||
</ul>
|
||||
|
||||
To be [dispersion](nkode_concepts.md/#dispersion-resistant-interface) resistant, the number of attributes must be greater than the number of keys.
|
||||
|
||||
```
|
||||
api = NKodeAPI()
|
||||
|
||||
policy = NKodePolicy(
|
||||
max_nkode_len=10,
|
||||
min_nkode_len=4,
|
||||
distinct_sets=0,
|
||||
distinct_attributes=4,
|
||||
byte_len=2
|
||||
)
|
||||
|
||||
keypad_size = KeypadSize(
|
||||
numb_of_keys = {{ keypad_size.numb_of_keys }},
|
||||
props_per_key = {{ keypad_size.props_per_key }} # aka number of sets
|
||||
)
|
||||
|
||||
customer_id = api.create_new_customer(keypad_size, policy)
|
||||
customer = api.customers[customer_id]
|
||||
```
|
||||
### Customer Attributes and Sets
|
||||
A customer has users and defines the attributes and set values for all its users.
|
||||
Since our customer has {{ keypad_size.numb_of_keys }} keys and {{ keypad_size.props_per_key }} attributes per key,
|
||||
this gives a customer interface of {{ keypad_size.numb_of_props }} distinct attributes and {{ keypad_size.props_per_key }} distinct attribute sets.
|
||||
Each attribute belongs to one of the {{ keypad_size.props_per_key }} sets. Each attribute and set value is a unique 2-byte integer in this example.
|
||||
|
||||
```
|
||||
set_vals = customer.attributes.set_vals
|
||||
|
||||
Customer Sets: {{ customer_set_vals }}
|
||||
```
|
||||
|
||||
```
|
||||
attr_vals = customer.attributes.attr_vals
|
||||
keypad_view(attr_vals, keypad_size.props_per_key)
|
||||
|
||||
Customer Attributes:
|
||||
{% for attrs in customer_attr_view -%}
|
||||
{{ attrs }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Attributes organized by set:
|
||||
```
|
||||
attr_set_view = matrix_transpose(attr_keypad_view)
|
||||
set_attribute_dict = dict(zip(set_vals, attr_set_view))
|
||||
|
||||
Set to Attribute Map:
|
||||
{% for set_val, attrs in set_attribute_dict.items() -%}
|
||||
{{ set_val }} : {{ attrs }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
## User Signup
|
||||
Now that we have a customer, we can create users. To create a new user:
|
||||
|
||||
1. Generate a random interface
|
||||
2. The user sets their nKode and sends their selection to the server
|
||||
3. The user confirms their nKode. If the user's nKode matches the policy, the server creates the user.
|
||||
### Random Interface Generation
|
||||
The user's interface must be dispersable so the server can determine the user's nkode.
|
||||
The server randomly drops attribute sets until
|
||||
the number of attributes equals the number of keys, making the interface dispersable.
|
||||
In our case, the server randomly drops {{ keypad_size.props_per_key - keypad_size.numb_of_keys }} attribute {{ "sets" if keypad_size.props_per_key - keypad_size.numb_of_keys > 1 else "set" }}.
|
||||
to give us a {{ keypad_size.numb_of_keys }} X {{ keypad_size.numb_of_keys }} keypad with possible index values ranging from 0-{{ keypad_size.numb_of_props - 1 }}.
|
||||
Each value in the interface is the index value of a customer attribute.
|
||||
The user never learns what their "real" attribute is. They do not see the index value representing their nKode or
|
||||
the customer server-side value.
|
||||
|
||||
```
|
||||
session_id, signup_interface = api.generate_index_interface(customer_id)
|
||||
signup_interface_keypad = list_to_matrix(signup_interface, keypad_size.props_per_key)
|
||||
|
||||
Signup Keypad:
|
||||
{% for key in signup_keypad -%}
|
||||
Key {{ loop.index }}: {{ key }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
### Set nKode
|
||||
The user identifies attributes in the interface they want in their nkode. Each attribute has an index value.
|
||||
Below, the user has selected `{{ user_passcode }}`. These index values can be represented by anything in the GUI.
|
||||
The only requirement is that the GUI attributes be associated with the same index every time the user logs in.
|
||||
If users want to change anything about their interface, they must also change their nkode.
|
||||
|
||||
```
|
||||
username = {{ username }}
|
||||
user_passcode = {{ user_passcode }}
|
||||
selected_keys_set = select_keys_with_passcode_values(user_passcode, signup_interface, keypad_size.props_per_key)
|
||||
|
||||
Selected Keys
|
||||
{{ selected_keys_set }}
|
||||
```
|
||||
|
||||
The user's passcode server side attributes are:
|
||||
```
|
||||
server_side_attr = [customer.attributes.attr_vals[idx] for idx in user_passcode]
|
||||
|
||||
User Passcode Server-side Attributes: {{ server_side_attr }}
|
||||
```
|
||||
|
||||
### Confirm nKode
|
||||
The user submits the set interface to the server and receives the _confirm interface_ as a response.
|
||||
The user finds their nKode again.
|
||||
|
||||
```
|
||||
confirm_interface = api.set_nkode(username, customer_id, selected_keys_set, session_id)
|
||||
keypad_view(confirm_interface, keypad_size.numb_of_keys)
|
||||
selected_keys_confirm = select_keys_with_passcode_values(user_passcode, confirm_interface, keypad_size.numb_of_keys)
|
||||
|
||||
Confirm Keypad:
|
||||
{% for key in confirm_keypad -%}
|
||||
Key {{ loop.index }}: {{ key }}
|
||||
{% endfor %}
|
||||
Selected Keys:
|
||||
{{ selected_keys_confirm }}
|
||||
```
|
||||
|
||||
The user submits their confirmation key selection and the user is created
|
||||
```
|
||||
success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id)
|
||||
```
|
||||
|
||||
### Passcode Enciphering, Hashing, and Salting
|
||||
When a new user creates an nKode, the server caches its set and confirms the interface and the user's key selection.
|
||||
On the last api.confirm_nkode, the server:
|
||||
|
||||
1. Deduces the user's attributes
|
||||
2. Validates the Passcode against the nKodePolicy
|
||||
3. Creates new User Cipher Keys
|
||||
4. Enciphers the user's mask
|
||||
5. Enciphers, salts, and hashes the user's passcode
|
||||
|
||||
Steps 1-2 are straightforward. For a better idea of how they work, see pyNKode.
|
||||
|
||||
#### User Cipher Keys
|
||||
|
||||
##### User Cipher Keys Data Structure
|
||||
```
|
||||
set_key = generate_random_nonrepeating_list(keypad_size.props_per_key, max_numb=2**(8*numb_of_bytes))
|
||||
set_key = xor_lists(set_key, customer_attr.set_vals)
|
||||
|
||||
UserCipherKeys(
|
||||
prop_key=generate_random_nonrepeating_list(keypad_size.props_per_key * keypad_size.numb_of_keys, max_numb=2**(8*numb_of_bytes)),
|
||||
pass_key=generate_random_nonrepeating_list(max_nkode_len, max_numb=2**(8*numb_of_bytes)),
|
||||
mask_key=generate_random_nonrepeating_list(max_nkode_len, max_numb=2**(8*numb_of_bytes)),
|
||||
set_key=set_key,
|
||||
salt=bcrypt.gensalt(),
|
||||
max_nkode_len=max_nkode_len
|
||||
)
|
||||
```
|
||||
|
||||
##### User Cipher Keys Values
|
||||
```
|
||||
user_cipher = UserCipherKeys(
|
||||
prop_key = {{ user_cipher.prop_key }},
|
||||
pass_key = {{ user_cipher.pass_key }},
|
||||
mask_key = {{ user_cipher.mask_key }},
|
||||
set_key = {{ user_cipher.set_key }},
|
||||
salt = {{ user_cipher.salt }},
|
||||
max_nkode_len = {{ user_cipher.max_nkode_len }}
|
||||
)
|
||||
```
|
||||
|
||||
The method UserCipherKeys.encipher_nkode secures a user's nKode in the database. This method is called in api.confirm_nkode
|
||||
|
||||
```
|
||||
class EncipheredNKode(BaseModel):
|
||||
code: str
|
||||
mask: str
|
||||
```
|
||||
|
||||
#### Mask Enciphering
|
||||
|
||||
Recall:
|
||||
|
||||
- set_key_i = (set_rand_numb_i ^ set_val_i)
|
||||
- mask_key_i = mask_rand_numb_i
|
||||
- padded_passcode_server_set_i = set_val_i
|
||||
- len(set_key) == len(mask_key) == (padded_passcode_server_set) == max_nkode_len == 10
|
||||
where i is the index
|
||||
|
||||
- mask_i = mask_key_i ^ padded_passcode_server_set_i ^ set_key_i
|
||||
- mask_i = mask_rand_num_i ^ set_val_i ^ set_rand_numb_i ^ set_val_i
|
||||
- mask_i = mask_rand_num_i ^ set_rand_numb_i # set_val_i is cancelled out
|
||||
|
||||
|
||||
```
|
||||
passcode = {{ user_passcode }}
|
||||
passcode_server_attr = [customer.attributes.attr_vals[idx] for idx in passcode]
|
||||
passcode_server_set = [customer.attributes.get_attr_set_val(attr) for attr in passcode_server_attr]
|
||||
|
||||
Passcode Set Vals: {{ passcode_server_attr }}
|
||||
Passcode Attr Vals: {{ passcode_server_set }}
|
||||
```
|
||||
|
||||
```
|
||||
padded_passcode_server_set = user_cipher.pad_user_mask(passcode_server_set, customer.nkode_policy.max_nkode_len)
|
||||
|
||||
set_idx = [customer.attributes.get_set_index(set_val) for set_val in padded_passcode_server_set]
|
||||
mask_set_keys = [user_cipher.set_key[idx] for idx in set_idx]
|
||||
|
||||
ciphered_mask = xor_lists(mask_set_keys, padded_passcode_server_set)
|
||||
ciphered_mask = xor_lists(ciphered_mask, user_cipher.mask_key)
|
||||
|
||||
mask = user_cipher.encode_base64_str(ciphered_mask)
|
||||
Mask: {{ enciphered_nkode.mask }}
|
||||
```
|
||||
|
||||
#### Passcode Enciphering and Hashing
|
||||
|
||||
- ciphered_customer_attr = prop_key ^ customer_attr
|
||||
- ciphered_passcode_i = pass_key_i ^ ciphered_customer_attr_i
|
||||
- code = hash(ciphered_passcode, salt)
|
||||
|
||||
```
|
||||
ciphered_customer_attrs = xor_lists(customer.attributes.attr_vals, user_cipher.prop_key)
|
||||
passcode_ciphered_attrs = [ciphered_customer_attrs[idx] for idx in passcode]
|
||||
pad_len = customer.nkode_policy.max_nkode_len - passcode_len
|
||||
|
||||
passcode_ciphered_attrs.extend([0 for _ in range(pad_len)])
|
||||
|
||||
ciphered_code = xor_lists(passcode_ciphered_attrs, user_cipher.pass_key)
|
||||
|
||||
passcode_bytes = int_array_to_bytes(ciphered_code)
|
||||
passcode_digest = base64.b64encode(hashlib.sha256(passcode_bytes).digest())
|
||||
hashed_data = bcrypt.hashpw(passcode_digest, user_cipher.salt)
|
||||
code = hashed_data.decode("utf-8")
|
||||
|
||||
Code: {{ enciphered_nkode.code }}
|
||||
```
|
||||
|
||||
## User Login
|
||||
To login, a user:
|
||||
|
||||
1. Gets login interface
|
||||
2. Submits key entry
|
||||
|
||||
### Get Login Interface
|
||||
The client requests the user's login interface.
|
||||
```
|
||||
login_interface = api.get_login_interface(username, customer_id)
|
||||
keypad_view(login_interface, keypad_size.props_per_key)
|
||||
```
|
||||
The server returns a randomly shuffled interface. Learn more about how the [User Interface Shuffle](nkode_concepts.md/#user-interface-shuffle) works
|
||||
```
|
||||
Login Interface Keypad View:
|
||||
{% for key in login_keypad -%}
|
||||
Key {{ loop.index }}: {{ key }}
|
||||
{% endfor %}
|
||||
```
|
||||
Recall the user's passcode is `user_passcode = {{ user_passcode }}` so the user selects keys ` selected_keys_login = {{ selected_login_keys }}`
|
||||
|
||||
```
|
||||
success = api.login(customer_id, username, selected_keys_login)
|
||||
```
|
||||
|
||||
### Validate Login Key Entry
|
||||
- decipher user mask and recover nkode set values
|
||||
- get presumed attribute from key selection and set values
|
||||
- encipher, salt, and hash presumed attribute values and compare them to the users hashed code
|
||||
|
||||
#### Decipher Mask
|
||||
|
||||
Recall:
|
||||
|
||||
- set_key_i = (set_key_rand_numb_i ^ set_val_i)
|
||||
- mask_i = mask_key_rand_num_i ^ set_key_rand_numb_i
|
||||
|
||||
Recover nKode set values:
|
||||
|
||||
- decode mask from base64 to int
|
||||
- deciphered_mask = mask ^ mask_key
|
||||
- deciphered_mask_i = set_key_rand_numb # mask_key_rand_num_i is cancelled out
|
||||
- set_key_rand_component = set_key ^ set_values
|
||||
- deduce the set value
|
||||
|
||||
```
|
||||
user = customer.users[username]
|
||||
user_cipher = user.user_cipher
|
||||
user_mask = user.enciphered_passcode.mask
|
||||
decoded_mask = user_cipher.decode_base64_str(user_mask)
|
||||
deciphered_mask = xor_lists(decoded_mask, user_cipher.mask_key)
|
||||
set_key_rand_component = xor_lists(set_vals, user_cipher.set_key)
|
||||
passcode_sets = []
|
||||
for set_cipher in deciphered_mask[:passcode_len]:
|
||||
set_idx = set_key_rand_component.index(set_cipher)
|
||||
passcode_sets.append(set_vals[set_idx])
|
||||
|
||||
Passcode Sets: {{ login_passcode_sets }}
|
||||
```
|
||||
|
||||
|
||||
### Get Presumed Attributes
|
||||
```
|
||||
set_vals_idx = [customer.attributes.get_set_index(set_val) for set_val in passcode_sets]
|
||||
|
||||
presumed_selected_attributes_idx = []
|
||||
for idx in range(passcode_len):
|
||||
key_numb = selected_keys_login[idx]
|
||||
set_idx = set_vals_idx[idx]
|
||||
selected_attr_idx = customer.users[username].user_interface.get_attr_idx_by_keynumb_setidx(key_numb, set_idx)
|
||||
presumed_selected_attributes_idx.append(selected_attr_idx)
|
||||
|
||||
Presumped Passcode: {{ presumed_selected_attributes_idx }}
|
||||
Recall User Passcode: {{ user_passcode }}
|
||||
```
|
||||
### Compare Enciphered Passcodes
|
||||
```
|
||||
enciphered_nkode = user_cipher.encipher_salt_hash_code(presumed_selected_attributes_idx, customer.attributes)
|
||||
```
|
||||
If `enciphered_nkode == user.enciphered_passcode.code`, the user's key selection is valid, and the login is successful.
|
||||
|
||||
## Renew Attributes
|
||||
Attributes renew is invoked with the renew_attributes method: `api.renew_attributes(customer_id)`
|
||||
The renew attributes process has three steps:
|
||||
1. Renew Customer Attributes
|
||||
2. Renew User Keys
|
||||
3. Refresh User on Login
|
||||
|
||||
When the customer calls the `renew_attributes` method, the method replaces the customer's attributes and set values. All its users go through an intermediate
|
||||
renewal step. The users fully renew after their first successful login. This first login refreshes their keys, salt, and hash with new values.
|
||||
|
||||
|
||||
### Customer Renew
|
||||
Old Customer attributes and set values are cached and copied to variables before renewal.
|
||||
```
|
||||
old_sets = customer.attributes.set_vals
|
||||
|
||||
Customer Sets: {{ customer_set_vals }}
|
||||
```
|
||||
|
||||
```
|
||||
old_attr = customer.attributes.attr_vals
|
||||
|
||||
Customer Attributes:
|
||||
{% for attrs in customer_attr_view -%}
|
||||
{{ attrs }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
After the renewal, the customer attributes and sets are new randomly generated values.
|
||||
```
|
||||
api.renew_attributes(customer_id)
|
||||
|
||||
set_vals = customer.attributes.set_vals
|
||||
|
||||
Customer Sets: {{ customer_new_set_vals }}
|
||||
```
|
||||
|
||||
```
|
||||
attr_vals = customer.attributes.attr_vals
|
||||
|
||||
Customer Attributes:
|
||||
{% for attrs in customer_new_attr_view -%}
|
||||
{{ attrs }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
### Renew User
|
||||
During the renewal, each user goes through a temporary transition period.
|
||||
```
|
||||
attrs_xor = xor_lists(new_attrs, old_attrs)
|
||||
sets_xor = xor_lists(new_sets, old_sets)
|
||||
for user in customer.users.values():
|
||||
user.renew = True
|
||||
user.user_cipher.set_key = xor_lists(user.user_cipher.set_key, sets_xor)
|
||||
user.user_cipher.prop_key = xor_lists(user.user_cipher.prop_key, attrs_xor)
|
||||
```
|
||||
##### User prop Key
|
||||
The user's prop key was a randomly generated list of length `numb_of_keys * attr_per_key`.
|
||||
Now each value in the prop key is `prop_key_i = old_prop_key_i ^ new_attr_i ^ old_attr_i`.
|
||||
Recall in the login process, `ciphered_customer_attrs = prop_key ^ customer_attr`.
|
||||
Since the customer_attr is now the new value, it gets canceled out, leaving:
|
||||
```
|
||||
new_prop_key = old_prop_key ^ old_attr ^ new_attr
|
||||
ciphered_customer_attrs = new_prop_key ^ new_attr
|
||||
ciphered_customer_attrs = old_prop_key ^ old_attr # since new_attr cancel out
|
||||
```
|
||||
Using the new customer attributes, we can validate the user's login attempt with the same hash.
|
||||
|
||||
##### User Set Key
|
||||
The user's set key was a randomly generated list of length `attr_per_key` xor `customer_set_vals`.
|
||||
The `old_set_vals` have been replaced with the new `new_set_vals`. The deciphering process described above
|
||||
remains the same.
|
||||
|
||||
### User Refresh
|
||||
Once the user has a successful login, they get a new salt and cipher keys, and their `enciphered_passcode` is recomputed
|
||||
with the new values.
|
||||
```
|
||||
user.user_cipher = UserCipherKeys.new(
|
||||
customer.attributes.keypad_size,
|
||||
customer.attributes.set_vals,
|
||||
user.user_cipher.max_nkode_len
|
||||
)
|
||||
user.enciphered_passcode = user.user_cipher.encipher_nkode(presumed_selected_attributes_idx, customer.attributes)
|
||||
user.renew = False
|
||||
```
|
||||
74
docs/nkode_unsecure_lowbandwitdh.md
Normal file
74
docs/nkode_unsecure_lowbandwitdh.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# 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 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
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Mobile Client
|
||||
participant Server
|
||||
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: The Server does not store the icons
|
||||
Server ->> Mobile Client: Keypad Index Array
|
||||
Mobile Client ->> User: Render Keypad
|
||||
User ->> Server: Set nKode
|
||||
Server ->> Server: Disperse Keypad
|
||||
Server ->> Mobile Client: Keypad Index Array
|
||||
Mobile Client ->> User: Render Keypad
|
||||
User ->> Server: Confirm nKode
|
||||
Note over User,Server: Login
|
||||
Server ->> Mobile Client: Keypad Index Array
|
||||
Mobile Client ->> User: Render Keypad
|
||||
User ->> Server: Successful Login
|
||||
Server ->> Server: Split Shuffle Keypad
|
||||
```
|
||||
|
||||
## Chacha20 Deterministic CSPRNG
|
||||
|
||||
A ChaCha20 Deterministic CSPRNG is a cryptographically secure pseudorandom number generator that uses the ChaCha20 stream cipher to produce a reproducible sequence of pseudorandom bytes. Given the same 256-bit key and 96-bit public nonce, it will always generate the same output stream, making it deterministic and suitable for use cases that require both security and repeatability.
|
||||
|
||||
## Secure Low-Bandwidth Architecture
|
||||
|
||||
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 (assume secure network)
|
||||
User ->> Server: Initiate Enrollment
|
||||
Server ->> Server: Generate Keypad Icons
|
||||
Server -->> Mobile Client: Store Icons On Device
|
||||
rect rgb(191, 223, 255)
|
||||
Server -->> Mobile Client: Store ChaCha20 256-bit key
|
||||
end
|
||||
Server ->> Mobile Client: Keypad Index Array
|
||||
Mobile Client ->> User: Render Keypad
|
||||
User ->> Server: Set nKode
|
||||
Server ->> Server: Disperse Keypad
|
||||
Server ->> Mobile Client: Keypad Index Array
|
||||
Mobile Client ->> User: Render Keypad
|
||||
User ->> Server: Confirm nKode
|
||||
Note over User,Server: Login (assume unsecure network)
|
||||
rect rgb(191, 223, 255)
|
||||
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/>Unshuffle(Shuffled Keypad Index Array, SharedKey, Nonce)
|
||||
end
|
||||
Mobile Client ->> User: Render Keypad
|
||||
User ->> Server: Successful Login
|
||||
Server ->> Server: Split Shuffle Keypad
|
||||
```
|
||||
@@ -1,215 +0,0 @@
|
||||
import numpy as np
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
import os
|
||||
from src.nkode_api import NKodeAPI
|
||||
from src.models import NKodePolicy, KeypadSize, EncipheredNKode
|
||||
from src.user_cipher import UserCipher
|
||||
from secrets import choice
|
||||
from string import ascii_lowercase
|
||||
import bcrypt
|
||||
import hashlib
|
||||
import base64
|
||||
|
||||
|
||||
def random_username() -> str:
|
||||
return "test_username" + "".join([choice(ascii_lowercase) for _ in range(6)])
|
||||
|
||||
|
||||
def select_keys_with_passcode_values(user_passcode: list[int], keypad: np.ndarray, attrs_per_key: int) -> list[int]:
|
||||
indices = [np.where(keypad == attr)[0][0] for attr in user_passcode]
|
||||
return [int(index // attrs_per_key) for index in indices]
|
||||
|
||||
|
||||
def visualize_keypad(keypad_list: np.ndarray, props_per_key: int):
|
||||
print("Keypad View")
|
||||
keypad_mat = keypad_list.reshape(-1, props_per_key)
|
||||
for idx, key_vals in enumerate(keypad_mat):
|
||||
print(f"Key {idx}: {key_vals}")
|
||||
|
||||
|
||||
def render_nkode_authentication(data: dict):
|
||||
# Set up the Jinja2 environment and template loader
|
||||
file_loader = FileSystemLoader('')
|
||||
env = Environment(loader=file_loader)
|
||||
|
||||
# Load the template
|
||||
template = env.get_template('nkode_authentication_template.md')
|
||||
|
||||
print(os.getcwd())
|
||||
# Render the template with the data
|
||||
output = template.render(data)
|
||||
|
||||
# Print or save the output
|
||||
output_file = os.path.expanduser("~/Desktop/nkode_authentication.md")
|
||||
with open(output_file, 'w') as fp:
|
||||
fp.write(output)
|
||||
print("File written successfully")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api = NKodeAPI()
|
||||
|
||||
policy = NKodePolicy(
|
||||
max_nkode_len=10,
|
||||
min_nkode_len=4,
|
||||
distinct_sets=0,
|
||||
distinct_attributes=4,
|
||||
byte_len=2,
|
||||
)
|
||||
keypad_size = KeypadSize(
|
||||
numb_of_keys=5,
|
||||
props_per_key=6 # aka number of sets
|
||||
)
|
||||
customer_id = api.create_new_customer(keypad_size, policy)
|
||||
customer = api.customers[customer_id]
|
||||
|
||||
set_vals = customer.cipher.set_key
|
||||
attr_vals = customer.cipher.prop_key
|
||||
customer_attr_view = attr_vals.reshape(-1, keypad_size.props_per_key)
|
||||
|
||||
attr_keypad_view = attr_vals.reshape(-1, keypad_size.props_per_key)
|
||||
attr_set_view = attr_keypad_view.T
|
||||
set_attribute_dict = dict(zip(set_vals, attr_set_view))
|
||||
|
||||
session_id, signup_interface = api.generate_signup_keypad(customer_id)
|
||||
signup_keypad = signup_interface.reshape(-1, keypad_size.numb_of_keys)
|
||||
|
||||
username = random_username()
|
||||
passcode_len = 4
|
||||
user_passcode = signup_interface[:passcode_len].tolist()
|
||||
selected_keys_set = select_keys_with_passcode_values(user_passcode, signup_interface, keypad_size.numb_of_keys)
|
||||
server_side_attr = [customer.cipher.prop_key[idx] for idx in user_passcode]
|
||||
|
||||
confirm_interface = api.set_nkode(username, customer_id, selected_keys_set, session_id)
|
||||
|
||||
confirm_keypad = confirm_interface.reshape(-1, keypad_size.numb_of_keys)
|
||||
|
||||
selected_keys_confirm = select_keys_with_passcode_values(user_passcode, confirm_interface, keypad_size.numb_of_keys)
|
||||
|
||||
success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id)
|
||||
assert success
|
||||
passcode_server_attr = [customer.cipher.prop_key[idx] for idx in user_passcode]
|
||||
passcode_server_set = [customer.cipher.get_prop_set_val(attr) for attr in passcode_server_attr]
|
||||
|
||||
user_keys = customer.users[username].cipher
|
||||
|
||||
padded_passcode_server_set = user_keys.pad_user_mask(np.array(passcode_server_set), customer.cipher.set_key)
|
||||
|
||||
set_idx = [customer.cipher.get_set_index(set_val) for set_val in padded_passcode_server_set]
|
||||
mask_set_keys = [user_keys.set_key[idx] for idx in set_idx]
|
||||
ciphered_mask = np.bitwise_xor(mask_set_keys, padded_passcode_server_set)
|
||||
ciphered_mask = np.bitwise_xor(ciphered_mask, user_keys.mask_key)
|
||||
mask = user_keys.encode_base64_str(ciphered_mask)
|
||||
#ciphered_customer_attrs = xor_lists(customer.cipher.prop_key, user_keys.prop_key)
|
||||
ciphered_customer_attrs = np.bitwise_xor(customer.cipher.prop_key, user_keys.prop_key)
|
||||
passcode_ciphered_attrs = [ciphered_customer_attrs[idx] for idx in user_passcode]
|
||||
pad_len = customer.nkode_policy.max_nkode_len - passcode_len
|
||||
passcode_ciphered_attrs.extend([0 for _ in range(pad_len)])
|
||||
#ciphered_code = xor_lists(passcode_ciphered_attrs, user_keys.pass_key)
|
||||
ciphered_code = np.bitwise_xor(passcode_ciphered_attrs, user_keys.pass_key)
|
||||
#passcode_bytes = int_array_to_bytes(ciphered_code)
|
||||
passcode_bytes = ciphered_code.tobytes()
|
||||
passcode_digest = base64.b64encode(hashlib.sha256(passcode_bytes).digest())
|
||||
hashed_data = bcrypt.hashpw(passcode_digest, user_keys.salt)
|
||||
code = hashed_data.decode("utf-8")
|
||||
|
||||
enciphered_nkode = EncipheredNKode(
|
||||
mask=mask,
|
||||
code=code,
|
||||
)
|
||||
"""
|
||||
USER LOGIN
|
||||
"""
|
||||
login_interface = api.get_login_keypad(username, customer_id)
|
||||
login_keypad = login_interface.reshape(-1, keypad_size.props_per_key)
|
||||
selected_keys_login = select_keys_with_passcode_values(user_passcode, login_interface, keypad_size.props_per_key)
|
||||
success = api.login(customer_id, username, selected_keys_login)
|
||||
assert success
|
||||
|
||||
"""
|
||||
VALIDATE LOGIN KEY ENTRY
|
||||
DECIPHER MASK
|
||||
"""
|
||||
|
||||
user = customer.users[username]
|
||||
set_vals = customer.cipher.set_key
|
||||
user_keys = user.cipher
|
||||
user_mask = user.enciphered_passcode.mask
|
||||
decoded_mask = user_keys.decode_base64_str(user_mask)
|
||||
deciphered_mask = np.bitwise_xor(decoded_mask, user_keys.mask_key)
|
||||
set_key_rand_component = np.bitwise_xor(set_vals, user_keys.set_key)
|
||||
login_passcode_sets = []
|
||||
for set_cipher in deciphered_mask[:passcode_len]:
|
||||
set_idx = np.where(set_key_rand_component == set_cipher)[0][0]
|
||||
login_passcode_sets.append(int(set_vals[set_idx]))
|
||||
|
||||
"""
|
||||
GET PRESUMED ATTRIBUTES
|
||||
"""
|
||||
|
||||
set_vals_idx = [customer.cipher.get_set_index(set_val) for set_val in login_passcode_sets]
|
||||
|
||||
presumed_selected_attributes_idx = []
|
||||
for idx in range(passcode_len):
|
||||
key_numb = selected_keys_login[idx]
|
||||
set_idx = set_vals_idx[idx]
|
||||
selected_attr_idx = customer.users[username].user_keypad.get_attr_idx_by_keynumb_setidx(key_numb, set_idx)
|
||||
presumed_selected_attributes_idx.append(selected_attr_idx)
|
||||
|
||||
"""
|
||||
RENEW KEYS
|
||||
"""
|
||||
|
||||
old_attrs = customer.cipher.prop_key.copy()
|
||||
old_sets = customer.cipher.set_key.copy()
|
||||
customer.cipher.renew()
|
||||
new_attrs = customer.cipher.prop_key
|
||||
new_sets = customer.cipher.set_key
|
||||
customer_new_attr_view = new_attrs.reshape(-1, keypad_size.props_per_key)
|
||||
"""
|
||||
RENEW USER
|
||||
"""
|
||||
attrs_xor = np.bitwise_xor(new_attrs, old_attrs)
|
||||
sets_xor = np.bitwise_xor(new_sets, old_sets)
|
||||
for user in customer.users.values():
|
||||
user.renew = True
|
||||
user.cipher.set_key = np.bitwise_xor(user.cipher.set_key, sets_xor)
|
||||
user.cipher.prop_key = np.bitwise_xor(user.cipher.prop_key, attrs_xor)
|
||||
|
||||
"""
|
||||
REFRESH USER KEYS
|
||||
"""
|
||||
user.cipher = UserCipher.create(
|
||||
customer.cipher.keypad_size,
|
||||
customer.cipher.set_key,
|
||||
user.cipher.max_nkode_len
|
||||
)
|
||||
user.enciphered_passcode = user.cipher.encipher_nkode(presumed_selected_attributes_idx, customer.cipher)
|
||||
user.renew = False
|
||||
|
||||
# Define some data to pass to the template
|
||||
data = {
|
||||
'keypad_size': keypad_size,
|
||||
'customer_set_vals': set_vals,
|
||||
'customer_attr_view': customer_attr_view,
|
||||
'set_attribute_dict': set_attribute_dict,
|
||||
'signup_keypad': signup_keypad,
|
||||
'username': 'test_user',
|
||||
'user_passcode': user_passcode,
|
||||
'selected_keys_set': selected_keys_set,
|
||||
'server_side_attr': server_side_attr,
|
||||
'confirm_keypad': confirm_keypad,
|
||||
'selected_keys_confirm': selected_keys_confirm,
|
||||
'user_cipher': user_keys,
|
||||
'passcode_server_attr': passcode_server_attr,
|
||||
'passcode_server_set': passcode_server_set,
|
||||
'enciphered_nkode': enciphered_nkode,
|
||||
'login_keypad': login_keypad,
|
||||
'selected_login_keys': selected_keys_login,
|
||||
'login_passcode_sets': login_passcode_sets,
|
||||
'presumed_selected_attributes_idx': presumed_selected_attributes_idx,
|
||||
'customer_new_attr_view': customer_new_attr_view,
|
||||
'customer_new_set_vals': new_sets,
|
||||
|
||||
}
|
||||
render_nkode_authentication(data)
|
||||
81
docs/scripts/render_diagrams_md.py
Normal file
81
docs/scripts/render_diagrams_md.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
from secrets import choice
|
||||
from string import ascii_lowercase
|
||||
from docs.scripts.utils import render_markdown_template, emojis
|
||||
from src.models import NKodePolicy, KeypadSize
|
||||
from src.nkode_api import NKodeAPI
|
||||
from src.utils import select_keys_with_passcode_values
|
||||
|
||||
|
||||
def display_icons(icons_array: np.ndarray, kp: KeypadSize) -> str:
|
||||
icons = "["
|
||||
for row in icons_array.reshape(-1, kp.numb_of_keys):
|
||||
icons += ",".join(row)
|
||||
icons += "<br/>"
|
||||
icons = icons[:-5]
|
||||
icons += "]"
|
||||
return icons
|
||||
|
||||
def display_icons_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 += "<br/>"
|
||||
return icons
|
||||
|
||||
if __name__ == "__main__":
|
||||
api = NKodeAPI()
|
||||
|
||||
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
|
||||
)
|
||||
customer_id = api.create_new_customer(keypad_size, policy)
|
||||
username = "test_username" + "".join([choice(ascii_lowercase) for _ in range(6)])
|
||||
signup_session_id, set_signup_keypad = api.generate_signup_keypad(customer_id, username)
|
||||
ordered_set_icons = emojis[set_signup_keypad]
|
||||
passcode_len = 4
|
||||
passcode_property_indices = np.random.choice(set_signup_keypad.reshape(-1), size=passcode_len,
|
||||
replace=False).tolist()
|
||||
selected_keys_set = select_keys_with_passcode_values(passcode_property_indices, set_signup_keypad,
|
||||
keypad_size.numb_of_keys)
|
||||
confirm_keypad = api.set_nkode(customer_id, selected_keys_set, signup_session_id)
|
||||
selected_keys_confirm = select_keys_with_passcode_values(passcode_property_indices, confirm_keypad,
|
||||
keypad_size.numb_of_keys)
|
||||
ordered_confirm_icons = emojis[confirm_keypad]
|
||||
success = api.confirm_nkode(customer_id, selected_keys_confirm, signup_session_id)
|
||||
context = {
|
||||
"email": "user@example.com",
|
||||
"signup_session_id": signup_session_id,
|
||||
"set_keypad": display_icons_keypad(set_signup_keypad.reshape(-1, keypad_size.numb_of_keys), keypad_size.numb_of_keys),
|
||||
"keypad_icons": display_icons(emojis[:keypad_size.total_props], keypad_size),
|
||||
"ordered_keypad": display_icons_keypad(ordered_set_icons, keypad_size.numb_of_keys),
|
||||
"passcode_user_icons": str(emojis[passcode_property_indices]),
|
||||
"selected_keys_set": str(selected_keys_set),
|
||||
"confirm_keypad": display_icons_keypad(confirm_keypad.reshape(-1, keypad_size.numb_of_keys), keypad_size.numb_of_keys),
|
||||
"confirm_ordered_keypad": display_icons_keypad(ordered_confirm_icons.reshape(-1, keypad_size.numb_of_keys), keypad_size.numb_of_keys),
|
||||
"confirm_key_selection": selected_keys_confirm
|
||||
}
|
||||
render_markdown_template(Path("../templates/enrollment_diagram.template.md"), Path("../enrollment_diagram.md"), context)
|
||||
login_keypad = api.get_login_keypad(username, customer_id)
|
||||
selected_keys_login = select_keys_with_passcode_values(passcode_property_indices, login_keypad,
|
||||
keypad_size.props_per_key)
|
||||
ordered_login_keypad = emojis[login_keypad]
|
||||
context = {
|
||||
"email": "user@example.com",
|
||||
"keypad_icons": display_icons(emojis[:keypad_size.total_props], keypad_size),
|
||||
"login_keypad": display_icons_keypad(login_keypad.reshape(-1, keypad_size.props_per_key), keypad_size.props_per_key),
|
||||
"ordered_login_icons": display_icons_keypad(ordered_login_keypad.reshape(-1, keypad_size.props_per_key),keypad_size.props_per_key),
|
||||
"passcode_user_icons": str(emojis[passcode_property_indices]),
|
||||
"selected_keys_login": str(selected_keys_login)
|
||||
}
|
||||
api.login(customer_id, username, selected_keys_login)
|
||||
render_markdown_template(Path("../templates/login_diagram.template.md"), Path("../login_diagram.md"), context)
|
||||
124
docs/scripts/render_encipher_decipher_diagrams.py
Normal file
124
docs/scripts/render_encipher_decipher_diagrams.py
Normal file
@@ -0,0 +1,124 @@
|
||||
import base64
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
|
||||
import bcrypt
|
||||
import numpy as np
|
||||
from secrets import choice
|
||||
from string import ascii_lowercase
|
||||
from docs.scripts.utils import render_markdown_template, emojis
|
||||
from src.models import NKodePolicy, KeypadSize
|
||||
from src.nkode_api import NKodeAPI
|
||||
from src.user_cipher import UserCipher
|
||||
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
|
||||
|
||||
def display_md_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 += "<br/>"
|
||||
return icons
|
||||
|
||||
if __name__ == "__main__":
|
||||
api = NKodeAPI()
|
||||
|
||||
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
|
||||
)
|
||||
customer_id = api.create_new_customer(keypad_size, policy)
|
||||
customer = api.get_customer(customer_id)
|
||||
username = "test_username" + "".join([choice(ascii_lowercase) for _ in range(6)])
|
||||
signup_session_id, set_signup_keypad = api.generate_signup_keypad(customer_id, username)
|
||||
ordered_set_icons = emojis[set_signup_keypad]
|
||||
passcode_len = 4
|
||||
passcode_property_indices = np.random.choice(set_signup_keypad.reshape(-1), size=passcode_len,
|
||||
replace=False).tolist()
|
||||
selected_keys_set = select_keys_with_passcode_values(passcode_property_indices, set_signup_keypad,
|
||||
keypad_size.numb_of_keys)
|
||||
confirm_keypad = api.set_nkode(customer_id, selected_keys_set, signup_session_id)
|
||||
selected_keys_confirm = select_keys_with_passcode_values(passcode_property_indices, confirm_keypad,
|
||||
keypad_size.numb_of_keys)
|
||||
ordered_confirm_icons = emojis[confirm_keypad]
|
||||
success = api.confirm_nkode(customer_id, selected_keys_confirm, signup_session_id)
|
||||
user = api.customers[customer_id].users[username]
|
||||
combined_prop_key = user.cipher.property_key ^ customer.cipher.property_key
|
||||
user_passcode = combined_prop_key[passcode_property_indices]
|
||||
pad_len = customer.nkode_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.cipher.pass_key
|
||||
passcode_prehash = base64.b64encode(hashlib.sha256(ciphered_passcode.tobytes()).digest())
|
||||
passcode_hash = bcrypt.hashpw(passcode_prehash, bcrypt.gensalt(rounds=12)).decode("utf-8")
|
||||
|
||||
padded_passcode_position_indices = customer.cipher.get_passcode_position_indices_padded(
|
||||
list(passcode_property_indices), customer.nkode_policy.max_nkode_len)
|
||||
user_position_key = user.cipher.combined_position_key ^ customer.cipher.position_key
|
||||
ordered_user_position_key = user_position_key[padded_passcode_position_indices]
|
||||
mask = ordered_user_position_key ^ user.cipher.mask_key
|
||||
encoded_mask = user.cipher.encode_base64_str(mask)
|
||||
login_keypad = api.get_login_keypad(username, customer_id)
|
||||
selected_keys_login = select_keys_with_passcode_values(passcode_property_indices, login_keypad,
|
||||
keypad_size.props_per_key)
|
||||
|
||||
old_props = customer.cipher.property_key.copy()
|
||||
old_pos = customer.cipher.position_key.copy()
|
||||
customer.cipher.property_key = np.random.choice(2 ** 16, size=keypad_size.total_props, replace=False)
|
||||
customer.cipher.position_key = np.random.choice(2 ** 16, size=keypad_size.props_per_key, replace=False)
|
||||
new_props = customer.cipher.property_key
|
||||
new_pos = customer.cipher.position_key
|
||||
props_xor = new_props ^ old_props
|
||||
pos_xor = new_pos ^ old_pos
|
||||
user = customer.users[username]
|
||||
new_user_cipher = UserCipher.create(keypad_size, customer.cipher.position_key, policy.max_nkode_len)
|
||||
context = {
|
||||
"max_nkode_len": policy.max_nkode_len,
|
||||
"numb_of_keys": keypad_size.numb_of_keys,
|
||||
"props_per_key": keypad_size.props_per_key,
|
||||
"customer_property_key": customer.cipher.property_key,
|
||||
"customer_position_key": customer.cipher.position_key,
|
||||
"user_property_key": user.cipher.property_key,
|
||||
"pass_key": user.cipher.pass_key,
|
||||
"combined_position_key": user.cipher.combined_position_key,
|
||||
"user_position_key": user_position_key,
|
||||
"mask_key": user.cipher.mask_key,
|
||||
"user_passcode_idxs": passcode_property_indices,
|
||||
"combined_property_key": combined_prop_key,
|
||||
"user_passcode_props": user_passcode,
|
||||
"padded_passcode": padded_passcode,
|
||||
"ciphered_passcode": ciphered_passcode,
|
||||
"code": passcode_hash,
|
||||
"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": mask,
|
||||
"selected_keys": selected_keys_login,
|
||||
"login_keypad": display_keypad(login_keypad, keypad_size.props_per_key),
|
||||
"login_keypad_md": display_md_keypad(login_keypad, keypad_size.props_per_key),
|
||||
"ordered_keys": login_keypad.reshape(-1, keypad_size.props_per_key)[selected_keys_login],
|
||||
"old_props": old_props,
|
||||
"new_props": new_props,
|
||||
"old_pos": old_pos,
|
||||
"new_pos": new_pos,
|
||||
"xor_props": props_xor,
|
||||
"xor_pos": pos_xor,
|
||||
"inter_user_position": user.cipher.combined_position_key ^ pos_xor,
|
||||
"inter_user_property_key": user.cipher.property_key ^ props_xor,
|
||||
"new_user_position": new_user_cipher.combined_position_key,
|
||||
"new_user_property_key": new_user_cipher.property_key,
|
||||
}
|
||||
render_markdown_template(Path("../templates/encipher_decipher_renew_nkode.template.md"), Path("../encipher_decipher_renew_nkode.md"), context)
|
||||
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)
|
||||
46
docs/scripts/utils.py
Normal file
46
docs/scripts/utils.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import numpy as np
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
|
||||
emojis = np.array([
|
||||
"🍎", "🍏", "🍊", "🍋", "🍌", "🍉", "🍇", "🍓", "🍒", "🍑",
|
||||
"🥭", "🍍", "🥥", "🥝", "🍅", "🍆", "🥑", "🥕", "🌽", "🥔",
|
||||
"🍠", "🥐", "🥖", "🥨", "🥯", "🥞", "🧀", "🍖", "🍗", "🥚",
|
||||
"🍔", "🍟", "🍕", "🌭", "🥪", "🌮", "🌯", "🍣", "🍤", "🍙",
|
||||
"🍚", "🍜", "🍲", "🍛", "🍱", "🥟", "🍦", "🍧", "🍨", "🍩",
|
||||
"🍪", "🎂", "🍰", "🧁", "🍫", "🍬", "🍭", "🍮", "☕", "🍵",
|
||||
"🥤", "🧃", "🍷", "🍸", "🍹", "🍺", "🥂", "🥃", "🐶", "🐱",
|
||||
"🐭", "🐹", "🐰", "🦊", "🐻", "🐼", "🐨", "🐯", "🦁", "🐮",
|
||||
"🐷", "🐽", "🐴", "🦄", "🦓", "🦒", "🐘", "🦏", "🐪", "🐫",
|
||||
"🐑", "🐐", "🐓", "🐔", "🐣", "🐤", "🐦", "🦅", "🦇", "🦉",
|
||||
"🐺", "🐍", "🐢", "🦎", "🐙", "🦑", "🦐", "🦀", "🐡", "🐠",
|
||||
"🐟", "🐬", "🐳", "🦈", "🐊", "🐅", "🐆", "🐾", "🦋", "🐞",
|
||||
"🐝", "🐜", "🕷️", "🕸️", "🌸", "🌹", "🌺", "🌻", "🌼", "🌷",
|
||||
"🌱", "🌲", "🌳", "🌴", "🌵", "🌾", "🌿", "🍀", "🍁", "🍂",
|
||||
"🍃", "⭐", "🌟", "✨", "⚡️", "☄️", "☀️", "🌤️", "⛅", "🌥️",
|
||||
"☁️", "🌦️", "🌧️", "⛈️", "🌩️", "❄️", "🌬️", "💨", "🌈", "☔",
|
||||
"💧", "🌊", "🔥", "💥", "⛄", "🌋", "⛰️", "🏔️", "🏕️", "🏖️",
|
||||
"🏜️", "🏝️", "🏞️", "🏟️", "🏠", "🏡", "🏢", "🏣", "🏤", "🏥",
|
||||
"🏦", "🏨", "🏩", "🏪", "🏫", "🏬", "🏭", "🏯", "🏰", "⛪",
|
||||
"⛩️", "🕌", "🕍", "🗿", "🎡", "🎢", "🎠", "🎪", "🎨", "🎬",
|
||||
"🎤", "🎧", "🎼", "🎹", "🎺", "🎸", "🥁", "🎻", "🎮", "🎲"
|
||||
])
|
||||
|
||||
def render_markdown_template(template_path, output_path, context: dict):
|
||||
template_dir = template_path.parent
|
||||
template_file = template_path.name
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(template_dir),
|
||||
autoescape=select_autoescape(['html', 'xml']),
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True
|
||||
)
|
||||
|
||||
template = env.get_template(template_file)
|
||||
rendered = template.render(**context)
|
||||
|
||||
with open(output_path, 'w') as f:
|
||||
f.write(rendered)
|
||||
|
||||
print(f"Template rendered to {output_path}")
|
||||
70
docs/tabletop-discussion.md
Normal file
70
docs/tabletop-discussion.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Table-top discussion
|
||||
|
||||
|
||||
## Documentation and Tutorials
|
||||
1. [Enrollment](enrollment_diagram.md)
|
||||
2. [Login](login_diagram.md)
|
||||
3. [Cipher and Renew](encipher_decipher_renew_nkode.md)
|
||||
4. [nKode API Tutorial 1](../notebooks/Enrollment_Login_Renewal_Simplified.ipynb)
|
||||
5. [nKode API Tutorial 2](../notebooks/Enrollment_Login_Renewal_Detailed.ipynb)
|
||||
6. [Dispersion Tutorial](../notebooks/Dispersion.ipynb)
|
||||
7. [Split Shuffle](../notebooks/Split_Shuffle.ipynb)
|
||||
|
||||
## Discussion Topics
|
||||
### nKode Length
|
||||
[Memorized Secret](https://pages.nist.gov/800-63-3/sp800-63b.html#memsecret) `Memorized secrets SHALL be at least 8 characters in length if chosen by the subscriber. Memorized secrets chosen randomly by the CSP or verifier SHALL be at least 6 characters in length and MAY be entirely numeric.`
|
||||
- The minimum entropy for a randomly chosen memorized secret is approximately 20 bits.
|
||||
- A keypad with 6 keys, each having 9 properties, exceeds this requirement with a minimum 4-character nKode, providing approximately 23 bits of entropy.
|
||||
|
||||
### nKode Observation
|
||||
- Cracking an nKode [Evil nKode](https://git.infra.nkode.tech/dkelly/evilkode)
|
||||
- Replay Attack
|
||||
|
||||
### Dispersion Attack
|
||||
|
||||
### nKode Over low-bandwidth
|
||||
|
||||
### nKode Over Unencrypted Channel
|
||||
- TOTP
|
||||
- DARC
|
||||
|
||||
### Discussion Outcomes:
|
||||
|
||||
#### Attacks and controls
|
||||
| Attacks | Controls |
|
||||
|-------------------------|--------------------------------------------------------------------------------|
|
||||
| Screen Recording Attack | Split shuffle/more icons per key than keys |
|
||||
| Exfiltrated DB | Physically separated keys and icons, partial or full encryption, nKode renewal |
|
||||
| *APT | *Don't wait for garbage collector, manage timeouts |
|
||||
| Phishing | Dispersion Resistant Keypad, nKode policy, passkey protected keypad icons |
|
||||
| *MiTM | TLS, *TOTP shuffle, *DARC |
|
||||
|
||||
*not implemented yet/needs another look
|
||||
|
||||
#### asks for Dr. Kandah
|
||||
|
||||
- Evil nKode screen watching/key replay
|
||||
- Given a particular policy and keypad size:
|
||||
- what is the probability of a key replay?
|
||||
- what trade-offs are made between key replay and cracking an nkode?
|
||||
- Is the split shuffle unbiased?
|
||||
- Can we rig the shuffle in our favor with keypad caching or other techniques?
|
||||
- Dispersion Attack/Phishing attack
|
||||
- is the dispersion algorithm unbiased?
|
||||
- Develop a modified dispersion algorithm to phish a dispersion resistant keypad
|
||||
- validate the cipher
|
||||
- validate the server-side values
|
||||
- validate the relationship between the mask and the hash
|
||||
- validate the renewal
|
||||
- are these processes/algorithms secure?
|
||||
- What is the minimum amount of encryption needed to secure user's nkodes against a full/partial database exfiltration
|
||||
- How long will it take to brute force a hash with a full plain text breach of the database and what's gained?
|
||||
- How often do nkode icons need to be changed to maintain security if at all?
|
||||
- if it does need to be changed can we start with 4 icons and add icons over time then roll the icons (drop the first icons and append a new one) after reaching a max size?
|
||||
- Low-bandwidth: how low can we go?
|
||||
- TCP vs UDP
|
||||
- Security of RX/TX without tls/encrypted channel
|
||||
- Hypothetical: What security gains are made if we split the cipher keys into multiple parts and put them on different machines in many locations?
|
||||
|
||||
Other stuff:
|
||||
- unbiased icons/psychology
|
||||
22
docs/templates/dispersion_attack.template.md
vendored
Normal file
22
docs/templates/dispersion_attack.template.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# Dispersion Attack
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Threat Actor
|
||||
participant nKode Server
|
||||
Threat Actor ->> Threat Actor: Get User's email/keypad icons
|
||||
Threat Actor ->> User: Send Phishing Email
|
||||
Note left of User: Recieves Email:<br/>Subject: Urgent<br/>Body: Click the link to verify your account https://www.nkod3.tech
|
||||
User ->> Threat Actor: Clicks link
|
||||
Threat Actor -->> User: Fake nKode Login Page
|
||||
Note left of User: Login Page<br/>{{nkode_keypad1}}
|
||||
User ->>+ Threat Actor: key selection {{key_selection1}}
|
||||
Threat Actor ->> Threat Actor: Disperse Keypad
|
||||
Threat Actor -->>- User: Invalid nKode Try Again
|
||||
Note left of User: Login Page<br/>{{nkode_keypad2}}
|
||||
User ->> Threat Actor: key selection {{key_selection2}}
|
||||
Threat Actor ->> Threat Actor: Deduce the user's nKode
|
||||
Threat Actor ->> nKode Server: {{user_passcode}}
|
||||
nKode Server -->> Threat Actor: Success
|
||||
```
|
||||
274
docs/templates/encipher_decipher_renew_nkode.template.md
vendored
Normal file
274
docs/templates/encipher_decipher_renew_nkode.template.md
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
# Encipher, Decipher and Renew nKode
|
||||
|
||||
## Customer 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 }}
|
||||
|
||||
## Customer Cipher
|
||||
- property key: {{ customer_property_key }}
|
||||
- position key: {{ customer_position_key }}
|
||||
|
||||
|
||||
---
|
||||
|
||||
## User Cipher
|
||||
- property key: {{ user_property_key }}
|
||||
- passcode key: {{ pass_key }}
|
||||
- combined position key: {{ combined_position_key }}
|
||||
- mask key: {{ mask_key }}
|
||||
|
||||
### Combined Postion Key
|
||||
```mermaid
|
||||
block-beta
|
||||
columns 2
|
||||
user_pos["user position key:\n{{user_position_key}}"]
|
||||
customer_pos["customer position key:\n{{customer_position_key}}"]
|
||||
space:2
|
||||
xor(("XOR")):2
|
||||
user_pos --> xor
|
||||
customer_pos --> xor
|
||||
space:2
|
||||
comb_pos["combined position key\n{{combined_position_key}}"]:2
|
||||
xor --> comb_pos
|
||||
```
|
||||
|
||||
## User Keypad
|
||||
- keypad example:<br/>{{ login_keypad_md }}
|
||||
- user passcode indices: {{ user_passcode_idxs}}
|
||||
|
||||
## nKode Cipher
|
||||
|
||||
### Passcode Hash
|
||||
```mermaid
|
||||
block-beta
|
||||
columns 2
|
||||
cprop["customer_property_key\n{{customer_property_key}}"]
|
||||
uprop["user_property_key\n{{user_property_key}}"]
|
||||
space:2
|
||||
xor1(("XOR")):2
|
||||
cprop --> xor1
|
||||
uprop --> xor1
|
||||
space:2
|
||||
prop["combined_property_key\n{{combined_property_key}}"]
|
||||
xor1 --> prop
|
||||
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 3
|
||||
passcode_idx["passcode indices:\n{{user_passcode_idxs}}"]
|
||||
comb_pos["combined position key:\n{{combined_position_key}}"]
|
||||
cust_pos["customer position key:\n{{customer_position_key}}"]
|
||||
|
||||
space:3
|
||||
propidx(["Get Position Idx:\nmap each to element mod props_per_key"])
|
||||
passcode_idx-->propidx
|
||||
space:1
|
||||
xor1(("XOR"))
|
||||
comb_pos --> xor1
|
||||
cust_pos --> xor1
|
||||
|
||||
space:3
|
||||
passcode_position_idx["passcode poition indices:\n{{passcode_position_idxs}}"]
|
||||
propidx --> passcode_position_idx
|
||||
|
||||
space:5
|
||||
pad1(("Pad with\nrandom indices"))
|
||||
passcode_position_idx --> pad1
|
||||
|
||||
space:5
|
||||
posidx["Padded Passcode Position Indices:\n{{pad_user_passcode_idxs}}"]
|
||||
pad1 --> posidx
|
||||
space:1
|
||||
user_pos["user position key:\n{{user_position_key}}"]
|
||||
xor1 --> user_pos
|
||||
|
||||
space:4
|
||||
sel(("select positions"))
|
||||
user_pos --> sel
|
||||
posidx --> sel
|
||||
space:5
|
||||
passcode_pos["ordered user passcode positions:\n{{ordered_user_position_key}}"]
|
||||
sel --> passcode_pos
|
||||
mask_key["mask key\n{{mask_key}}"]
|
||||
space:4
|
||||
xor2(("XOR"))
|
||||
mask_key --> xor2
|
||||
passcode_pos --> xor2
|
||||
space:5
|
||||
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["combined_property_key\n{{combined_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 nKode
|
||||
|
||||
nKode renewal is a three step process:
|
||||
1. Renew Customer Keys
|
||||
2. Intermediate User Keys
|
||||
3. Renew User Keys on Login
|
||||
|
||||
|
||||
{% set md_tick = '`' %}
|
||||
```mermaid
|
||||
flowchart
|
||||
subgraph Renew Customer Keys
|
||||
old_prop["`old customer property key:<br/>{{old_props}}`"]
|
||||
new_prop["`new customer property key:<br/>{{new_props}}`"]
|
||||
old_pos["`old customer position key:<br/>{{old_pos}}`"]
|
||||
new_pos["`new customer position key:<br/>{{new_pos}}`"]
|
||||
xor1(("XOR"))
|
||||
xor2(("XOR"))
|
||||
xor_prop["`xor property key:<br/>{{xor_props}}`"]
|
||||
xor_pos["`xor position key:<br/>{{xor_pos}}`"]
|
||||
old_prop --> xor1
|
||||
new_prop --> xor1
|
||||
xor1 --> xor_prop
|
||||
old_pos --> xor2
|
||||
new_pos --> xor2
|
||||
xor2 --> xor_pos
|
||||
end
|
||||
|
||||
subgraph Intermediate User Keys
|
||||
users@{shape: procs, label: "users"}
|
||||
users --> eachuser
|
||||
subgraph eachuser [for each user]
|
||||
subgraph old user keys
|
||||
old_user_pos["`combined position key:<br/>{{user_position_key}}`"]
|
||||
old_user_prop["`property key:<br/>{{user_property_key}}`"]
|
||||
old_renew["renew: False"]
|
||||
end
|
||||
xor3(("XOR"))
|
||||
xor4(("XOR"))
|
||||
old_user_pos --> xor3
|
||||
xor_pos --> xor3
|
||||
xor3 --> inter_user_pos
|
||||
old_user_prop --> xor4
|
||||
xor_prop --> xor4
|
||||
xor4 --> inter_user_prop
|
||||
subgraph inter_user[intermediate user keys]
|
||||
inter_user_pos["`combined position key:<br/>{{inter_user_position}}`"]
|
||||
inter_user_prop["`property key:<br/>{{inter_user_property_key}}`"]
|
||||
inter_renew["renew: True"]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
subgraph Renew User Keys on Login
|
||||
login["First login post renew"]
|
||||
inter_user --> login
|
||||
subgraph new_user [New User Keys]
|
||||
new_user_pos["`combined position key:<br/>{{new_user_position}}`"]
|
||||
new_user_prop["`property key:<br/>{{new_user_property_key}}`"]
|
||||
new_renew["renew: False"]
|
||||
end
|
||||
login --> new_user
|
||||
end
|
||||
|
||||
```
|
||||
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.
|
||||
|
||||
37
docs/templates/enrollment_diagram.template.md
vendored
Normal file
37
docs/templates/enrollment_diagram.template.md
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# nKode Enrollment
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Client
|
||||
participant Server
|
||||
Note over User,Server: Enrollment
|
||||
Client->>User: Signup Form
|
||||
Note left of User: email: {{ email }}
|
||||
User->>Client: Submit Email
|
||||
Client->>+Server: Signup Session: {{ email }}
|
||||
Server->>Server: Create Signup Session
|
||||
Note over User,Server: Set nKode
|
||||
Server-->>-Client: signup_session_id, set_keypad, icons
|
||||
Note left of Server: signup_session_id:<br/>{{ signup_session_id }}
|
||||
Note left of Server: set_keypad:<br/>{{set_keypad}}
|
||||
Note left of Server: Icons:<br/>{{keypad_icons}}
|
||||
Client->>Client: Order Icons by keypad
|
||||
Client->>User: Display Keypad
|
||||
Note left of Client: {{ ordered_keypad }}
|
||||
Note left of User: User icons: {{ passcode_user_icons }}
|
||||
User->>Client: Set Key Selection: {{ selected_keys_set }}
|
||||
Client->>+Server: Set nKode:<br/>{{ signup_session_id }}<br/>{{ selected_keys_set }}
|
||||
Server->>Server: Disperse Set Keypad
|
||||
Note over User,Server: Confirm nKode
|
||||
Server-->>-Client: signup_session_id, confirm_keypad, icons
|
||||
Note left of Server: signup_session_id:<br/>{{ signup_session_id }}
|
||||
Note left of Server: confirm_keypad:<br/>{{confirm_keypad}}
|
||||
Client->>Client: Order Icons by keypad
|
||||
Client->>User: Display Keypad
|
||||
Note left of Client: {{ confirm_ordered_keypad }}
|
||||
Note left of User: User icons: {{ passcode_user_icons }}
|
||||
User->>Client: Key Selection: {{ confirm_key_selection }}
|
||||
Client->>+Server: Confirm nKode:<br/>{{ signup_session_id }}<br/>{{ confirm_key_selection }}
|
||||
Server->>Server: Create User
|
||||
Server-->>-Client: Success
|
||||
```
|
||||
0
docs/templates/keypad_shuffle.template.md
vendored
Normal file
0
docs/templates/keypad_shuffle.template.md
vendored
Normal file
22
docs/templates/login_diagram.template.md
vendored
Normal file
22
docs/templates/login_diagram.template.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# nKode Login
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Client
|
||||
participant Server
|
||||
Note over User,Server: Login
|
||||
Client->>User: Login Form
|
||||
Note left of User: email: {{ email }}
|
||||
User->>Server: Submit Email
|
||||
Server->>Client: login_keypad, icons
|
||||
Note left of Server: Login Keypad:<br/>{{ login_keypad }}
|
||||
Note left of Server: Icons:<br/>{{ keypad_icons }}
|
||||
Client->>Client: Order Icons
|
||||
Client->>User: Display Keypad
|
||||
Note left of Client: {{ ordered_login_icons }}
|
||||
Note left of User: User passcode icons: {{ passcode_user_icons }}
|
||||
User->>Client: Selected Keys<br/>{{selected_keys_login}}
|
||||
Client->>Server: Login:<br/>email: {{email}}<br/>selected_keys: {{selected_keys_login}}
|
||||
Server-->>Client: Success
|
||||
```
|
||||
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
|
||||
```
|
||||
208
environment.yaml
Normal file
208
environment.yaml
Normal file
@@ -0,0 +1,208 @@
|
||||
name: pynkode
|
||||
channels:
|
||||
- defaults
|
||||
dependencies:
|
||||
- annotated-types=0.6.0=py310hca03da5_0
|
||||
- anyio=4.2.0=py310hca03da5_0
|
||||
- appnope=0.1.2=py310hca03da5_1001
|
||||
- argon2-cffi=21.3.0=pyhd3eb1b0_0
|
||||
- argon2-cffi-bindings=21.2.0=py310h1a28f6b_0
|
||||
- asttokens=2.0.5=pyhd3eb1b0_0
|
||||
- async-lru=2.0.4=py310hca03da5_0
|
||||
- attrs=23.1.0=py310hca03da5_0
|
||||
- babel=2.11.0=py310hca03da5_0
|
||||
- beautifulsoup4=4.12.3=py310hca03da5_0
|
||||
- bleach=4.1.0=pyhd3eb1b0_0
|
||||
- brotli-python=1.0.9=py310h313beb8_8
|
||||
- bzip2=1.0.8=h80987f9_6
|
||||
- ca-certificates=2024.3.11=hca03da5_0
|
||||
- certifi=2024.7.4=py310hca03da5_0
|
||||
- cffi=1.16.0=py310h80987f9_1
|
||||
- charset-normalizer=2.0.4=pyhd3eb1b0_0
|
||||
- comm=0.2.1=py310hca03da5_0
|
||||
- cyrus-sasl=2.1.28=h9131b1a_1
|
||||
- debugpy=1.6.7=py310h313beb8_0
|
||||
- decorator=5.1.1=pyhd3eb1b0_0
|
||||
- defusedxml=0.7.1=pyhd3eb1b0_0
|
||||
- executing=0.8.3=pyhd3eb1b0_0
|
||||
- gettext=0.21.0=h13f89a0_1
|
||||
- glib=2.78.4=h313beb8_0
|
||||
- glib-tools=2.78.4=h313beb8_0
|
||||
- gst-plugins-base=1.14.1=h313beb8_1
|
||||
- gstreamer=1.14.1=h80987f9_1
|
||||
- icu=73.1=h313beb8_0
|
||||
- idna=3.7=py310hca03da5_0
|
||||
- ipykernel=6.28.0=py310hca03da5_0
|
||||
- ipython=8.25.0=py310hca03da5_0
|
||||
- ipywidgets=8.1.2=py310hca03da5_0
|
||||
- jedi=0.18.1=py310hca03da5_1
|
||||
- jinja2=3.1.4=py310hca03da5_0
|
||||
- jpeg=9e=h80987f9_1
|
||||
- json5=0.9.6=pyhd3eb1b0_0
|
||||
- jsonschema=4.19.2=py310hca03da5_0
|
||||
- jsonschema-specifications=2023.7.1=py310hca03da5_0
|
||||
- jupyter=1.0.0=py310hca03da5_9
|
||||
- jupyter-lsp=2.2.0=py310hca03da5_0
|
||||
- jupyter_client=8.6.0=py310hca03da5_0
|
||||
- jupyter_console=6.6.3=py310hca03da5_0
|
||||
- jupyter_core=5.7.2=py310hca03da5_0
|
||||
- jupyter_events=0.10.0=py310hca03da5_0
|
||||
- jupyter_server=2.14.1=py310hca03da5_0
|
||||
- jupyter_server_terminals=0.4.4=py310hca03da5_1
|
||||
- jupyterlab=4.0.11=py310hca03da5_0
|
||||
- jupyterlab_pygments=0.1.2=py_0
|
||||
- jupyterlab_server=2.25.1=py310hca03da5_0
|
||||
- jupyterlab_widgets=3.0.10=py310hca03da5_0
|
||||
- krb5=1.20.1=hf3e1bf2_1
|
||||
- libclang=14.0.6=default_h1b80db6_1
|
||||
- libclang13=14.0.6=default_h24352ff_1
|
||||
- libcxx=14.0.6=h848a8c0_0
|
||||
- libedit=3.1.20230828=h80987f9_0
|
||||
- libffi=3.4.4=hca03da5_1
|
||||
- libglib=2.78.4=h0a96307_0
|
||||
- libiconv=1.16=h80987f9_3
|
||||
- libllvm14=14.0.6=h7ec7a93_3
|
||||
- libpng=1.6.39=h80987f9_0
|
||||
- libpq=12.17=h02f6b3c_0
|
||||
- libsodium=1.0.18=h1a28f6b_0
|
||||
- libxml2=2.10.4=h0b34f26_2
|
||||
- llvm-openmp=14.0.6=hc6e5704_0
|
||||
- lz4-c=1.9.4=h313beb8_1
|
||||
- markupsafe=2.1.3=py310h80987f9_0
|
||||
- matplotlib-inline=0.1.6=py310hca03da5_0
|
||||
- mistune=2.0.4=py310hca03da5_0
|
||||
- mysql=5.7.24=ha71a6ea_2
|
||||
- nbclient=0.8.0=py310hca03da5_0
|
||||
- nbconvert=7.10.0=py310hca03da5_0
|
||||
- nbformat=5.9.2=py310hca03da5_0
|
||||
- ncurses=6.4=h313beb8_0
|
||||
- nest-asyncio=1.6.0=py310hca03da5_0
|
||||
- notebook=7.0.8=py310hca03da5_2
|
||||
- notebook-shim=0.2.3=py310hca03da5_0
|
||||
- openssl=3.0.14=h80987f9_0
|
||||
- overrides=7.4.0=py310hca03da5_0
|
||||
- packaging=24.1=py310hca03da5_0
|
||||
- pandocfilters=1.5.0=pyhd3eb1b0_0
|
||||
- parso=0.8.3=pyhd3eb1b0_0
|
||||
- pcre2=10.42=hb066dcc_1
|
||||
- pexpect=4.8.0=pyhd3eb1b0_3
|
||||
- pip=24.0=py310hca03da5_0
|
||||
- platformdirs=3.10.0=py310hca03da5_0
|
||||
- ply=3.11=py310hca03da5_0
|
||||
- prometheus_client=0.14.1=py310hca03da5_0
|
||||
- prompt-toolkit=3.0.43=py310hca03da5_0
|
||||
- prompt_toolkit=3.0.43=hd3eb1b0_0
|
||||
- psutil=5.9.0=py310h1a28f6b_0
|
||||
- ptyprocess=0.7.0=pyhd3eb1b0_2
|
||||
- pure_eval=0.2.2=pyhd3eb1b0_0
|
||||
- pycparser=2.21=pyhd3eb1b0_0
|
||||
- pydantic=2.5.3=py310hca03da5_0
|
||||
- pydantic-core=2.14.6=py310hf0e4da2_0
|
||||
- pygments=2.15.1=py310hca03da5_1
|
||||
- pyqt=5.15.10=py310h313beb8_0
|
||||
- pyqt5-sip=12.13.0=py310h80987f9_0
|
||||
- pysocks=1.7.1=py310hca03da5_0
|
||||
- python=3.10.14=hb885b13_1
|
||||
- python-dateutil=2.9.0post0=py310hca03da5_2
|
||||
- python-fastjsonschema=2.16.2=py310hca03da5_0
|
||||
- python-json-logger=2.0.7=py310hca03da5_0
|
||||
- pytz=2024.1=py310hca03da5_0
|
||||
- pyyaml=6.0.1=py310h80987f9_0
|
||||
- pyzmq=25.1.2=py310h313beb8_0
|
||||
- qt-main=5.15.2=h0917680_10
|
||||
- qtconsole=5.5.1=py310hca03da5_0
|
||||
- qtpy=2.4.1=py310hca03da5_0
|
||||
- readline=8.2=h1a28f6b_0
|
||||
- referencing=0.30.2=py310hca03da5_0
|
||||
- requests=2.32.2=py310hca03da5_0
|
||||
- rfc3339-validator=0.1.4=py310hca03da5_0
|
||||
- rfc3986-validator=0.1.1=py310hca03da5_0
|
||||
- rpds-py=0.10.6=py310hf0e4da2_0
|
||||
- send2trash=1.8.2=py310hca03da5_0
|
||||
- setuptools=69.5.1=py310hca03da5_0
|
||||
- sip=6.7.12=py310h313beb8_0
|
||||
- six=1.16.0=pyhd3eb1b0_1
|
||||
- sniffio=1.3.0=py310hca03da5_0
|
||||
- soupsieve=2.5=py310hca03da5_0
|
||||
- sqlite=3.45.3=h80987f9_0
|
||||
- stack_data=0.2.0=pyhd3eb1b0_0
|
||||
- terminado=0.17.1=py310hca03da5_0
|
||||
- tinycss2=1.2.1=py310hca03da5_0
|
||||
- tk=8.6.14=h6ba3021_0
|
||||
- tomli=2.0.1=py310hca03da5_0
|
||||
- tornado=6.4.1=py310h80987f9_0
|
||||
- traitlets=5.14.3=py310hca03da5_0
|
||||
- typing-extensions=4.11.0=py310hca03da5_0
|
||||
- typing_extensions=4.11.0=py310hca03da5_0
|
||||
- urllib3=2.2.2=py310hca03da5_0
|
||||
- wcwidth=0.2.5=pyhd3eb1b0_0
|
||||
- webencodings=0.5.1=py310hca03da5_1
|
||||
- websocket-client=1.8.0=py310hca03da5_0
|
||||
- wheel=0.43.0=py310hca03da5_0
|
||||
- widgetsnbextension=4.0.10=py310hca03da5_0
|
||||
- xz=5.4.6=h80987f9_1
|
||||
- yaml=0.2.5=h1a28f6b_0
|
||||
- zeromq=4.3.5=h313beb8_0
|
||||
- zlib=1.2.13=h18a0788_1
|
||||
- zstd=1.5.5=hd90d995_2
|
||||
- pip:
|
||||
- aiofiles==23.2.1
|
||||
- altair==5.3.0
|
||||
- arrow==1.3.0
|
||||
- bcrypt==4.1.3
|
||||
- click==8.1.7
|
||||
- contourpy==1.2.1
|
||||
- cycler==0.12.1
|
||||
- dnspython==2.6.1
|
||||
- email-validator==2.2.0
|
||||
- exceptiongroup==1.2.1
|
||||
- fastapi==0.111.0
|
||||
- fastapi-cli==0.0.4
|
||||
- ffmpy==0.3.2
|
||||
- filelock==3.15.4
|
||||
- fonttools==4.53.1
|
||||
- fqdn==1.5.1
|
||||
- fsspec==2024.6.1
|
||||
- gradio==4.37.2
|
||||
- gradio-client==1.0.2
|
||||
- h11==0.14.0
|
||||
- httpcore==1.0.5
|
||||
- httptools==0.6.1
|
||||
- httpx==0.27.0
|
||||
- huggingface-hub==0.23.4
|
||||
- importlib-resources==6.4.0
|
||||
- iniconfig==2.0.0
|
||||
- isoduration==20.11.0
|
||||
- jsonpointer==3.0.0
|
||||
- kiwisolver==1.4.5
|
||||
- markdown-it-py==3.0.0
|
||||
- matplotlib==3.9.1
|
||||
- mdurl==0.1.2
|
||||
- numpy==2.0.0
|
||||
- orjson==3.10.6
|
||||
- pandas==2.2.2
|
||||
- pillow==10.4.0
|
||||
- pluggy==1.5.0
|
||||
- pydub==0.25.1
|
||||
- pyparsing==3.1.2
|
||||
- pytest==8.2.2
|
||||
- python-dotenv==1.0.1
|
||||
- python-multipart==0.0.9
|
||||
- rich==13.7.1
|
||||
- ruff==0.5.1
|
||||
- semantic-version==2.10.0
|
||||
- shellingham==1.5.4
|
||||
- starlette==0.37.2
|
||||
- tomlkit==0.12.0
|
||||
- toolz==0.12.1
|
||||
- tqdm==4.66.4
|
||||
- typer==0.12.3
|
||||
- types-python-dateutil==2.9.0.20241206
|
||||
- tzdata==2024.1
|
||||
- ujson==5.10.0
|
||||
- uri-template==1.3.0
|
||||
- uvicorn==0.30.1
|
||||
- uvloop==0.19.0
|
||||
- watchfiles==0.22.0
|
||||
- webcolors==24.11.1
|
||||
- websockets==11.0.3
|
||||
200
notebooks/Dispersion.ipynb
Normal file
200
notebooks/Dispersion.ipynb
Normal file
@@ -0,0 +1,200 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-21T10:41:27.004729Z",
|
||||
"start_time": "2025-03-21T10:41:26.963236Z"
|
||||
},
|
||||
"collapsed": false,
|
||||
"jupyter": {
|
||||
"outputs_hidden": false
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"import os\n",
|
||||
"sys.path.append(os.path.abspath('..')) # Adds the parent directory to path\n",
|
||||
"from src.user_keypad import UserKeypad\n",
|
||||
"from IPython.display import Markdown, display\n",
|
||||
"from src.models import KeypadSize\n",
|
||||
"from src.utils import random_property_rotation, keypad_md_table\n",
|
||||
"import numpy as np"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Keypad Dispersion\n",
|
||||
"\n",
|
||||
"Keypad dispersion refers to an operation that redistributes the properties assigned to each key on a keypad, ensuring that no property shares a key with a property that was previously adjacent to it.\n",
|
||||
"Keypads are only dispersable if `numb_of_keys <= properites_per_key`. It's used during nKode enrollment to infer property selection.\n",
|
||||
"\n",
|
||||
"A keypad dispersion is completed in two steps:\n",
|
||||
"1. Create a property rotation array; a randomly permuted subset of indices, selected without replacement from a range equal to the number of keys on a keypad, with its length truncated to match the number of properties assigned per key.\n",
|
||||
"2. Rotate each position, similar to a ring or combination lock, by a distance equal to its corresponding value in the property rotation array."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-21T10:41:27.020289Z",
|
||||
"start_time": "2025-03-21T10:41:27.014493Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/markdown": [
|
||||
"\n",
|
||||
"## Example Keypad\n",
|
||||
"5 X 4 keypad (5 keys, 4 properties per key).\n"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/markdown": [
|
||||
"||position 0|position 1|position 2|position 3|\n",
|
||||
"|-|-|-|-|-|\n",
|
||||
"|key 0|1|10|11|100|\n",
|
||||
"|key 1|2|20|22|200|\n",
|
||||
"|key 2|3|30|33|300|\n",
|
||||
"|key 3|4|40|44|400|\n",
|
||||
"|key 4|5|50|55|500|"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"keypad_size = KeypadSize(numb_of_keys=5, props_per_key=4)\n",
|
||||
"props = [1, 10, 11, 100]\n",
|
||||
"keypad_list = []\n",
|
||||
"for key_numb in range(1,keypad_size.numb_of_keys+1):\n",
|
||||
" keypad_list.extend([key_numb * prop for prop in props])\n",
|
||||
"\n",
|
||||
"user_keypad = UserKeypad(keypad_size=keypad_size, keypad=np.array(keypad_list))\n",
|
||||
"display(Markdown(f\"\"\"\n",
|
||||
"## Example Keypad\n",
|
||||
"{keypad_size.numb_of_keys} X {keypad_size.props_per_key} keypad ({keypad_size.numb_of_keys} keys, {keypad_size.props_per_key} properties per key).\n",
|
||||
"\"\"\"))\n",
|
||||
"display(Markdown(keypad_md_table(user_keypad.keypad, keypad_size)))\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Create Property Rotation Array"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-21T10:41:27.065332Z",
|
||||
"start_time": "2025-03-21T10:41:27.056656Z"
|
||||
},
|
||||
"collapsed": false,
|
||||
"jupyter": {
|
||||
"outputs_hidden": false
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Property Rotation: [4 2 0 1]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"prop_rotation = np.random.choice(keypad_size.numb_of_keys, size=keypad_size.props_per_key, replace=False)\n",
|
||||
"print(f\"Property Rotation: {prop_rotation}\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Apply the Rotation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-21T10:41:27.074908Z",
|
||||
"start_time": "2025-03-21T10:41:27.072449Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/markdown": [
|
||||
"||position 0|position 1|position 2|position 3|\n",
|
||||
"|-|-|-|-|-|\n",
|
||||
"|key 0|2|40|11|500|\n",
|
||||
"|key 1|3|50|22|100|\n",
|
||||
"|key 2|4|10|33|200|\n",
|
||||
"|key 3|5|20|44|300|\n",
|
||||
"|key 4|1|30|55|400|"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"dispersed_interface = random_property_rotation(\n",
|
||||
" user_keypad.keypad_matrix(),\n",
|
||||
" prop_rotation\n",
|
||||
")\n",
|
||||
"display(Markdown(keypad_md_table(dispersed_interface.reshape(-1), keypad_size)))"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.14"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
946
notebooks/Enrollment_Login_Renewal_Detailed.ipynb
Normal file
946
notebooks/Enrollment_Login_Renewal_Detailed.ipynb
Normal file
@@ -0,0 +1,946 @@
|
||||
{
|
||||
"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",
|
||||
"sys.path.append(os.path.abspath('..')) # Adds the parent directory to path\n",
|
||||
"from src.nkode_api import NKodeAPI\n",
|
||||
"from src.models import NKodePolicy, KeypadSize\n",
|
||||
"from src.utils import select_keys_with_passcode_values\n",
|
||||
"from secrets import choice\n",
|
||||
"from string import ascii_lowercase\n",
|
||||
"import numpy as np\n",
|
||||
"import bcrypt\n",
|
||||
"import hashlib\n",
|
||||
"import base64\n",
|
||||
"from IPython.display import Markdown, display\n",
|
||||
"\n",
|
||||
"def random_username() -> str:\n",
|
||||
" return \"test_username\" + \"\".join([choice(ascii_lowercase) for _ in range(6)])\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def keypad_view(keypad: np.ndarray, props_per_key: int):\n",
|
||||
" interface_keypad = keypad.reshape(-1, props_per_key)\n",
|
||||
" for idx, key_vals in enumerate(interface_keypad):\n",
|
||||
" print(f\"Key {idx}: {key_vals}\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.446190Z",
|
||||
"start_time": "2025-03-27T19:17:57.443952Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"api = NKodeAPI()\n",
|
||||
"user_icons = np.array([\n",
|
||||
" \"😀\", \"😂\", \"🥳\", \"😍\", \"🤓\",\n",
|
||||
" \"😎\", \"🥺\", \"😡\", \"😱\", \"🤯\",\n",
|
||||
" \"🥰\", \"😴\", \"🤔\", \"🙃\", \"😇\",\n",
|
||||
" \"🤖\", \"👽\", \"👾\", \"🐱\", \"🐶\",\n",
|
||||
" \"🦁\", \"🐻\", \"🐸\", \"🐙\", \"🦄\",\n",
|
||||
" \"🌟\", \"⚡\", \"🔥\", \"🍕\", \"🎉\"\n",
|
||||
"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"Each customer specifies a keypad size and a nkode policy.\n",
|
||||
"The keypad can't be dispersable (`numb_of_keys < properties_per_key`)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Customer Cipher Keys\n",
|
||||
"Each customer has unique cipher keys.\n",
|
||||
"These keys are used to encipher and decipher a user's nKode.\n",
|
||||
"There are two types of Customer Cipher Keys:\n",
|
||||
"1. property key: Combined with the user property key to get the server-side representation of a users icons.\n",
|
||||
"2. position key: Combined with the user position key to get the server-side representation the position in each key.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.487136Z",
|
||||
"start_time": "2025-03-27T19:17:57.475079Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Customer Position Key: [40442 29276 29073 55607 14230 47593]\n",
|
||||
"Customer Properties Key:\n",
|
||||
"[21190 30210 9541 19864 15205 49310]\n",
|
||||
"[55331 19035 8032 36826 40376 8457]\n",
|
||||
"[47892 59102 4159 3691 31648 60302]\n",
|
||||
"[37326 6094 58132 24712 36587 17695]\n",
|
||||
"[62108 52530 63411 22211 34115 22936]\n",
|
||||
"Position to Properties Map:\n",
|
||||
"40442: [21190 55331 47892 37326 62108]\n",
|
||||
"29276: [30210 19035 59102 6094 52530]\n",
|
||||
"29073: [ 9541 8032 4159 58132 63411]\n",
|
||||
"55607: [19864 36826 3691 24712 22211]\n",
|
||||
"14230: [15205 40376 31648 36587 34115]\n",
|
||||
"47593: [49310 8457 60302 17695 22936]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Position Value to Icons Map:\n",
|
||||
"40442: ['😀' '🥺' '🤔' '🐱' '🦄']\n",
|
||||
"29276: ['😂' '😡' '🙃' '🐶' '🌟']\n",
|
||||
"29073: ['🥳' '😱' '😇' '🦁' '⚡']\n",
|
||||
"55607: ['😍' '🤯' '🤖' '🐻' '🔥']\n",
|
||||
"14230: ['🤓' '🥰' '👽' '🐸' '🍕']\n",
|
||||
"47593: ['😎' '😴' '👾' '🐙' '🎉']\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### User Signup\n",
|
||||
"Users can create an nKode with these steps:\n",
|
||||
"1. Generate a randomly shuffled keypad\n",
|
||||
"2. Set user nKode\n",
|
||||
"3. Confirm user nKode\n",
|
||||
"\n",
|
||||
"#### Generate Keypad\n",
|
||||
" For the server to determine the users nKode, the user's keypad must be dispersable.\n",
|
||||
" To make the keypad dispersable, the server will randomly drop key positions so the number of properties per key is equal to the number of keys.\n",
|
||||
" In our case, the server drops 1 key position to give us a 5 X 5 keypad with possible index values ranging from 0-29.\n",
|
||||
" - Run the cell below over and over to see it change. Notice that values never move out of their columns just their rows.\n",
|
||||
" - each value in the keypad is the index value of a customer properties\n",
|
||||
" - the user never learns their server-side properties"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.541997Z",
|
||||
"start_time": "2025-03-27T19:17:57.534379Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/markdown": [
|
||||
"### Icon Keypad"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Key 0: ['🙃' '😱' '🔥' '🍕' '🎉']\n",
|
||||
"Key 1: ['🐶' '🦁' '🐻' '🤓' '🐙']\n",
|
||||
"Key 2: ['🌟' '😇' '😍' '👽' '👾']\n",
|
||||
"Key 3: ['😡' '⚡' '🤯' '🐸' '😴']\n",
|
||||
"Key 4: ['😂' '🥳' '🤖' '🥰' '😎']\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/markdown": [
|
||||
"### Index Keypad"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Key 0: [13 8 27 28 29]\n",
|
||||
"Key 1: [19 20 21 4 23]\n",
|
||||
"Key 2: [25 14 3 16 17]\n",
|
||||
"Key 3: [ 7 26 9 22 11]\n",
|
||||
"Key 4: [ 1 2 15 10 5]\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/markdown": [
|
||||
"### Customer Properties Keypad"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Key 0: [59102 8032 22211 34115 22936]\n",
|
||||
"Key 1: [ 6094 58132 24712 15205 17695]\n",
|
||||
"Key 2: [52530 4159 19864 31648 60302]\n",
|
||||
"Key 3: [19035 63411 36826 36587 8457]\n",
|
||||
"Key 4: [30210 9541 3691 40376 49310]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"User Passcode Indices: [7, 13, 27, 26]\n",
|
||||
"User Passcode Icons: ['😡' '🙃' '🔥' '⚡']\n",
|
||||
"User Passcode Server-side properties: [19035 59102 22211 63411]\n",
|
||||
"Selected Keys: [3, 0, 0, 3]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Key 0: [25 2 27 4 11]\n",
|
||||
"Key 1: [19 14 15 22 29]\n",
|
||||
"Key 2: [ 7 20 3 28 5]\n",
|
||||
"Key 3: [13 26 21 10 17]\n",
|
||||
"Key 4: [ 1 8 9 16 23]\n",
|
||||
"Selected Keys\n",
|
||||
"[2, 3, 0, 3]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Set Key 0: ['😡' '⚡' '🤯' '🐸' '😴']\n",
|
||||
"Confirm Key 0: ['😡' '🦁' '😍' '🍕' '😎']\n",
|
||||
"Overlapping icon 😡\n",
|
||||
"Set Key 1: ['🙃' '😱' '🔥' '🍕' '🎉']\n",
|
||||
"Confirm Key 1: ['🙃' '⚡' '🐻' '🥰' '👾']\n",
|
||||
"Overlapping icon 🙃\n",
|
||||
"Set Key 2: ['🙃' '😱' '🔥' '🍕' '🎉']\n",
|
||||
"Confirm Key 2: ['🌟' '🥳' '🔥' '🤓' '😴']\n",
|
||||
"Overlapping icon 🔥\n",
|
||||
"Set Key 3: ['😡' '⚡' '🤯' '🐸' '😴']\n",
|
||||
"Confirm Key 3: ['🙃' '⚡' '🐻' '🥰' '👾']\n",
|
||||
"Overlapping icon ⚡\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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]]}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## User Cipher\n",
|
||||
"\n",
|
||||
"Users have 4 cipher keys:\n",
|
||||
"1. property_key: The counterpart to the `customer_prop_key`. A user's server-side passcode is composed of elements in `user_prop_key XOR customer_prop_key`.\n",
|
||||
"2. pass_key: The passcode key is used to encipher user passcode\n",
|
||||
"3. combined_position_key: The combined position key is `user_pos_key XOR customer_pos_key`.\n",
|
||||
"4. mask_key: The mask key used to encipher user nKode\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.879460Z",
|
||||
"start_time": "2025-03-27T19:17:57.873816Z"
|
||||
}
|
||||
},
|
||||
"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)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:57.904749Z",
|
||||
"start_time": "2025-03-27T19:17:57.902224Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Property Key:\n",
|
||||
"[[14162 15278 5779 57356 9084 42916]\n",
|
||||
" [62550 57245 40145 52844 62606 28882]\n",
|
||||
" [16963 51204 54863 4741 39274 22253]\n",
|
||||
" [45416 46785 54545 12343 60959 30982]\n",
|
||||
" [23400 12253 26764 20884 19959 11955]]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Passcode Key: [55447 46315 12524 17763 63442 8950 24638 56921 18442 22528]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Mask Key: [37865 47229 1360 12907 33808 18226 62137 23444 5922 45367]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Combined Position Key: [48833 17040 25020 22197 22334 15077]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"User Position Key = combined_pos_key XOR customer_pos_key: [ 9019 12492 4141 36738 24744 33548]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Combined Position to Properties Map:\n",
|
||||
"48833: [14162 62550 16963 45416 23400]\n",
|
||||
"17040: [15278 57245 51204 46785 12253]\n",
|
||||
"25020: [ 5779 40145 54863 54545 26764]\n",
|
||||
"22197: [57356 52844 4741 12343 20884]\n",
|
||||
"22334: [ 9084 62606 39274 60959 19959]\n",
|
||||
"15077: [42916 28882 22253 30982 11955]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"2. Recover the `user_position_key`. Recall `user.cipher.combined_position_key = user_position_key XOR customer.cipher.positon_key`\n",
|
||||
"3. Order the `user_position_key` by the `padded_passcode_position_indices`\n",
|
||||
"4. Mask the `ordered_user_position_key`\n",
|
||||
"5. Base 64 encode the mask"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.179247Z",
|
||||
"start_time": "2025-03-27T19:17:58.176595Z"
|
||||
}
|
||||
},
|
||||
"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)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Encipher Passcode\n",
|
||||
"1. Compute `combined_property_key`\n",
|
||||
"2. Recover `user_passcode = ordered_combined_proptery_key`; order by passcode_property_indices\n",
|
||||
"3. Zero pad `user_pascode`\n",
|
||||
"4. Encipher `user_passcode` with `user.cipher.pass_key`\n",
|
||||
"5. Hash `ciphered_passcode`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.455536Z",
|
||||
"start_time": "2025-03-27T19:17:58.212205Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"combined_prop_key = customer.cipher.property_key ^ user_cipher.property_key\n",
|
||||
"user_passcode = combined_prop_key[passcode_property_indices]\n",
|
||||
"pad_len = customer.nkode_policy.max_nkode_len - passcode_len\n",
|
||||
"padded_passcode = np.concatenate((user_passcode, np.zeros(pad_len, dtype=user_passcode.dtype)))\n",
|
||||
"ciphered_passcode = padded_passcode ^ user_cipher.pass_key\n",
|
||||
"passcode_prehash = base64.b64encode(hashlib.sha256(ciphered_passcode.tobytes()).digest())\n",
|
||||
"passcode_hash = bcrypt.hashpw(passcode_prehash, bcrypt.gensalt(rounds=12)).decode(\"utf-8\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### User Login\n",
|
||||
"1. Get login keypad\n",
|
||||
"2. Select keys with passcode icons (in our case, passcode property indices)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.702891Z",
|
||||
"start_time": "2025-03-27T19:17:58.461555Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Key 0: [12 13 8 27 28 29]\n",
|
||||
"Key 1: [24 19 20 21 4 23]\n",
|
||||
"Key 2: [ 6 25 14 3 16 17]\n",
|
||||
"Key 3: [ 0 7 26 9 22 11]\n",
|
||||
"Key 4: [18 1 2 15 10 5]\n",
|
||||
"User Passcode: [7, 13, 27, 26]\n",
|
||||
"\n",
|
||||
"Selected Keys:\n",
|
||||
" [3, 0, 0, 3]\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Validate Login Key Entry\n",
|
||||
"- decipher user mask and recover nkode position values\n",
|
||||
"- get presumed properties from key selection and position values\n",
|
||||
"- compare with hash"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Decipher Mask\n",
|
||||
"Recover nKode position values:\n",
|
||||
"- decode mask from base64 to int\n",
|
||||
"- ordered_user_position_key = mask ^ mask_key\n",
|
||||
"- user_position_key = user.cipher.co\n",
|
||||
"- deduce the set indices"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.713231Z",
|
||||
"start_time": "2025-03-27T19:17:58.710458Z"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"user = api.customers[customer_id].users[username]\n",
|
||||
"mask = user.cipher.decode_base64_str(user.enciphered_passcode.mask)\n",
|
||||
"ordered_user_position_key = mask ^ user.cipher.mask_key\n",
|
||||
"user_position_key = customer.cipher.position_key ^ user.cipher.combined_position_key"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Get Presumed Properties\n",
|
||||
"- Get the passcode position indices (within the keys)\n",
|
||||
"- Get the presumed property indices from the key and position within the key"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:58.729425Z",
|
||||
"start_time": "2025-03-27T19:17:58.727025Z"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"valid_nkode = user.cipher.compare_nkode(presumed_property_indices, customer.cipher, user.enciphered_passcode.code)\n",
|
||||
"assert valid_nkode"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Renew Properties\n",
|
||||
"1. Renew Customer Keys\n",
|
||||
"2. Intermediate User Keys\n",
|
||||
"3. Renew User Keys on Login\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:59.477909Z",
|
||||
"start_time": "2025-03-27T19:17:58.990632Z"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
" code = api.customers[customer_id].users[username].enciphered_passcode.code\n",
|
||||
" print(f\"mask: {mask}, code: {code}\\n\")\n",
|
||||
"\n",
|
||||
"print(\"Old User Cipher and Mask\")\n",
|
||||
"print_user_enciphered_code()\n",
|
||||
"api.renew_keys(customer_id) # Steps 1 and 2\n",
|
||||
"login_keypad = api.get_login_keypad(username, customer_id)\n",
|
||||
"selected_keys_login = select_keys_with_passcode_values(passcode_property_indices, login_keypad, keypad_size.props_per_key)\n",
|
||||
"success = api.login(customer_id, username, selected_keys_login) # Step 3\n",
|
||||
"print(\"New User Cipher and Mask\")\n",
|
||||
"print_user_enciphered_code()\n",
|
||||
"assert success"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"old_props = customer.cipher.property_key.copy()\n",
|
||||
"old_pos = customer.cipher.position_key.copy()\n",
|
||||
"customer.cipher.property_key = np.random.choice(2 ** 16, size=keypad_size.total_props, replace=False)\n",
|
||||
"customer.cipher.position_key = np.random.choice(2 ** 16, size=keypad_size.props_per_key, replace=False)\n",
|
||||
"new_props = customer.cipher.property_key\n",
|
||||
"new_pos = customer.cipher.position_key"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Intermediate Renew User\n",
|
||||
"User property and position keys go through an intermediate phase.\n",
|
||||
"#### user.cipher.combined_position_key\n",
|
||||
"- user_combined_position_key = user_combined_position_key XOR pos_xor\n",
|
||||
"- user_combined_position_key = (user_position_key XOR old_customer_position_key) XOR (old_customer_position_key XOR new_customer_position_key)\n",
|
||||
"- user_combined_position_key = user_position_key XOR new_customer_position_key\n",
|
||||
"#### user.cipher.combined_position_key\n",
|
||||
"- user_property_key = user_property_key XOR props_xor\n",
|
||||
"- user_property_key = user_property_key XOR old_customer_property_key XOR new_customer_property_key\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-27T19:17:59.500256Z",
|
||||
"start_time": "2025-03-27T19:17:59.497839Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"props_xor = new_props ^ old_props\n",
|
||||
"pos_xor = new_pos ^ old_pos\n",
|
||||
"for user in customer.users.values():\n",
|
||||
" user.renew = True\n",
|
||||
" user.cipher.combined_position_key = user.cipher.combined_position_key ^ pos_xor\n",
|
||||
" user.cipher.property_key = user.cipher.property_key ^ props_xor"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if user.renew:\n",
|
||||
" user.cipher = UserCipher.create(\n",
|
||||
" customer.cipher.keypad_size,\n",
|
||||
" customer.cipher.position_key,\n",
|
||||
" user.cipher.max_nkode_len\n",
|
||||
" )\n",
|
||||
" user.enciphered_passcode = user.cipher.encipher_nkode(presumed_property_indices, customer.cipher)\n",
|
||||
" user.renew = False"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.14"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
245
notebooks/Enrollment_Login_Renewal_Simplified.ipynb
Normal file
245
notebooks/Enrollment_Login_Renewal_Simplified.ipynb
Normal file
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"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",
|
||||
"sys.path.append(os.path.abspath('..')) # Adds the parent directory to path\n",
|
||||
"from src.nkode_api import NKodeAPI\n",
|
||||
"from src.models import NKodePolicy, KeypadSize\n",
|
||||
"from src.utils import select_keys_with_passcode_values\n",
|
||||
"from secrets import choice\n",
|
||||
"from string import ascii_lowercase\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"def random_username() -> str:\n",
|
||||
" return \"test_username\" + \"\".join([choice(ascii_lowercase) for _ in range(6)])\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Initialize nKode API and Create 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",
|
||||
"Each customer specifies a keypad size and a nkode policy.\n",
|
||||
"The keypad can't be dispersable (`numb_of_keys < properties_per_key`)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "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",
|
||||
" max_nkode_len=10,\n",
|
||||
" min_nkode_len=4,\n",
|
||||
" distinct_positions=0, # complexity\n",
|
||||
" distinct_properties=4, # disparity\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.get_customer(customer_id)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## nKode Enrollment\n",
|
||||
"Users enroll in three steps:\n",
|
||||
"1. Create Signup Session\n",
|
||||
"2. Set nKode\n",
|
||||
"3. Confirm nKode\n",
|
||||
"\n",
|
||||
"#### Create Signup Session\n",
|
||||
"A user, associate with customer (or business), specifies a username and receives a signup_session_id and set_keypad. The keypad is a index array. It tells the client how to sort the user's icons."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 32,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-28T15:06:18.914254Z",
|
||||
"start_time": "2025-03-28T15:06:18.911798Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"username = random_username()\n",
|
||||
"signup_session_id, set_keypad = api.generate_signup_keypad(customer_id, username)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### User Login\n",
|
||||
"1. Get login keypad\n",
|
||||
"2. Select keys with passcode icons (in our case, passcode property indices)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 35,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-28T15:06:19.559753Z",
|
||||
"start_time": "2025-03-28T15:06:19.254675Z"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Renew Properties\n",
|
||||
"Replace server-side ciphers keys and nkode hash with new values.\n",
|
||||
"1. Renew Customer Properties\n",
|
||||
"2. Renew User Keys\n",
|
||||
"3. Refresh User on Login"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 36,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-28T15:06:20.181548Z",
|
||||
"start_time": "2025-03-28T15:06:19.568067Z"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 37,
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-28T15:06:20.500050Z",
|
||||
"start_time": "2025-03-28T15:06:20.194912Z"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.14"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
205
notebooks/Split_Shuffle.ipynb
Normal file
205
notebooks/Split_Shuffle.ipynb
Normal file
@@ -0,0 +1,205 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-21T10:15:10.166756Z",
|
||||
"start_time": "2025-03-21T10:15:10.163120Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"import os\n",
|
||||
"sys.path.append(os.path.abspath('..')) # Adds the parent directory to path\n",
|
||||
"from src.models import KeypadSize\n",
|
||||
"from src.user_keypad import UserKeypad\n",
|
||||
"from src.utils import keypad_md_table\n",
|
||||
"import numpy as np\n",
|
||||
"from IPython.display import Markdown, display"
|
||||
],
|
||||
"id": "1f073371d04d02ef",
|
||||
"outputs": [],
|
||||
"execution_count": 37
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## Keypad Split Shuffle\n",
|
||||
"\n",
|
||||
"The split shuffle algorithm aims to increase the number of observations required to decipher an nKode.\n",
|
||||
"For more details, refer to the [Evil nKode](https://github.com/Arcanum-Technology/evil-nkode).\n",
|
||||
"\n",
|
||||
"The positions of the keypad properties are divided into two sets, with each set being shuffled collectively to form a new key."
|
||||
],
|
||||
"id": "651d7d661f4128d"
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"collapsed": true,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-21T10:15:10.177990Z",
|
||||
"start_time": "2025-03-21T10:15:10.172547Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"keypad_size = KeypadSize(numb_of_keys=5, props_per_key=4)\n",
|
||||
"props = [1, 10, 11, 100]\n",
|
||||
"keypad_list = []\n",
|
||||
"for key_numb in range(1,keypad_size.numb_of_keys+1):\n",
|
||||
" keypad_list.extend([key_numb * prop for prop in props])\n",
|
||||
"\n",
|
||||
"user_keypad = UserKeypad(keypad_size=keypad_size, keypad=np.array(keypad_list))\n",
|
||||
"display(Markdown(f\"\"\"\n",
|
||||
"## Example Keypad\n",
|
||||
"{keypad_size.numb_of_keys} X {keypad_size.props_per_key} keypad ({keypad_size.numb_of_keys} keys, {keypad_size.props_per_key} properties per key).\n",
|
||||
"\"\"\"))\n",
|
||||
"keypad_mat = user_keypad.keypad_matrix()\n",
|
||||
"display(Markdown(keypad_md_table(user_keypad.keypad, keypad_size)))"
|
||||
],
|
||||
"id": "initial_id",
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
],
|
||||
"text/markdown": "\n## Example Keypad\n5 X 4 keypad (5 keys, 4 properties per key).\n"
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
],
|
||||
"text/markdown": "||position 0|position 1|position 2|position 3|\n|-|-|-|-|-|\n|key 0|1|10|11|100|\n|key 1|2|20|22|200|\n|key 2|3|30|33|300|\n|key 3|4|40|44|400|\n|key 4|5|50|55|500|"
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"execution_count": 38
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-21T10:15:10.187564Z",
|
||||
"start_time": "2025-03-21T10:15:10.184075Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"np.random.shuffle(keypad_mat)\n",
|
||||
"display(Markdown(\"\"\"\n",
|
||||
"### Step 1\n",
|
||||
"\n",
|
||||
"Shuffle the keys\n",
|
||||
"\"\"\"\n",
|
||||
"))\n",
|
||||
"display(Markdown(keypad_md_table(keypad_mat.reshape(-1), keypad_size)))"
|
||||
],
|
||||
"id": "43db2b9d247f420d",
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
],
|
||||
"text/markdown": "\n### Step 1\n\nShuffle the keys\n"
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
],
|
||||
"text/markdown": "||position 0|position 1|position 2|position 3|\n|-|-|-|-|-|\n|key 0|1|10|11|100|\n|key 1|3|30|33|300|\n|key 2|2|20|22|200|\n|key 3|5|50|55|500|\n|key 4|4|40|44|400|"
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"execution_count": 39
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-21T10:15:10.202941Z",
|
||||
"start_time": "2025-03-21T10:15:10.198267Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"display(Markdown(\"\"\"\n",
|
||||
"### Step 2\n",
|
||||
"\n",
|
||||
"Choose half of the properties and randomly assign them to a new key as a group, then shuffle this group to another new key.\"\"\"\n",
|
||||
"))\n",
|
||||
"prop_permutation = np.random.permutation(keypad_size.props_per_key)[: keypad_size.props_per_key // 2]\n",
|
||||
"print(f\"Selected Group: {prop_permutation}\")\n",
|
||||
"key_permutation = np.random.permutation(keypad_size.numb_of_keys)\n",
|
||||
"# shuffle the selected property sets to new keys as a group\n",
|
||||
"keypad_mat[:, prop_permutation] = keypad_mat[key_permutation, :][:, prop_permutation]\n",
|
||||
"display(Markdown(keypad_md_table(keypad_mat.reshape(-1), keypad_size)))"
|
||||
],
|
||||
"id": "8c322a6c074392e6",
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
],
|
||||
"text/markdown": "\n### Step 2\n\nChoose half of the properties and randomly assign them to a new key as a group, then shuffle this group to another new key."
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Selected Group: [2 0]\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
],
|
||||
"text/markdown": "||position 0|position 1|position 2|position 3|\n|-|-|-|-|-|\n|key 0|2|10|22|100|\n|key 1|4|30|44|300|\n|key 2|1|20|11|200|\n|key 3|5|50|55|500|\n|key 4|3|40|33|400|"
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"execution_count": 40
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2",
|
||||
"version": "2.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## Dispersion"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"from src.user_keypad import UserKeypad\n",
|
||||
"from IPython.display import Markdown, display\n",
|
||||
"from src.models import KeypadSize\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"def keypad_md_table(keypad_list: np.ndarray, keypad_size: KeypadSize) -> str:\n",
|
||||
" assert (keypad_size.numb_of_props == len(keypad_list))\n",
|
||||
" keypad = keypad_list.reshape(-1, keypad_size.props_per_key)\n",
|
||||
" table = \"|key|\" + \"\".join([f\"set{idx}|\" for idx in range(keypad_size.props_per_key)])\n",
|
||||
" table += \"\\n|\" + \"\".join(\"-|\" for _ in range(keypad_size.props_per_key + 1))\n",
|
||||
"\n",
|
||||
" for key in range(keypad_size.numb_of_keys):\n",
|
||||
" table += f\"\\n|key{key+1}|\"\n",
|
||||
" table += \"|\".join([str(attr) for attr in keypad[key]])\n",
|
||||
" table += \"|\"\n",
|
||||
" return table\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"keypad_size = KeypadSize(numb_of_keys=5, props_per_key=4)\n",
|
||||
"attrs = [1, 10, 11, 100]\n",
|
||||
"keypad = []\n",
|
||||
"for key_numb in range(1,keypad_size.numb_of_keys+1):\n",
|
||||
" keypad.extend([key_numb * attr for attr in attrs])\n",
|
||||
"\n",
|
||||
"demo_interface = UserKeypad(keypad_size=keypad_size, keypad=np.array(keypad))\n",
|
||||
"\n",
|
||||
"display(Markdown(keypad_md_table(demo_interface.keypad, keypad_size)))\n"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-14T11:14:49.006056Z",
|
||||
"start_time": "2025-03-14T11:14:48.964902Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
],
|
||||
"text/markdown": "|key|set0|set1|set2|set3|\n|-|-|-|-|-|\n|key1|1|10|11|100|\n|key2|2|20|22|200|\n|key3|3|30|33|300|\n|key4|4|40|44|400|\n|key5|5|50|55|500|"
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"execution_count": 1
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"demo_interface_matrix = demo_interface.keypad.reshape(-1, demo_interface.keypad_size.props_per_key)\n",
|
||||
"shuffled_keys = np.random.permutation(demo_interface_matrix)\n",
|
||||
"shuffled_keys_list = shuffled_keys.reshape(-1)\n",
|
||||
"display(Markdown(keypad_md_table(shuffled_keys_list, keypad_size)))"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-14T11:15:32.023001Z",
|
||||
"start_time": "2025-03-14T11:15:32.009838Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
],
|
||||
"text/markdown": "|key|set0|set1|set2|set3|\n|-|-|-|-|-|\n|key1|4|40|44|400|\n|key2|3|30|33|300|\n|key3|2|20|22|200|\n|key4|1|10|11|100|\n|key5|5|50|55|500|"
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"execution_count": 3
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"attr_rotation = np.random.choice(range(keypad_size.numb_of_keys), size=keypad_size.props_per_key, replace=False)\n",
|
||||
"dispersed_interface = UserKeypad.random_attribute_rotation(\n",
|
||||
" shuffled_keys,\n",
|
||||
" attr_rotation\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"display(Markdown(keypad_md_table(dispersed_interface.reshape(-1), keypad_size)))\n"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-14T11:17:30.317806Z",
|
||||
"start_time": "2025-03-14T11:17:30.313082Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Markdown object>"
|
||||
],
|
||||
"text/markdown": "|key|set0|set1|set2|set3|\n|-|-|-|-|-|\n|key1|2|50|33|100|\n|key2|1|40|22|500|\n|key3|5|30|11|400|\n|key4|4|20|55|300|\n|key5|3|10|44|200|"
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"execution_count": 5
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2",
|
||||
"version": "2.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -1,840 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"from src.nkode_api import NKodeAPI\n",
|
||||
"from src.models import NKodePolicy, KeypadSize\n",
|
||||
"from secrets import choice\n",
|
||||
"from string import ascii_lowercase\n",
|
||||
"import numpy as np"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:40:54.981894Z",
|
||||
"start_time": "2025-03-13T15:40:54.975972Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": 157
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"def random_username() -> str:\n",
|
||||
" return \"test_username\" + \"\".join([choice(ascii_lowercase) for _ in range(6)])\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def select_keys_with_passcode_values(user_passcode: list[int], keypad: np.ndarray, attrs_per_key: int) -> list[int]:\n",
|
||||
" indices = [np.where(keypad == attr)[0][0] for attr in user_passcode]\n",
|
||||
" return [int(index // attrs_per_key) for index in indices]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def keypad_view(keypad: np.ndarray, attrs_per_key: int):\n",
|
||||
" print(\"Keypad View\")\n",
|
||||
" interface_keypad = keypad.reshape(-1, attrs_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-13T15:41:02.574902Z",
|
||||
"start_time": "2025-03-13T15:41:02.566577Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": 159
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"api = NKodeAPI()"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:41:06.702836Z",
|
||||
"start_time": "2025-03-13T15:41:06.697810Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": 160
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"# NKode API\n",
|
||||
"### Customer NKode Policy and Interface\n",
|
||||
"A customer defines their NKode Policy and their interface. Below we've set:\n",
|
||||
"- max nkode length = 10\n",
|
||||
"- min nkode length = 4\n",
|
||||
"- distinct attributes = 4\n",
|
||||
"- distinct set = 0\n",
|
||||
"- byte len = 2\n",
|
||||
"\n",
|
||||
"This customer also has an interface with 5 keys and 6 attributes per key. The number of attributes must be greater than the number of keys to be dispersion resistant."
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"policy = NKodePolicy(\n",
|
||||
" max_nkode_len=10,\n",
|
||||
" min_nkode_len=4,\n",
|
||||
" distinct_sets=0,\n",
|
||||
" distinct_attributes=4,\n",
|
||||
" byte_len=2,\n",
|
||||
")\n",
|
||||
"keypad_size = KeypadSize(\n",
|
||||
" numb_of_keys = 5,\n",
|
||||
" props_per_key = 6 # aka number of sets\n",
|
||||
")\n",
|
||||
"customer_id = api.create_new_customer(keypad_size, policy)\n",
|
||||
"customer = api.customers[customer_id]"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:41:09.768933Z",
|
||||
"start_time": "2025-03-13T15:41:09.760522Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": 161
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### NKode Customer\n",
|
||||
"A customer has users and defines the attributes and set values for all its users.\n",
|
||||
"Since our customer has 5 keys and 6 attributes per key, this gives a customer interface of 30 distinct attributes and 6 distinct attribute sets.\n",
|
||||
"Each attribute belongs to one of the 6 sets."
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"#### Customer and Attribute Values"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"set_vals = customer.cipher.set_key\n",
|
||||
"attr_vals = customer.cipher.prop_key\n",
|
||||
"print(f\"Customer Sets: {set_vals}\")\n",
|
||||
"print(f\"Customer Attributes:\")\n",
|
||||
"customer_prop_keypad = attr_vals.reshape(-1, keypad_size.props_per_key)\n",
|
||||
"for idx, key_vals in enumerate(customer_prop_keypad):\n",
|
||||
" print(f\"{key_vals}\")"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:41:11.949505Z",
|
||||
"start_time": "2025-03-13T15:41:11.941034Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Customer Sets: [25709 8136 44044 46233 29423 25038]\n",
|
||||
"Customer Attributes:\n",
|
||||
"[57582 3595 14389 48861 6004 53011]\n",
|
||||
"[ 2051 49181 8905 9645 63388 60901]\n",
|
||||
"[ 5952 47243 39367 26329 21337 27385]\n",
|
||||
"[ 8812 29822 227 12486 4712 16109]\n",
|
||||
"[28006 29445 47125 35353 35910 6662]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 162
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"#### Customer Set To Attribute Map"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"set_attribute_dict = dict(zip(set_vals, customer_prop_keypad.T))\n",
|
||||
"print(f\"Set to Attribute Map:\")\n",
|
||||
"for set_val, attrs in set_attribute_dict.items():\n",
|
||||
" print(f\"{set_val}: {attrs}\")"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:41:14.890303Z",
|
||||
"start_time": "2025-03-13T15:41:14.883511Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Set to Attribute Map:\n",
|
||||
"25709: [57582 2051 5952 8812 28006]\n",
|
||||
"8136: [ 3595 49181 47243 29822 29445]\n",
|
||||
"44044: [14389 8905 39367 227 47125]\n",
|
||||
"46233: [48861 9645 26329 12486 35353]\n",
|
||||
"29423: [ 6004 63388 21337 4712 35910]\n",
|
||||
"25038: [53011 60901 27385 16109 6662]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 163
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### User Signup\n",
|
||||
"To create a new must call this endpoints in order:\n",
|
||||
"1. Generate Index Interface\n",
|
||||
"2. Set User NKode\n",
|
||||
"3. Confirm User NKode\n",
|
||||
"\n",
|
||||
"#### Generate Index Interface\n",
|
||||
" For the server to determine the users nkode, the user's interface must be dispersable. To make the interface dispersable, the server will randomly drop attribute sets to the number of attributes is equal to the number of keys. In our case, the server drops 1 attribute set to give us a 5 X 5 keypad with possible index values ranging from 0-29.\n",
|
||||
" - Run the cell below over and over to see it change. Notice that values never move out of their columns just their rows.\n",
|
||||
" - each value in the interface is the index value of a customer attribute\n",
|
||||
" - the user never learns what their \"real\" attribute is. All they do is specify an index in the customer interface\n"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"session_id, signup_keypad = api.generate_signup_keypad(customer_id)\n",
|
||||
"print(signup_keypad.reshape(-1, keypad_size.numb_of_keys))"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:41:19.446496Z",
|
||||
"start_time": "2025-03-13T15:41:19.440194Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[[21 3 27 15 9]\n",
|
||||
" [19 1 25 13 7]\n",
|
||||
" [23 5 29 17 11]\n",
|
||||
" [22 4 28 16 10]\n",
|
||||
" [20 2 26 14 8]]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 164
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"#### Set NKode\n",
|
||||
"The user identifies attributes in the interface they want in their nkode. Each attribute in the gui has an index value. Below the user has selected 16, 9, 6, 19. Graphically represent with anything. The only requirement is that the graphical attributes must be associated with the same index value everytime the user goes to login. If the user wants to change anything about their interface(the number of keys, attributes or graphical attributes), they must also change their nkode."
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"keypad_view(signup_keypad, keypad_size.numb_of_keys)\n",
|
||||
"username = random_username()\n",
|
||||
"passcode_len = 4\n",
|
||||
"user_passcode = signup_keypad[:passcode_len]\n",
|
||||
"selected_keys_set = select_keys_with_passcode_values(user_passcode, signup_keypad, keypad_size.numb_of_keys)\n",
|
||||
"print(f\"User Passcode: {user_passcode}\")\n",
|
||||
"print(f\"Selected Keys\\n{selected_keys_set}\")\n",
|
||||
"server_side_attr = [int(customer.cipher.prop_key[idx]) for idx in user_passcode]\n",
|
||||
"print(f\"User Passcode Server-side Attributes: {server_side_attr}\")"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:41:22.138365Z",
|
||||
"start_time": "2025-03-13T15:41:22.130412Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Keypad View\n",
|
||||
"Key 0: [21 3 27 15 9]\n",
|
||||
"Key 1: [19 1 25 13 7]\n",
|
||||
"Key 2: [23 5 29 17 11]\n",
|
||||
"Key 3: [22 4 28 16 10]\n",
|
||||
"Key 4: [20 2 26 14 8]\n",
|
||||
"User Passcode: [21 3 27 15]\n",
|
||||
"Selected Keys\n",
|
||||
"[0, 0, 0, 0]\n",
|
||||
"User Passcode Server-side Attributes: [12486, 48861, 35353, 26329]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 165
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"confirm_interface = api.set_nkode(username, customer_id, selected_keys_set, session_id)\n",
|
||||
"keypad_view(confirm_interface, keypad_size.numb_of_keys)\n",
|
||||
"selected_keys_confirm = select_keys_with_passcode_values(user_passcode, confirm_interface, keypad_size.numb_of_keys)\n",
|
||||
"print(f\"Selected Keys\\n{selected_keys_confirm}\")"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:41:25.094831Z",
|
||||
"start_time": "2025-03-13T15:41:25.087580Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Keypad View\n",
|
||||
"Key 0: [22 2 29 15 7]\n",
|
||||
"Key 1: [23 4 27 13 8]\n",
|
||||
"Key 2: [21 5 25 14 10]\n",
|
||||
"Key 3: [19 3 26 16 11]\n",
|
||||
"Key 4: [20 1 28 17 9]\n",
|
||||
"Selected Keys\n",
|
||||
"[2, 3, 1, 0]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 166
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# the session is deleted after the nkode is confirmed. To rerun this cell, rerun the cells above starting with cell 8 where the username is created\n",
|
||||
"success = api.confirm_nkode(username, customer_id, selected_keys_confirm, session_id)\n",
|
||||
"print(success)"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:43:12.401745Z",
|
||||
"start_time": "2025-03-13T15:42:59.685701Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"True\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 168
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## Enciphering nKode\n",
|
||||
"The method UserCipherKeys.encipher_nkode secures a users nKode in the database. This method is called in api.confirm_nkode\n",
|
||||
"```\n",
|
||||
"class EncipheredNKode(BaseModel):\n",
|
||||
" code: str\n",
|
||||
" mask: str\n",
|
||||
"```\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:43:26.496044Z",
|
||||
"start_time": "2025-03-13T15:43:26.484618Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"from src.user_cipher import UserCipher\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"set_key = np.array([46785, 4782, 4405, 44408, 35377, 55527])\n",
|
||||
"set_key = np.bitwise_xor(set_key, customer.cipher.set_key)\n",
|
||||
"user_keys = UserCipher(\n",
|
||||
" prop_key = np.array([\n",
|
||||
" 57200, 8398, 54694, 25997, 30388,\n",
|
||||
" 46948, 45549, 30364, 49712, 10447,\n",
|
||||
" 9205, 1777, 10731, 30979, 2795,\n",
|
||||
" 17068, 56758, 62574, 28641, 11451,\n",
|
||||
" 26820, 50373, 48783, 25350, 62177,\n",
|
||||
" 60608, 54242, 4637, 3525, 16313\n",
|
||||
" ]),\n",
|
||||
" pass_key=np.array([16090, 38488, 45111, 32674, 46216, 52013, 48980, 36811, 35296, 17206]),\n",
|
||||
" mask_key=np.array([29575, 43518, 44373, 62063, 37651, 31671, 31663, 65514, 36454, 47325]),\n",
|
||||
" set_key=np.array(set_key),\n",
|
||||
" salt=b'$2b$12$fX.in.GGAjz3QBBwqSWc6e',\n",
|
||||
" max_nkode_len=customer.nkode_policy.max_nkode_len, \n",
|
||||
")\n",
|
||||
"\n",
|
||||
"passcode_server_attr = [int(customer.cipher.prop_key[idx]) for idx in user_passcode]\n",
|
||||
"passcode_server_set = [int(customer.cipher.get_prop_set_val(attr)) for attr in passcode_server_attr]\n",
|
||||
"print(f\"Passcode Set Vals: {passcode_server_set}\")\n",
|
||||
"print(f\"Passcode Attr Vals: {passcode_server_attr}\")"
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Passcode Set Vals: [46233, 46233, 46233, 46233]\n",
|
||||
"Passcode Attr Vals: [12486, 48861, 35353, 26329]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 169
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"#### Encipher Mask\n",
|
||||
"Recall:\n",
|
||||
"1. set_key_i = (set_rand_numb_i ^ set_val_i) \n",
|
||||
"2. mask_key_i = mask_rand_numb_i\n",
|
||||
"3. padded_passcode_server_set_i = set_val_i\n",
|
||||
"4. len(set_key) == len(mask_key) == (padded_passcode_server_set) == max_nkode_len == 10\n",
|
||||
"where i is the index\n",
|
||||
" \n",
|
||||
"- mask_i = mask_key_i ^ padded_passcode_server_set_i ^ set_key_i\n",
|
||||
"- mask_i = mask_rand_num_i ^ set_val_i ^ set_rand_numb_i ^ set_val_i\n",
|
||||
"- mask_i = mask_rand_num_i ^ set_rand_numb_i # set_val_i is cancelled out"
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:43:30.557640Z",
|
||||
"start_time": "2025-03-13T15:43:30.550158Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"padded_passcode_server_set = user_keys.pad_user_mask(passcode_server_set, customer.cipher.set_key)\n",
|
||||
"\n",
|
||||
"set_idx = [customer.cipher.get_set_index(set_val) for set_val in padded_passcode_server_set]\n",
|
||||
"mask_set_keys = [user_keys.set_key[idx] for idx in set_idx]\n",
|
||||
"ciphered_mask = np.bitwise_xor(mask_set_keys, padded_passcode_server_set)\n",
|
||||
"ciphered_mask = np.bitwise_xor(ciphered_mask, user_keys.mask_key)\n",
|
||||
"mask = user_keys.encode_base64_str(ciphered_mask)"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 170
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"#### Encipher Passcode\n",
|
||||
"UserCipherKeys.encipher_salt_hash_code:\n",
|
||||
"\n",
|
||||
"- ciphered_customer_attr = alpha_key ^ customer_attr\n",
|
||||
"- ciphered_passcode_i = pass_key_i ^ ciphered_customer_attr_i\n",
|
||||
"- code = hash(ciphered_passcode, salt)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:43:33.209403Z",
|
||||
"start_time": "2025-03-13T15:43:32.898812Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import bcrypt\n",
|
||||
"import hashlib\n",
|
||||
"import base64\n",
|
||||
"\n",
|
||||
"ciphered_customer_attrs = np.bitwise_xor(customer.cipher.prop_key, user_keys.prop_key)\n",
|
||||
"passcode_ciphered_attrs = [ciphered_customer_attrs[idx] for idx in user_passcode]\n",
|
||||
"pad_len = customer.nkode_policy.max_nkode_len - passcode_len\n",
|
||||
"\n",
|
||||
"passcode_ciphered_attrs.extend([0 for _ in range(pad_len)])\n",
|
||||
"\n",
|
||||
"ciphered_code = np.bitwise_xor(passcode_ciphered_attrs, user_keys.pass_key)\n",
|
||||
"\n",
|
||||
"#passcode_bytes = int_array_to_bytes(ciphered_code)\n",
|
||||
"passcode_bytes = ciphered_code.tobytes()\n",
|
||||
"passcode_digest = base64.b64encode(hashlib.sha256(passcode_bytes).digest())\n",
|
||||
"hashed_data = bcrypt.hashpw(passcode_digest, user_keys.salt)\n",
|
||||
"code = hashed_data.decode(\"utf-8\")"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 171
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:43:35.234325Z",
|
||||
"start_time": "2025-03-13T15:43:35.229292Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"from src.models import EncipheredNKode\n",
|
||||
"\n",
|
||||
"enciphered_nkode = EncipheredNKode(\n",
|
||||
" mask=mask,\n",
|
||||
" code=code,\n",
|
||||
")"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 172
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### User Login\n",
|
||||
"1. Get login interface\n",
|
||||
"2. Login\n"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"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(user_passcode, login_keypad, keypad_size.props_per_key)\n",
|
||||
"print(f\"Selected Keys: {selected_keys_login}\")\n",
|
||||
"success = api.login(customer_id, username, selected_keys_login)\n",
|
||||
"print(success)"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:43:55.881744Z",
|
||||
"start_time": "2025-03-13T15:43:37.544889Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Keypad View\n",
|
||||
"Key 0: [18 19 20 21 22 23]\n",
|
||||
"Key 1: [0 1 2 3 4 5]\n",
|
||||
"Key 2: [24 25 26 27 28 29]\n",
|
||||
"Key 3: [12 13 14 15 16 17]\n",
|
||||
"Key 4: [ 6 7 8 9 10 11]\n",
|
||||
"Selected Keys: [0, 1, 2, 3]\n",
|
||||
"True\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 173
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## Validate Login Key Entry\n",
|
||||
"- decipher user mask and recover nkode set values\n",
|
||||
"- get presumed attribute from key selection and set values\n",
|
||||
"- encipher, salt and hash presumed attribute values and compare it to the users hashed code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### Decipher Mask\n",
|
||||
"Recall:\n",
|
||||
"- set_key_i = (set_key_rand_numb_i ^ set_val_i) \n",
|
||||
"- mask_i = mask_key_rand_num_i ^ set_key_rand_numb_i\n",
|
||||
"\n",
|
||||
"Recover nKode set values: \n",
|
||||
"- decode mask from base64 to int\n",
|
||||
"- deciphered_mask = mask ^ mask_key\n",
|
||||
"- deciphered_mask_i = set_key_rand_numb # mask_key_rand_num_i is cancelled out\n",
|
||||
"- set_key_rand_component = set_key ^ set_values\n",
|
||||
"- deduce the set value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:44:12.223540Z",
|
||||
"start_time": "2025-03-13T15:44:12.215204Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"user = api.customers[customer_id].users[username]\n",
|
||||
"decoded_mask = user.cipher.decode_base64_str(user.enciphered_passcode.mask)\n",
|
||||
"deciphered_mask = np.bitwise_xor(decoded_mask, user.cipher.mask_key)\n",
|
||||
"set_key = np.bitwise_xor(customer.cipher.set_key, user.cipher.set_key)\n",
|
||||
"passcode_sets = []\n",
|
||||
"for set_cipher in deciphered_mask[:passcode_len]:\n",
|
||||
" set_idx = np.where(set_key == set_cipher)[0][0]\n",
|
||||
" passcode_sets.append(int(customer.cipher.set_key[set_idx]))\n",
|
||||
"print(passcode_sets)"
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[46233, 46233, 46233, 46233]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 174
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"source": "### Get Presumed Attributes\n"
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:44:41.027416Z",
|
||||
"start_time": "2025-03-13T15:44:15.077074Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"set_vals_idx = [customer.cipher.get_set_index(set_val) for set_val in passcode_sets]\n",
|
||||
"\n",
|
||||
"presumed_selected_attributes_idx = []\n",
|
||||
"for idx in range(passcode_len):\n",
|
||||
" key_numb = selected_keys_login[idx]\n",
|
||||
" set_idx = set_vals_idx[idx]\n",
|
||||
" selected_attr_idx = customer.users[username].user_keypad.get_attr_idx_by_keynumb_setidx(key_numb, set_idx)\n",
|
||||
" presumed_selected_attributes_idx.append(selected_attr_idx)\n",
|
||||
"\n",
|
||||
"print(user_passcode.tolist() == presumed_selected_attributes_idx)"
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"True\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 175
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"source": "### Compare Enciphered Passcodes"
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:48:46.158380Z",
|
||||
"start_time": "2025-03-13T15:48:45.854570Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"enciphered_nkode = user.cipher.encipher_salt_hash_code(presumed_selected_attributes_idx, customer.cipher)\n",
|
||||
"print(enciphered_nkode == user.enciphered_passcode.code)\n"
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"True\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 178
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## Renew Attributes \n",
|
||||
"1. Renew Customer Attributes \n",
|
||||
"2. Renew User Keys\n",
|
||||
"3. Refresh User on Login\n",
|
||||
"\n"
|
||||
],
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:50:03.065695Z",
|
||||
"start_time": "2025-03-13T15:50:02.452807Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"def print_user_enciphered_code():\n",
|
||||
" mask = api.customers[customer_id].users[username].enciphered_passcode.mask\n",
|
||||
" code = api.customers[customer_id].users[username].enciphered_passcode.code\n",
|
||||
" print(f\"mask: {mask}, code: {code}\")\n",
|
||||
"\n",
|
||||
"print_user_enciphered_code() \n",
|
||||
"api.renew_attributes(customer_id)\n",
|
||||
"print_user_enciphered_code()\n",
|
||||
"\n",
|
||||
"login_keypad = api.get_login_keypad(username, customer_id)\n",
|
||||
"selected_keys_login = select_keys_with_passcode_values(user_passcode, login_keypad, keypad_size.props_per_key)\n",
|
||||
"success = api.login(customer_id, username, selected_keys_login)\n",
|
||||
"print(success)\n",
|
||||
"print_user_enciphered_code()"
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"mask: Veawti5Qkgy2y/XtFTZZ7w5oYgA=, code: $2b$12$OHHiqL888FauxiXIVICtouT1xR8iwe79oz63mXkPWKldHoLHPsByy\n",
|
||||
"mask: Veawti5Qkgy2y/XtFTZZ7w5oYgA=, code: $2b$12$OHHiqL888FauxiXIVICtouT1xR8iwe79oz63mXkPWKldHoLHPsByy\n",
|
||||
"True\n",
|
||||
"mask: Sxe3AjutJzGrQLgq/RnfK9G3wHc=, code: $2b$12$.wb5cZjbXJBY6/MQxJIlGu/JyqaF78IM.1n3QP6coxOcKLORU/Io6\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 179
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"#### Renew Customer Keys\n",
|
||||
"- Get old attributes and sets\n",
|
||||
"- Replace attributes and sets"
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:52:30.906955Z",
|
||||
"start_time": "2025-03-13T15:52:30.902577Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"old_props = customer.cipher.prop_key.copy()\n",
|
||||
"old_sets = customer.cipher.set_key.copy()\n",
|
||||
"customer.cipher.renew()\n",
|
||||
"new_props = customer.cipher.prop_key\n",
|
||||
"new_sets = customer.cipher.set_key"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 181
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### Renew User\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:52:39.513715Z",
|
||||
"start_time": "2025-03-13T15:52:39.510395Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"props_xor = np.bitwise_xor(new_props, old_props)\n",
|
||||
"sets_xor = np.bitwise_xor(new_sets, old_sets)\n",
|
||||
"for user in customer.users.values():\n",
|
||||
" user.renew = True\n",
|
||||
" user.cipher.set_key = np.bitwise_xor(user.cipher.set_key, sets_xor)\n",
|
||||
" user.cipher.prop_key = np.bitwise_xor(user.cipher.prop_key, props_xor)"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 182
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "markdown",
|
||||
"source": "### Refresh User Keys"
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-03-13T15:53:22.653638Z",
|
||||
"start_time": "2025-03-13T15:53:22.345351Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"user.cipher = UserCipher.create(\n",
|
||||
" customer.cipher.keypad_size,\n",
|
||||
" customer.cipher.set_key,\n",
|
||||
" user.cipher.max_nkode_len\n",
|
||||
")\n",
|
||||
"user.enciphered_passcode = user.cipher.encipher_nkode(presumed_selected_attributes_idx, customer.cipher)\n",
|
||||
"user.renew = False"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 186
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2",
|
||||
"version": "2.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
bcrypt~=4.1.3
|
||||
numpy~=2.0.0
|
||||
jinja2~=3.1.4
|
||||
pytest~=8.2.2
|
||||
ipython~=8.25.0
|
||||
ipython~=8.25.0
|
||||
jinja2~=3.1.4
|
||||
@@ -1,6 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from uuid import UUID
|
||||
import numpy as np
|
||||
from uuid import UUID, uuid4
|
||||
from src.customer_cipher import CustomerCipher
|
||||
from src.models import NKodePolicy
|
||||
from src.user import User
|
||||
@@ -12,7 +11,18 @@ class Customer:
|
||||
cipher: CustomerCipher
|
||||
users: dict[str, User]
|
||||
|
||||
# TODO: validate policy and keypad_list size don't conflict
|
||||
@classmethod
|
||||
def create(cls, nkode_policy: NKodePolicy, cipher: CustomerCipher) -> 'Customer':
|
||||
if nkode_policy.distinct_positions > cipher.keypad_size.numb_of_keys:
|
||||
raise ValueError("Distinct sets cannot be greater than the number of keys")
|
||||
if nkode_policy.distinct_properties > cipher.keypad_size.total_props:
|
||||
raise ValueError("Distinct properties cannot be greater than the total number of properties")
|
||||
return Customer(
|
||||
customer_id=uuid4(),
|
||||
nkode_policy=nkode_policy,
|
||||
cipher=cipher,
|
||||
users={}
|
||||
)
|
||||
|
||||
def add_new_user(self, user: User):
|
||||
if user.username in self.users:
|
||||
@@ -22,58 +32,51 @@ class Customer:
|
||||
def valid_key_entry(self, username, selected_keys) -> bool:
|
||||
if username not in self.users:
|
||||
raise ValueError(f"User '{username}' does not exist")
|
||||
|
||||
numb_of_keys = self.cipher.keypad_size.numb_of_keys
|
||||
if not all(0 <= key_idx < numb_of_keys for key_idx in selected_keys):
|
||||
raise ValueError(f"Invalid key indices. Must be between 0 and {numb_of_keys - 1}")
|
||||
|
||||
passcode_len = len(selected_keys)
|
||||
user = self.users[username]
|
||||
|
||||
passcode_set_vals = user.cipher.decipher_mask(
|
||||
user.enciphered_passcode.mask, self.cipher.set_key, passcode_len)
|
||||
set_vals_idx = [self.cipher.get_set_index(set_val) for set_val in passcode_set_vals]
|
||||
|
||||
presumed_selected_attributes_idx = []
|
||||
for idx in range(passcode_len):
|
||||
key_numb = selected_keys[idx]
|
||||
set_idx = set_vals_idx[idx]
|
||||
selected_attr_idx = user.user_keypad.get_attr_idx_by_keynumb_setidx(key_numb, set_idx)
|
||||
presumed_selected_attributes_idx.append(selected_attr_idx)
|
||||
|
||||
enciphered_attr = user.cipher.encipher_salt_hash_code(presumed_selected_attributes_idx, self.cipher)
|
||||
if enciphered_attr != user.enciphered_passcode.code:
|
||||
user.enciphered_passcode.mask, self.cipher.position_key, passcode_len)
|
||||
set_vals_idx = [self.cipher.get_position_index(set_val) for set_val in passcode_set_vals]
|
||||
presumed_property_idxs = user.user_keypad.get_prop_idxs_by_keynumb_setidx(selected_keys, set_vals_idx)
|
||||
if not user.cipher.compare_nkode(presumed_property_idxs, self.cipher,user.enciphered_passcode.code):
|
||||
return False
|
||||
|
||||
if user.renew:
|
||||
user.refresh_passcode(presumed_selected_attributes_idx, self.cipher)
|
||||
user.refresh_passcode(presumed_property_idxs, self.cipher)
|
||||
user.user_keypad.split_shuffle()
|
||||
#self.users[username] = user
|
||||
return True
|
||||
|
||||
def renew_keys(self) -> bool:
|
||||
old_attrs = self.cipher.prop_key.copy()
|
||||
old_sets = self.cipher.set_key.copy()
|
||||
old_props = self.cipher.property_key.copy()
|
||||
old_sets = self.cipher.position_key.copy()
|
||||
self.cipher.renew()
|
||||
new_attrs = self.cipher.prop_key
|
||||
new_sets = self.cipher.set_key
|
||||
new_props = self.cipher.property_key
|
||||
new_sets = self.cipher.position_key
|
||||
|
||||
attrs_xor = np.bitwise_xor(new_attrs, old_attrs)
|
||||
set_xor = np.bitwise_xor(new_sets, old_sets)
|
||||
props_xor = new_props ^ old_props
|
||||
set_xor = new_sets ^ old_sets
|
||||
for user in self.users.values():
|
||||
user.renew_keys(set_xor, attrs_xor)
|
||||
user.renew_keys(set_xor, props_xor)
|
||||
self.users[user.username] = user
|
||||
return True
|
||||
|
||||
def valid_new_nkode(self, passcode_attr_idx: list[int]) -> bool:
|
||||
nkode_len = len(passcode_attr_idx)
|
||||
passcode_set_values = [
|
||||
self.cipher.get_prop_set_val(int(self.cipher.prop_key[attr_idx])) for attr_idx in passcode_attr_idx
|
||||
]
|
||||
def valid_new_nkode(self, passcode_prop_idx: list[int]) -> bool:
|
||||
nkode_len = len(passcode_prop_idx)
|
||||
#passcode_set_values = [
|
||||
# self.cipher.get_prop_set_val(int(self.cipher.property_key[prop_idx])) for prop_idx in passcode_prop_idx
|
||||
#]
|
||||
passcode_set_values = self.cipher.get_props_position_vals(passcode_prop_idx)
|
||||
distinct_sets = len(set(passcode_set_values))
|
||||
distinct_attributes = len(set(passcode_attr_idx))
|
||||
distinct_properties = len(set(passcode_prop_idx))
|
||||
if (
|
||||
self.nkode_policy.min_nkode_len <= nkode_len <= self.nkode_policy.max_nkode_len and
|
||||
distinct_sets >= self.nkode_policy.distinct_sets and
|
||||
distinct_attributes >= self.nkode_policy.distinct_attributes
|
||||
distinct_sets >= self.nkode_policy.distinct_positions and
|
||||
distinct_properties >= self.nkode_policy.distinct_properties
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -6,16 +6,13 @@ from src.models import KeypadSize
|
||||
|
||||
@dataclass
|
||||
class CustomerCipher:
|
||||
prop_key: np.ndarray
|
||||
set_key: np.ndarray
|
||||
property_key: np.ndarray
|
||||
position_key: np.ndarray
|
||||
keypad_size: KeypadSize
|
||||
MAX_KEYS: ClassVar[int] = 256
|
||||
MAX_PROP_PER_KEY: ClassVar[int] = 256
|
||||
|
||||
def __post_init__(self):
|
||||
self.check_keys_vs_props()
|
||||
|
||||
def check_keys_vs_props(self) -> None:
|
||||
if self.keypad_size.is_dispersable:
|
||||
raise ValueError("number of keys must be less than the number of "
|
||||
"properties per key to be dispersion resistant")
|
||||
@@ -24,28 +21,40 @@ class CustomerCipher:
|
||||
def create(cls, keypad_size: KeypadSize) -> 'CustomerCipher':
|
||||
if keypad_size.numb_of_keys > cls.MAX_KEYS or keypad_size.props_per_key > cls.MAX_PROP_PER_KEY:
|
||||
raise ValueError(f"Keys and properties per key must not exceed {cls.MAX_KEYS}")
|
||||
|
||||
# Using numpy to generate non-repeating random integers
|
||||
prop_key = np.random.choice(2 ** 16, size=keypad_size.numb_of_props, replace=False)
|
||||
set_key = np.random.choice(2 ** 16, size=keypad_size.props_per_key, replace=False)
|
||||
|
||||
prop_key = np.random.choice(2 ** 16, size=keypad_size.total_props, replace=False)
|
||||
pos_key = np.random.choice(2 ** 16, size=keypad_size.props_per_key, replace=False)
|
||||
return cls(
|
||||
prop_key=prop_key,
|
||||
set_key=set_key,
|
||||
property_key=prop_key,
|
||||
position_key=pos_key,
|
||||
keypad_size=keypad_size,
|
||||
)
|
||||
|
||||
def renew(self):
|
||||
self.prop_key = np.random.choice(2 ** 16, size=self.keypad_size.numb_of_props, replace=False)
|
||||
self.set_key = np.random.choice(2 ** 16, size=self.keypad_size.props_per_key, replace=False)
|
||||
self.property_key = np.random.choice(2 ** 16, size=self.keypad_size.total_props, replace=False)
|
||||
self.position_key = np.random.choice(2 ** 16, size=self.keypad_size.props_per_key, replace=False)
|
||||
|
||||
def get_prop_set_val(self, prop: int) -> int:
|
||||
assert np.isin(prop, self.prop_key)
|
||||
prop_idx = np.where(self.prop_key == prop)[0][0]
|
||||
set_idx = prop_idx % self.keypad_size.props_per_key
|
||||
return int(self.set_key[set_idx])
|
||||
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_set_index(self, set_val: int) -> int:
|
||||
if not np.isin(set_val, self.set_key):
|
||||
raise ValueError(f"Set value {set_val} not found in set values")
|
||||
return int(np.where(self.set_key == set_val)[0][0])
|
||||
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 get_position_index(self, pos_val: int) -> int:
|
||||
if not np.isin(pos_val, self.position_key):
|
||||
raise ValueError(f"Position value {pos_val} not found in customer cipher position_key")
|
||||
return int(np.where(self.position_key == pos_val)[0][0])
|
||||
|
||||
def get_passcode_position_indices_padded(self, passcode_indices: list[int], max_nkode_len: int) -> list[int]:
|
||||
if not all(0 <= idx < self.keypad_size.total_props for idx in passcode_indices):
|
||||
raise ValueError("invalid passcode index")
|
||||
pos_indices = [idx % self.keypad_size.props_per_key for idx in passcode_indices]
|
||||
pad_len = 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()
|
||||
@@ -10,11 +10,11 @@ class EncipheredNKode:
|
||||
class NKodePolicy:
|
||||
max_nkode_len: int = 10
|
||||
min_nkode_len: int = 4
|
||||
distinct_sets: int = 0
|
||||
distinct_attributes: int = 4
|
||||
byte_len: int = 2 # Todo: this should change the total number of bytes an attribute or set value can be
|
||||
distinct_positions: int = 0
|
||||
distinct_properties: int = 4
|
||||
byte_len: int = 2 # Todo: this should change the total number of bytes an properties or set value can be
|
||||
lock_out: int = 5
|
||||
expiration: int = -1 # in seconds -1 means nkode never expires
|
||||
expiration: int = -1 # in seconds -1 means nKode never expires
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -23,9 +23,9 @@ class KeypadSize:
|
||||
numb_of_keys: int
|
||||
|
||||
@property
|
||||
def numb_of_props(self) -> int:
|
||||
def total_props(self) -> int:
|
||||
return self.props_per_key * self.numb_of_keys
|
||||
|
||||
@property
|
||||
def is_dispersable(self) -> bool:
|
||||
return self.props_per_key <= self.numb_of_keys
|
||||
return self.props_per_key <= self.numb_of_keys
|
||||
|
||||
@@ -15,23 +15,28 @@ class NKodeAPI:
|
||||
customers: dict[UUID, Customer] = field(default_factory=dict)
|
||||
signup_sessions: dict[UUID, UserSignupSession] = field(default_factory=dict)
|
||||
|
||||
def get_customer(self, customer_id: UUID) -> Customer:
|
||||
return self.customers[customer_id]
|
||||
|
||||
def create_new_customer(self, keypad_size: KeypadSize, nkode_policy: NKodePolicy) -> UUID:
|
||||
new_customer = Customer(
|
||||
customer_id=uuid4(),
|
||||
new_customer = Customer.create(
|
||||
cipher=CustomerCipher.create(keypad_size),
|
||||
users={},
|
||||
nkode_policy=nkode_policy
|
||||
)
|
||||
self.customers[new_customer.customer_id] = new_customer
|
||||
return new_customer.customer_id
|
||||
|
||||
def generate_signup_keypad(self, customer_id: UUID) -> tuple[UUID, np.ndarray]:
|
||||
def generate_signup_keypad(self, customer_id: UUID, username: str) -> tuple[UUID, np.ndarray]:
|
||||
if customer_id not in self.customers.keys():
|
||||
raise ValueError(f"Customer with ID '{customer_id}' does not exist")
|
||||
customer = self.customers[customer_id]
|
||||
if username in customer.users.keys():
|
||||
raise ValueError(f"Username '{username}' already exists for this customer")
|
||||
customer = self.customers[customer_id]
|
||||
login_keypad = UserKeypad.create(customer.cipher.keypad_size)
|
||||
set_keypad = login_keypad.sign_up_keypad()
|
||||
new_session = UserSignupSession(
|
||||
username=username,
|
||||
session_id=uuid4(),
|
||||
login_keypad=login_keypad,
|
||||
set_keypad=set_keypad.keypad,
|
||||
@@ -43,24 +48,20 @@ class NKodeAPI:
|
||||
|
||||
def set_nkode(
|
||||
self,
|
||||
username: str,
|
||||
customer_id: UUID,
|
||||
key_selection: list[int],
|
||||
session_id: UUID
|
||||
) -> np.ndarray:
|
||||
if customer_id not in self.customers.keys():
|
||||
raise ValueError(f"Customer ID {customer_id} not found")
|
||||
customer = self.customers[customer_id]
|
||||
if username in customer.users.keys():
|
||||
raise ValueError(f"Username '{username}' already exists for this customer")
|
||||
if session_id not in self.signup_sessions.keys():
|
||||
raise ValueError(f"Session ID {session_id} not found")
|
||||
self.signup_sessions[session_id].set_user_nkode(username, key_selection)
|
||||
session = self.signup_sessions[session_id]
|
||||
self.signup_sessions[session_id].set_user_nkode(session.username, key_selection)
|
||||
return self.signup_sessions[session_id].confirm_keypad
|
||||
|
||||
def confirm_nkode(
|
||||
self,
|
||||
username: str,
|
||||
customer_id: UUID,
|
||||
confirm_key_entry: list[int],
|
||||
session_id: UUID
|
||||
@@ -70,18 +71,16 @@ class NKodeAPI:
|
||||
session = self.signup_sessions[session_id]
|
||||
if customer_id != session.customer_id:
|
||||
raise AssertionError(f"Customer ID mismatch: {customer_id} vs {session.customer_id}")
|
||||
if username != session.username:
|
||||
raise AssertionError(f"Username mismatch: {username} vs {session.username}")
|
||||
customer = self.customers[customer_id]
|
||||
passcode = self.signup_sessions[session_id].deduce_passcode(confirm_key_entry)
|
||||
new_user_keys = UserCipher.create(
|
||||
customer.cipher.keypad_size,
|
||||
customer.cipher.set_key,
|
||||
customer.cipher.position_key,
|
||||
customer.nkode_policy.max_nkode_len
|
||||
)
|
||||
enciphered_passcode = new_user_keys.encipher_nkode(passcode, customer.cipher)
|
||||
new_user = User(
|
||||
username=username,
|
||||
username=session.username,
|
||||
enciphered_passcode=enciphered_passcode,
|
||||
cipher=new_user_keys,
|
||||
user_keypad=self.signup_sessions[session_id].login_keypad,
|
||||
@@ -97,7 +96,6 @@ class NKodeAPI:
|
||||
if username not in customer.users.keys():
|
||||
raise ValueError("Username not found")
|
||||
user = customer.users[username]
|
||||
user.user_keypad.partial_keypad_shuffle()
|
||||
# TODO: implement split_keypad_shuffle()
|
||||
return user.user_keypad.keypad
|
||||
|
||||
@@ -107,7 +105,7 @@ class NKodeAPI:
|
||||
customer = self.customers[customer_id]
|
||||
return customer.valid_key_entry(username, key_selection)
|
||||
|
||||
def renew_attributes(self, customer_id: UUID) -> bool:
|
||||
def renew_keys(self, customer_id: UUID) -> bool:
|
||||
if customer_id not in self.customers.keys():
|
||||
raise ValueError("Customer ID not found")
|
||||
return self.customers[customer_id].renew_keys()
|
||||
|
||||
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
|
||||
|
||||
12
src/user.py
12
src/user.py
@@ -18,14 +18,14 @@ class User:
|
||||
|
||||
def renew_keys(self, set_xor: np.ndarray, prop_xor: np.ndarray):
|
||||
self.renew = True
|
||||
self.cipher.set_key = np.bitwise_xor(self.cipher.set_key, set_xor)
|
||||
self.cipher.prop_key = np.bitwise_xor(self.cipher.prop_key, prop_xor)
|
||||
self.cipher.combined_position_key = self.cipher.combined_position_key ^ set_xor
|
||||
self.cipher.property_key = self.cipher.property_key ^ prop_xor
|
||||
|
||||
def refresh_passcode(self, passcode_attr_idx: list[int], customer_attributes: CustomerCipher):
|
||||
def refresh_passcode(self, passcode_prop_idxs: list[int], customer_cipher: CustomerCipher):
|
||||
self.cipher = UserCipher.create(
|
||||
customer_attributes.keypad_size,
|
||||
customer_attributes.set_key,
|
||||
customer_cipher.keypad_size,
|
||||
customer_cipher.position_key,
|
||||
self.cipher.max_nkode_len
|
||||
)
|
||||
self.enciphered_passcode = self.cipher.encipher_nkode(passcode_attr_idx, customer_attributes)
|
||||
self.enciphered_passcode = self.cipher.encipher_nkode(passcode_prop_idxs, customer_cipher)
|
||||
self.renew = False
|
||||
|
||||
@@ -3,149 +3,119 @@ import hashlib
|
||||
from dataclasses import dataclass
|
||||
import bcrypt
|
||||
import numpy as np
|
||||
from secrets import choice
|
||||
from src.models import EncipheredNKode, KeypadSize
|
||||
from src.customer_cipher import CustomerCipher
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserCipher:
|
||||
prop_key: np.ndarray
|
||||
set_key: np.ndarray
|
||||
property_key: np.ndarray
|
||||
combined_position_key: np.ndarray
|
||||
pass_key: np.ndarray
|
||||
mask_key: np.ndarray
|
||||
salt: bytes
|
||||
max_nkode_len: int
|
||||
|
||||
@classmethod
|
||||
def create(cls, keypad_size: KeypadSize, set_values: np.ndarray, max_nkode_len: int) -> 'UserCipher':
|
||||
if len(set_values) != keypad_size.props_per_key:
|
||||
raise ValueError("Invalid set values")
|
||||
|
||||
set_values_array = np.array(set_values, dtype=np.uint16)
|
||||
set_key = generate_random_nonrepeating_array(keypad_size.props_per_key)
|
||||
set_key = np.bitwise_xor(set_key, set_values_array)
|
||||
|
||||
def create(cls, keypad_size: KeypadSize, customer_pos_key: np.ndarray, max_nkode_len: int) -> 'UserCipher':
|
||||
if len(customer_pos_key) != keypad_size.props_per_key:
|
||||
raise ValueError("Invalid position values")
|
||||
user_pos_key = np.random.choice(2**16,size=keypad_size.props_per_key, replace=False)
|
||||
return UserCipher(
|
||||
prop_key=generate_random_nonrepeating_array(keypad_size.props_per_key * keypad_size.numb_of_keys),
|
||||
pass_key=generate_random_nonrepeating_array(max_nkode_len),
|
||||
mask_key=generate_random_nonrepeating_array(max_nkode_len),
|
||||
set_key=set_key,
|
||||
salt=bcrypt.gensalt(),
|
||||
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),
|
||||
combined_position_key=user_pos_key ^ customer_pos_key,
|
||||
max_nkode_len=max_nkode_len
|
||||
)
|
||||
|
||||
def pad_user_mask(self, user_mask: np.ndarray, set_vals: np.ndarray) -> np.ndarray:
|
||||
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 mask is too long")
|
||||
|
||||
user_mask_array = np.array(user_mask, dtype=np.uint16)
|
||||
# Create padding of random choices from set_vals
|
||||
raise ValueError("User encoded_mask is too long")
|
||||
padding_size = self.max_nkode_len - len(user_mask)
|
||||
padding_indices = np.random.choice(len(set_vals), padding_size)
|
||||
padding = np.array([set_vals[i] for i in padding_indices], dtype=np.uint16)
|
||||
# Concatenate original mask with padding
|
||||
padded_user_mask = np.concatenate([user_mask_array, padding])
|
||||
return padded_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(int_array_to_bytes(data)).decode("utf-8")
|
||||
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 _hash_passcode(self, passcode: np.ndarray) -> str:
|
||||
passcode_bytes = int_array_to_bytes(passcode)
|
||||
passcode_digest = base64.b64encode(hashlib.sha256(passcode_bytes).digest())
|
||||
hashed_data = bcrypt.hashpw(passcode_digest, self.salt)
|
||||
return hashed_data.decode("utf-8")
|
||||
|
||||
def encipher_nkode(
|
||||
self,
|
||||
passcode_prop_idx: list[int],
|
||||
customer_cipher: CustomerCipher
|
||||
) -> EncipheredNKode:
|
||||
passcode_prop_idx_array = np.array(passcode_prop_idx, dtype=np.uint16)
|
||||
passcode_attrs = np.array([customer_cipher.prop_key[idx] for idx in passcode_prop_idx_array], dtype=np.uint16)
|
||||
passcode_sets = np.array([customer_cipher.get_prop_set_val(attr) for attr in passcode_attrs], dtype=np.uint16)
|
||||
mask = self.encipher_mask(passcode_sets.tolist(), customer_cipher)
|
||||
code = self.encipher_salt_hash_code(passcode_prop_idx, customer_cipher)
|
||||
mask = self.encipher_mask(passcode_prop_idx, customer_cipher)
|
||||
code = self.hash_nkode(passcode_prop_idx, customer_cipher)
|
||||
return EncipheredNKode(
|
||||
code=code,
|
||||
mask=mask
|
||||
)
|
||||
|
||||
def encipher_salt_hash_code(
|
||||
def hash_nkode(
|
||||
self,
|
||||
passcode_prop_idx: list[int],
|
||||
customer_prop: CustomerCipher,
|
||||
) -> str:
|
||||
passcode_prop_idx_array = np.array(passcode_prop_idx, dtype=np.uint16)
|
||||
passcode_len = len(passcode_prop_idx_array)
|
||||
passcode_attrs = np.array([customer_prop.prop_key[idx] for idx in passcode_prop_idx_array], dtype=np.uint16)
|
||||
salt = bcrypt.gensalt(rounds=12)
|
||||
passcode_bytes = self.prehash_passcode(passcode_prop_idx, customer_prop)
|
||||
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],
|
||||
customer_prop: CustomerCipher,
|
||||
hashed_passcode: str
|
||||
) -> bool:
|
||||
passcode_bytes = self.prehash_passcode(passcode_prop_idx, customer_prop)
|
||||
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],
|
||||
customer_prop: CustomerCipher
|
||||
) -> bytes:
|
||||
passcode_len = len(passcode_prop_idx)
|
||||
passcode_cipher = self.pass_key.copy()
|
||||
for idx in range(passcode_len):
|
||||
attr_idx = passcode_prop_idx_array[idx]
|
||||
passcode_cipher[idx] = passcode_cipher[idx] ^ self.prop_key[attr_idx] ^ passcode_attrs[idx]
|
||||
|
||||
return self._hash_passcode(passcode_cipher)
|
||||
passcode_cipher[:passcode_len] = (
|
||||
passcode_cipher[:passcode_len] ^
|
||||
self.property_key[passcode_prop_idx] ^
|
||||
customer_prop.property_key[passcode_prop_idx]
|
||||
)
|
||||
return passcode_cipher.astype(np.uint16).tobytes()
|
||||
|
||||
def encipher_mask(
|
||||
self,
|
||||
passcode_sets: list[int],
|
||||
customer_attributes: CustomerCipher
|
||||
passcode_prop_idx: list[int],
|
||||
customer_cipher: CustomerCipher
|
||||
) -> str:
|
||||
padded_passcode_sets = self.pad_user_mask(passcode_sets, customer_attributes.set_key)
|
||||
pos_idxs = customer_cipher.get_passcode_position_indices_padded(passcode_prop_idx, len(self.mask_key))
|
||||
ordered_pos_key = self.combined_position_key[pos_idxs]
|
||||
ordered_customer_pos_key = customer_cipher.position_key[pos_idxs]
|
||||
mask = ordered_pos_key ^ ordered_customer_pos_key ^ self.mask_key
|
||||
encoded_mask = self.encode_base64_str(mask)
|
||||
return encoded_mask
|
||||
|
||||
# Get indices of set values
|
||||
set_idx = np.array([customer_attributes.get_set_index(set_val) for set_val in padded_passcode_sets],
|
||||
dtype=np.uint16)
|
||||
mask_set_keys = np.array([self.set_key[idx] for idx in set_idx], dtype=np.uint16)
|
||||
def decipher_mask(self, encoded_mask: str, customer_pos_key: np.ndarray, 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
|
||||
user_pos_key = customer_pos_key ^ self.combined_position_key
|
||||
passcode_position = []
|
||||
for position_val in ordered_user_pos_key[:passcode_len]:
|
||||
position_idx = np.where(user_pos_key == position_val)[0][0]
|
||||
passcode_position.append(int(customer_pos_key[position_idx]))
|
||||
return passcode_position
|
||||
|
||||
# XOR operations
|
||||
ciphered_mask = np.bitwise_xor(mask_set_keys, padded_passcode_sets)
|
||||
ciphered_mask = np.bitwise_xor(ciphered_mask, self.mask_key)
|
||||
|
||||
mask = self.encode_base64_str(ciphered_mask)
|
||||
return mask
|
||||
|
||||
def decipher_mask(self, mask: str, set_vals: np.ndarray, passcode_len: int) -> np.ndarray:
|
||||
set_vals_array = np.array(set_vals, dtype=np.uint16)
|
||||
decoded_mask = self.decode_base64_str(mask)
|
||||
deciphered_mask = np.bitwise_xor(decoded_mask, self.mask_key)
|
||||
|
||||
set_key_rand_component = np.bitwise_xor(set_vals_array, self.set_key)
|
||||
passcode_sets = []
|
||||
|
||||
for set_cipher in deciphered_mask[:passcode_len]:
|
||||
# Find index where values match
|
||||
set_idx = np.where(set_key_rand_component == set_cipher)[0][0]
|
||||
passcode_sets.append(set_vals[set_idx])
|
||||
|
||||
return passcode_sets
|
||||
|
||||
|
||||
# NumPy utility functions to replace the existing ones
|
||||
def generate_random_nonrepeating_array(array_len: int, min_val: int = 0, max_val: int = 2 ** 16) -> np.ndarray:
|
||||
if max_val - min_val < array_len:
|
||||
raise ValueError("Range of values is less than the array length requested")
|
||||
|
||||
# Generate array of random unique integers
|
||||
return np.random.choice(
|
||||
np.arange(min_val, max_val, dtype=np.uint16),
|
||||
size=array_len,
|
||||
replace=False
|
||||
)
|
||||
|
||||
|
||||
def int_array_to_bytes(int_arr: np.ndarray, byte_size: int = 2) -> bytes:
|
||||
return b"".join([int(num).to_bytes(byte_size, byteorder='big') for num in int_arr])
|
||||
@@ -1,5 +1,6 @@
|
||||
from dataclasses import dataclass
|
||||
import numpy as np
|
||||
from src.utils import random_property_rotation
|
||||
from src.models import KeypadSize
|
||||
|
||||
@dataclass
|
||||
@@ -10,7 +11,7 @@ class UserKeypad:
|
||||
@classmethod
|
||||
def create(cls, keypad_size: KeypadSize) -> 'UserKeypad':
|
||||
keypad = UserKeypad(
|
||||
keypad=np.arange(keypad_size.numb_of_props),
|
||||
keypad=np.arange(keypad_size.total_props),
|
||||
keypad_size=keypad_size
|
||||
)
|
||||
keypad.random_keypad_shuffle()
|
||||
@@ -21,13 +22,12 @@ class UserKeypad:
|
||||
raise ValueError("Keypad size is dispersable")
|
||||
self.random_keypad_shuffle()
|
||||
keypad_matrix = self.keypad_matrix()
|
||||
attr_set_view = keypad_matrix.T
|
||||
#attr_set_view = secure_fisher_yates_shuffle(attr_set_view)
|
||||
attr_set_view = np.random.permutation(attr_set_view)
|
||||
attr_set_view = attr_set_view[:self.keypad_size.numb_of_keys]
|
||||
keypad_matrix = attr_set_view.reshape(-1)#matrix_transpose(attr_set_view)
|
||||
prop_set_view = keypad_matrix.T
|
||||
random_sets = np.random.permutation(self.keypad_size.props_per_key)[: self.keypad_size.numb_of_keys]
|
||||
random_sets.sort()
|
||||
prop_set_view = prop_set_view[random_sets, :]
|
||||
return UserKeypad(
|
||||
keypad=keypad_matrix.reshape(-1),#matrix_to_list(keypad_matrix),
|
||||
keypad=prop_set_view.T.reshape(-1),
|
||||
keypad_size=KeypadSize(
|
||||
numb_of_keys=self.keypad_size.numb_of_keys,
|
||||
props_per_key=self.keypad_size.numb_of_keys
|
||||
@@ -38,74 +38,59 @@ class UserKeypad:
|
||||
return self.keypad.reshape(-1,self.keypad_size.props_per_key)
|
||||
|
||||
def random_keypad_shuffle(self):
|
||||
rng = np.random.default_rng()
|
||||
keypad_view = self.keypad_matrix()
|
||||
rng.shuffle(keypad_view, axis=0)
|
||||
np.random.shuffle(keypad_view)
|
||||
set_view = keypad_view.T
|
||||
set_view = rng.permutation(set_view, axis=1)
|
||||
keypad_view = set_view.T
|
||||
self.keypad = keypad_view.reshape(-1)
|
||||
for prop_set in set_view:
|
||||
np.random.shuffle(prop_set)
|
||||
self.keypad = set_view.T.reshape(-1)
|
||||
|
||||
def disperse_keypad(self):
|
||||
if not self.keypad_size.is_dispersable:
|
||||
raise ValueError("Keypad size is not dispersable")
|
||||
rng = np.random.default_rng()
|
||||
#user_keypad_matrix = list_to_matrix(self.keypad, self.keypad_size.props_per_key)
|
||||
user_keypad_matrix = self.keypad_matrix()
|
||||
#shuffled_keys = secure_fisher_yates_shuffle(user_keypad_matrix)
|
||||
shuffled_keys = rng.permutation(user_keypad_matrix, axis=0)
|
||||
#attr_rotation = secure_fisher_yates_shuffle(list(range(self.keypad_size.numb_of_keys)))[:self.keypad_size.props_per_key]
|
||||
attr_rotation = rng.permutation(list(range(self.keypad_size.numb_of_keys)))[:self.keypad_size.props_per_key]
|
||||
dispersed_keypad = self.random_attribute_rotation(
|
||||
keypad_mat = self.keypad_matrix()
|
||||
shuffled_keys = np.random.permutation(keypad_mat)
|
||||
prop_rotation = np.random.choice(self.keypad_size.numb_of_keys, size=self.keypad_size.props_per_key, replace=False)
|
||||
dispersed_keypad = random_property_rotation(
|
||||
shuffled_keys,
|
||||
attr_rotation.tolist(),
|
||||
prop_rotation.tolist(),
|
||||
)
|
||||
self.keypad = dispersed_keypad.reshape(-1)
|
||||
|
||||
def partial_keypad_shuffle(self):
|
||||
# TODO: this should be split shuffle
|
||||
#numb_of_selected_sets = self.keypad_size.props_per_key // 2
|
||||
## randomly shuffle half the sets. if props_per_key is odd, randomly add one 50% of the time
|
||||
#numb_of_selected_sets += choice([0, 1]) if (self.keypad_size.props_per_key & 1) == 1 else 0
|
||||
#selected_sets = secure_fisher_yates_shuffle(list(range(self.keypad_size.props_per_key)))[:numb_of_selected_sets]
|
||||
#user_keypad_matrix = self.keypad_matrix()
|
||||
#shuffled_keys = secure_fisher_yates_shuffle(user_keypad_matrix)
|
||||
#keypad_by_sets = []
|
||||
#for idx, attrs in enumerate(matrix_transpose(shuffled_keys)):
|
||||
# if idx in selected_sets:
|
||||
# keypad_by_sets.append(secure_fisher_yates_shuffle(attrs))
|
||||
# else:
|
||||
# keypad_by_sets.append(attrs)
|
||||
#self.keypad = matrix_to_list(matrix_transpose(keypad_by_sets))
|
||||
pass
|
||||
def split_shuffle(self):
|
||||
# shuffle all keys
|
||||
keypad_mat = self.keypad_matrix()
|
||||
np.random.shuffle(keypad_mat)
|
||||
# select half the property positions
|
||||
prop_permutation = np.random.permutation(self.keypad_size.props_per_key)[: self.keypad_size.props_per_key // 2]
|
||||
key_permutation = np.random.permutation(self.keypad_size.numb_of_keys)
|
||||
# shuffle the selected property sets to new keys as a group
|
||||
keypad_mat[:, prop_permutation] = keypad_mat[key_permutation, :][:, prop_permutation]
|
||||
self.keypad = keypad_mat.reshape(-1)
|
||||
|
||||
@staticmethod
|
||||
def random_attribute_rotation(
|
||||
user_keypad: np.ndarray,
|
||||
attr_rotation: list[int]
|
||||
) -> np.ndarray:
|
||||
transposed = user_keypad.T
|
||||
if len(attr_rotation) != len(transposed):
|
||||
raise ValueError("attr_rotation must be the same length as the number of attributes")
|
||||
for idx, attr_set in enumerate(transposed):
|
||||
rotation = attr_rotation[idx]
|
||||
rotation = rotation % len(attr_set) if len(attr_set) > 0 else 0
|
||||
transposed[idx] = np.roll(attr_set, rotation)
|
||||
return transposed.T
|
||||
|
||||
def attribute_adjacency_graph(self) -> dict[int, set[int]]:
|
||||
def property_adjacency_graph(self) -> dict[int, set[int]]:
|
||||
user_keypad_keypad = self.keypad_matrix()
|
||||
graph = {}
|
||||
for key in user_keypad_keypad:
|
||||
for attr in key:
|
||||
graph[attr] = set(key)
|
||||
graph[attr].remove(attr)
|
||||
for prop in key:
|
||||
graph[prop] = set(key)
|
||||
graph[prop].remove(prop)
|
||||
return graph
|
||||
|
||||
def get_attr_idx_by_keynumb_setidx(self, key_numb: int, set_idx: int) -> int:
|
||||
def get_prop_idx_by_keynumb_setidx(self, key_numb: int, set_idx: int) -> int:
|
||||
if not (0 <= key_numb < self.keypad_size.numb_of_keys):
|
||||
raise ValueError(f"key_numb must be between 0 and {self.keypad_size.numb_of_keys - 1}")
|
||||
if not (0 <= set_idx < self.keypad_size.props_per_key):
|
||||
raise ValueError(f"set_idx must be between 0 and {self.keypad_size.props_per_key - 1}")
|
||||
keypad_attr_idx = self.keypad_matrix()
|
||||
return int(keypad_attr_idx[key_numb][set_idx])
|
||||
raise ValueError(f"padded_passcode_position_indices must be between 0 and {self.keypad_size.props_per_key - 1}")
|
||||
keypad_prop_idx = self.keypad_matrix()
|
||||
return int(keypad_prop_idx[key_numb][set_idx])
|
||||
|
||||
def get_prop_idxs_by_keynumb_setidx(self, key_numb: list[int], set_idx: list[int]) -> list[int]:
|
||||
if len(key_numb) != len(set_idx):
|
||||
raise ValueError("key_numb and padded_passcode_position_indices must be the same length")
|
||||
if not all(0 <= kn < self.keypad_size.numb_of_keys for kn in key_numb):
|
||||
raise ValueError(f"All key_numb must be between 0 and {self.keypad_size.numb_of_keys - 1}")
|
||||
if not all(0 <= si < self.keypad_size.props_per_key for si in set_idx):
|
||||
raise ValueError(f"All padded_passcode_position_indices must be between 0 and {self.keypad_size.props_per_key - 1}")
|
||||
keypad_matrix = self.keypad_matrix()
|
||||
return keypad_matrix[key_numb, set_idx].reshape(-1).tolist()
|
||||
|
||||
@@ -11,22 +11,22 @@ class UserSignupSession:
|
||||
customer_id: UUID
|
||||
login_keypad: UserKeypad
|
||||
keypad_size: KeypadSize
|
||||
username: str
|
||||
set_keypad: Optional[np.ndarray] = None
|
||||
confirm_keypad: Optional[np.ndarray] = None
|
||||
set_key_entry: Optional[np.ndarray] = None
|
||||
username: Optional[str] = None
|
||||
|
||||
def deduce_passcode(self, confirm_key_entry: list[int]) -> list[int]:
|
||||
if not all(0 <= key <= self.keypad_size.numb_of_keys for key in confirm_key_entry):
|
||||
raise ValueError("Key values must be within valid range")
|
||||
attrs_per_key = self.keypad_size.props_per_key
|
||||
props_per_key = self.keypad_size.props_per_key
|
||||
set_key_entry = self.set_key_entry
|
||||
if len(set_key_entry) != len(confirm_key_entry):
|
||||
raise ValueError("Key entry lengths must match")
|
||||
set_keypad = self.set_keypad
|
||||
confirm_keypad = self.confirm_keypad
|
||||
set_key_vals = [set_keypad[key * attrs_per_key:(key + 1) * attrs_per_key] for key in set_key_entry]
|
||||
confirm_key_vals = [confirm_keypad[key * attrs_per_key:(key + 1) * attrs_per_key] for key in
|
||||
set_key_vals = [set_keypad[key * props_per_key:(key + 1) * props_per_key] for key in set_key_entry]
|
||||
confirm_key_vals = [confirm_keypad[key * props_per_key:(key + 1) * props_per_key] for key in
|
||||
confirm_key_entry]
|
||||
passcode = []
|
||||
for idx in range(len(set_key_entry)):
|
||||
|
||||
34
src/utils.py
Normal file
34
src/utils.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import numpy as np
|
||||
|
||||
from src.models import KeypadSize
|
||||
|
||||
|
||||
def random_property_rotation(
|
||||
user_keypad: np.ndarray,
|
||||
prop_rotation: list[int]
|
||||
) -> np.ndarray:
|
||||
transposed = user_keypad.T
|
||||
if len(prop_rotation) != len(transposed):
|
||||
raise ValueError("prop_rotation must be the same length as the number of properties")
|
||||
for idx, prop_set in enumerate(transposed):
|
||||
rotation = prop_rotation[idx]
|
||||
rotation = rotation % len(prop_set) if len(prop_set) > 0 else 0
|
||||
transposed[idx] = np.roll(prop_set, rotation)
|
||||
return transposed.T
|
||||
|
||||
|
||||
def keypad_md_table(keypad_list: np.ndarray, keypad_size: KeypadSize) -> str:
|
||||
assert (keypad_size.total_props == len(keypad_list))
|
||||
keypad = keypad_list.reshape(-1, keypad_size.props_per_key)
|
||||
table = "||" + "".join([f"position {idx}|" for idx in range(keypad_size.props_per_key)])
|
||||
table += "\n|" + "".join("-|" for _ in range(keypad_size.props_per_key + 1))
|
||||
|
||||
for key in range(keypad_size.numb_of_keys):
|
||||
table += f"\n|key {key}|"
|
||||
table += "|".join([str(prop) for prop in keypad[key]])
|
||||
table += "|"
|
||||
return table
|
||||
|
||||
def select_keys_with_passcode_values(user_passcode_idxs: list[int], keypad: np.ndarray, props_per_key: int) -> list[int]:
|
||||
indices = [np.where(keypad == prop)[0][0] for prop in user_passcode_idxs]
|
||||
return [int(index // props_per_key) for index in indices]
|
||||
@@ -3,43 +3,36 @@ import pytest
|
||||
from src.nkode_api import NKodeAPI
|
||||
from src.models import NKodePolicy, KeypadSize
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def nkode_api() -> NKodeAPI:
|
||||
return NKodeAPI()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("keypad_size,passocode_len", [
|
||||
@pytest.mark.parametrize("keypad_size,passcode_len", [
|
||||
(KeypadSize(numb_of_keys=10, props_per_key=11), 4),
|
||||
(KeypadSize(numb_of_keys=10, props_per_key=12), 5),
|
||||
])
|
||||
def test_create_new_user_and_renew_keys(nkode_api, keypad_size, passocode_len):
|
||||
def test_create_new_user_and_renew_keys(nkode_api, keypad_size, passcode_len):
|
||||
username = "test_username"
|
||||
nkode_policy = NKodePolicy() # default policy
|
||||
customer_id = nkode_api.create_new_customer(keypad_size, nkode_policy)
|
||||
session_id, set_keypad = nkode_api.generate_signup_keypad(customer_id)
|
||||
user_passcode = set_keypad[:passocode_len]
|
||||
session_id, set_keypad = nkode_api.generate_signup_keypad(customer_id, username)
|
||||
user_passcode = set_keypad[:passcode_len]
|
||||
|
||||
signup_key_selection = lambda keypad: [int(np.where(keypad == attr)[0][0]) // keypad_size.numb_of_keys for attr in user_passcode]
|
||||
signup_key_selection = lambda keypad: [int(np.where(keypad == prop)[0][0]) // keypad_size.numb_of_keys for prop in user_passcode]
|
||||
set_key_selection = signup_key_selection(set_keypad)
|
||||
|
||||
confirm_keypad = nkode_api.set_nkode(username, customer_id, set_key_selection, session_id)
|
||||
confirm_keypad = nkode_api.set_nkode(customer_id, set_key_selection, session_id)
|
||||
confirm_key_selection = signup_key_selection(confirm_keypad)
|
||||
successful_confirm = nkode_api.confirm_nkode(
|
||||
username,
|
||||
customer_id,
|
||||
confirm_key_selection,
|
||||
session_id
|
||||
)
|
||||
successful_confirm = nkode_api.confirm_nkode(customer_id, confirm_key_selection, session_id)
|
||||
assert successful_confirm
|
||||
|
||||
sign_in_key_selection = lambda keypad: [int(np.where(keypad ==attr)[0][0]) // keypad_size.props_per_key for attr in user_passcode]
|
||||
sign_in_key_selection = lambda keypad: [int(np.where(keypad ==prop)[0][0]) // keypad_size.props_per_key for prop in user_passcode]
|
||||
login_keypad = nkode_api.get_login_keypad(username, customer_id)
|
||||
login_key_selection = sign_in_key_selection(login_keypad)
|
||||
successful_login = nkode_api.login(customer_id, username, login_key_selection)
|
||||
assert successful_login
|
||||
|
||||
successful_renew = nkode_api.renew_attributes(customer_id)
|
||||
successful_renew = nkode_api.renew_keys(customer_id)
|
||||
assert successful_renew
|
||||
|
||||
login_keypad = nkode_api.get_login_keypad(username, customer_id)
|
||||
|
||||
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))
|
||||
@@ -7,9 +7,9 @@ from src.models import KeypadSize
|
||||
"keypad_size",
|
||||
[KeypadSize(numb_of_keys=10, props_per_key=11)]
|
||||
)
|
||||
def test_attr_set_idx(keypad_size):
|
||||
def test_prop_set_idx(keypad_size):
|
||||
user_keypad = UserKeypad.create(keypad_size)
|
||||
for attr_idx in range(keypad_size.numb_of_props):
|
||||
user_keypad_idx = user_keypad.keypad[attr_idx]
|
||||
for prop_idx in range(keypad_size.total_props):
|
||||
user_keypad_idx = user_keypad.keypad[prop_idx]
|
||||
|
||||
assert (attr_idx % keypad_size.props_per_key == user_keypad_idx % keypad_size.props_per_key)
|
||||
assert (prop_idx % keypad_size.props_per_key == user_keypad_idx % keypad_size.props_per_key)
|
||||
|
||||
@@ -11,7 +11,6 @@ from src.user_cipher import UserCipher, CustomerCipher
|
||||
]
|
||||
)
|
||||
def test_encode_decode_base64(passcode_len):
|
||||
#data = generate_random_nonrepeating_list(passcode_len)
|
||||
data = np.random.choice(2**16, passcode_len, replace=False)
|
||||
encoded = UserCipher.encode_base64_str(data)
|
||||
decoded = UserCipher.decode_base64_str(encoded)
|
||||
@@ -28,14 +27,12 @@ def test_encode_decode_base64(passcode_len):
|
||||
])
|
||||
def test_decode_mask(keypad_size, max_nkode_len):
|
||||
customer = CustomerCipher.create(keypad_size)
|
||||
#passcode_entry = generate_random_nonrepeating_list(keypad_size.numb_of_props,max_val=keypad_size.numb_of_props)[:4]
|
||||
passcode_entry = np.random.choice(keypad_size.numb_of_props, 4, replace=False)
|
||||
passcode_values = [customer.prop_key[idx] for idx in passcode_entry]
|
||||
set_vals = customer.set_key
|
||||
user_keys = UserCipher.create(keypad_size, set_vals, max_nkode_len)
|
||||
passcode_entry = np.random.choice(keypad_size.total_props, 4, replace=False)
|
||||
passcode_values = [customer.property_key[idx] for idx in passcode_entry]
|
||||
customer_pos_vals = customer.position_key
|
||||
user_keys = UserCipher.create(keypad_size, customer_pos_vals, max_nkode_len)
|
||||
passcode = user_keys.encipher_nkode(passcode_entry, customer)
|
||||
|
||||
orig_passcode_set_vals = [customer.get_prop_set_val(attr) for attr in passcode_values]
|
||||
passcode_set_vals = user_keys.decipher_mask(passcode.mask, set_vals, len(passcode_entry))
|
||||
assert (len(passcode_set_vals) == len(orig_passcode_set_vals))
|
||||
assert (all(orig_passcode_set_vals[idx] == passcode_set_vals[idx] for idx in range(len(passcode_set_vals))))
|
||||
orig_passcode_pos_vals = customer.get_props_position_vals(passcode_values)
|
||||
passcode_pos_vals = user_keys.decipher_mask(passcode.mask, customer_pos_vals, 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))))
|
||||
|
||||
@@ -5,31 +5,34 @@ from src.models import KeypadSize
|
||||
|
||||
@pytest.fixture()
|
||||
def user_keypad():
|
||||
return UserKeypad.create(keypad_size=KeypadSize(props_per_key=7, numb_of_keys=10))
|
||||
return UserKeypad.create(keypad_size=KeypadSize(props_per_key=5, numb_of_keys=5))
|
||||
|
||||
|
||||
def test_dispersion(user_keypad):
|
||||
for _ in range(10000):
|
||||
pre_dispersion_graph = user_keypad.attribute_adjacency_graph()
|
||||
pre_dispersion_graph = user_keypad.property_adjacency_graph()
|
||||
user_keypad.disperse_keypad()
|
||||
post_dispersion_graph = user_keypad.attribute_adjacency_graph()
|
||||
for attr, adj_graph in pre_dispersion_graph.items():
|
||||
assert (adj_graph.isdisjoint(post_dispersion_graph[attr]))
|
||||
post_dispersion_graph = user_keypad.property_adjacency_graph()
|
||||
for prop, adj_graph in pre_dispersion_graph.items():
|
||||
assert (adj_graph.isdisjoint(post_dispersion_graph[prop]))
|
||||
|
||||
|
||||
#def test_shuffle_attrs(user_keypad):
|
||||
# """there's no easy way to test this. At some point we'll have to run this code thousands of time to see if we get
|
||||
# expected statistical outcomes like:
|
||||
# - every attribute gets to every key with a uniform distribution
|
||||
# - every attribute is adjacent to every other attribute with uniform distribution
|
||||
# - the order in which the cipher move from key to key is random (i.e. the distance traveled is uniform)
|
||||
# """
|
||||
# pre_shuffle_keypad = user_keypad.keypad
|
||||
# user_keypad.partial_keypad_shuffle()
|
||||
# post_shuffle_keypad = user_keypad.keypad
|
||||
# assert (not all(
|
||||
# post_shuffle_keypad[idx] == pre_shuffle_keypad[idx] for idx in range(len(post_shuffle_keypad))
|
||||
# ))
|
||||
# assert (not all(
|
||||
# post_shuffle_keypad[idx] != pre_shuffle_keypad[idx] for idx in range(len(post_shuffle_keypad))
|
||||
# ))
|
||||
def test_shuffle_props(user_keypad):
|
||||
"""there's no easy way to test this. At some point we'll have to run this code thousands of time to see if we get
|
||||
expected statistical outcomes like:
|
||||
- every property gets to every key with a uniform distribution
|
||||
- every property is adjacent to every other property with uniform distribution
|
||||
- the order in which the cipher move from key to key is random (i.e. the distance traveled is uniform)
|
||||
"""
|
||||
pre_shuffle_keypad = user_keypad.keypad.copy()
|
||||
print("")
|
||||
print(user_keypad.keypad_matrix())
|
||||
user_keypad.split_shuffle()
|
||||
post_shuffle_keypad = user_keypad.keypad.copy()
|
||||
print(user_keypad.keypad_matrix())
|
||||
assert (not all(
|
||||
post_shuffle_keypad[idx] == pre_shuffle_keypad[idx] for idx in range(len(post_shuffle_keypad))
|
||||
))
|
||||
assert (not all(
|
||||
post_shuffle_keypad[idx] != pre_shuffle_keypad[idx] for idx in range(len(post_shuffle_keypad))
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user