Skip to content

Commit 3a6f3ed

Browse files
committed
Updated password prompting mechanism
1 parent 1ece9ab commit 3a6f3ed

File tree

3 files changed

+68
-20
lines changed

3 files changed

+68
-20
lines changed

[email protected]

89.5 KB
Loading

README.md

+35-8
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,61 @@ Generates Time-based One-Time Password's (TOTP) using MicroPython, Raspberry Pi
2424
- Open an interactive session on the Pico and encrypt the `codes.json` and `WifiSecrets.json` as below:
2525
```
2626
>>> import cryptor
27-
>>> cryptor.encrypt('codes.json','mypasswd')
28-
>>> cryptor.encrypt('WifiSecrets.json','mypasswd')
27+
>>> key='your8chr'
28+
>>> cryptor.encrypt('codes.json',key)
29+
>>> cryptor.encrypt('WifiSecrets.json',key)
2930
```
3031
- Remove the unencrypted `WifiSecrets.json` and `codes.json` from the Pico W storage
3132
- Copy main.py to the Pico
3233
- Reset the Pico W
3334
- Enter your password at the initial password prompt.
34-
- Fix datetime at prompt if Wifi doesn't work.
35+
- Fix datetime at Date time prompt if Wifi doesn't work.
3536
- Now you can cycle through your TOTP's using Key0 of the Pico-Oled-1.3.
3637
- Key1 of the Pico-Oled-1.3 toggles the display ON/OFF.
3738

38-
# Updating secrets
39+
## Password length, range and security
40+
41+
The secrets are encrypted using a simple scheme using an 8 character password. However valid characters are any ASCII
42+
character with *the following exceptions*:
43+
- Space and non-blocking space
44+
- Quotes: `"`, `'`,`` ` `` (including backquote/backtick)
45+
- backslash - `\`,
46+
- Hyphen: `-`
47+
48+
The password is taken as a 8 pairs of 2 digits, each pair representing the ordinal of a character of the password. Each digit takes
49+
a seperate input, leading to quicker input with just increment/decrement keys (worst case is 12 keypresses per characters encoded as
50+
numbers, as compared to 46 with incrementing characters)
51+
52+
<img src="[email protected]" />
53+
54+
The SHA256 digest of this key is generated and used as the encryption key for the secret files.
55+
56+
There are 89 valid characters (128 - 32 - 2 - 5) and so 89^8 possible passwords, so it's around a day's worth of effort to crack this scheme.
57+
58+
However, additional security can be achieved by taking another number as input, and then running those many rounds of hashing on the hash results to generate the final hashed key.
59+
60+
But that's future work for the paranoid forker.
61+
62+
63+
## Updating secrets
3964

4065
- Connect to the REPL
4166
- Press CTRL-C to stop the code and bring up the prompt
4267
- Run the following code to dump the secrets files to console as plain text:
4368
```
4469
>>> import cryptor
45-
>>> print(cryptor.decrypt('codes.json.encoded','mypasswd').decode())
46-
>>> print(cryptor.decrypt('WifiSecrets.json.encoded','mypasswd').decode())
70+
>>> key='your8chr'
71+
>>> print(cryptor.decrypt('codes.json.encoded',key).decode())
72+
>>> print(cryptor.decrypt('WifiSecrets.json.encoded',key).decode())
4773
```
4874
- Copy the outputs into `codes.json` and `WifiSecrets.json` on your workstation, and update with new secrets as required.
4975
- Push the updated `codes.json` and `WifiSecrets.json` to the Pico W.
5076
- Encrypt `codes.json` and `WifiSecrets.json` and overwrite previous encoded files as below:
5177
```
5278
>>> import cryptor
53-
>>> cryptor.encrypt('codes.json','mypasswd')
54-
>>> cryptor.encrypt('WifiSecrets.json','mypasswd')
79+
>>> key='your8chr'
80+
>>> cryptor.encrypt('codes.json',key)
81+
>>> cryptor.encrypt('WifiSecrets.json',key)
5582
```
5683
- Remove the unencrypted `WifiSecrets.json` and `codes.json` from the Pico W storage.
5784

password.py

+33-12
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33

44
def get(display):
55
# year, month, day, hours, minutes, seconds)
6-
password=[0,0,0,0,0,0,0,0]
6+
# 8 2-digit numbers
7+
password=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
78
# Rollover limits
8-
chars=b'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+=/?[]{};:,.<>|~'
9+
chars=b'!#$%&()*+,./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~'
910
clen=len(chars)
1011
selected_idx = 0
1112
max_idx=len(password)
@@ -18,7 +19,6 @@ def get(display):
1819
display.text("Dec", 0, display_height - 9)
1920
display.text("Next", display_width - 30, display_height - 9)
2021

21-
display.text(" C C C C C C C C", 14, display_height // 2 - 15)
2222

2323
changed=True
2424

@@ -30,26 +30,47 @@ def get(display):
3030
if display.is_pressed(display.KEY0):
3131
changed=True
3232
password[selected_idx] += 1
33-
if password[selected_idx] == clen:
34-
password[selected_idx] = 0
33+
if selected_idx%2:
34+
if (password[selected_idx-1]*10+password[selected_idx]) >= clen or (password[selected_idx-1]//10 < clen//10 and password[selected_idx]>9):
35+
password[selected_idx] = 0
36+
else:
37+
if (password[selected_idx]*10+password[selected_idx+1]) >= clen:
38+
password[selected_idx] = 0
3539
if display.is_pressed(display.KEY1):
3640
changed=True
3741
password[selected_idx] -= 1
38-
if password[selected_idx] < 0:
39-
password[selected_idx] = clen-1
42+
if selected_idx%2:
43+
if password[selected_idx] < 0:
44+
if password[selected_idx-1]//10 < clen//10:
45+
password[selected_idx] = 9
46+
else:
47+
password[selected_idx] = (clen%10)-1
48+
else:
49+
if password[selected_idx] < 0:
50+
if password[selected_idx+1] >= clen%10:
51+
password[selected_idx] = (clen//10)-1
52+
else:
53+
password[selected_idx] = (clen//10)
4054
else:
4155
if display.is_pressed(display.KEY0):
4256
break
4357

4458
if changed:
45-
display.fill_rect(0, display_height // 2, display_width, 9, 0x0000)
59+
# Clear from (0,display_height//2 - 20),till (display_width, display_height // 2 + 10 + 7)
60+
display.fill_rect(0, display_height // 2 -20, display_width, 37, 0x0000)
4661
display.text(
47-
" ".join("%s%c" % (">" if idx == selected_idx else "", chars[sep])
48-
for idx, sep in enumerate(password)),
49-
21, display_height // 2+5)
62+
" ".join("%c" % chars[j] for j in [i[0]*10+i[1] for i in [password[k:k+2] for k in range(0,len(password),2)]]),
63+
14, display_height // 2 - 20)
64+
display.text(
65+
"".join("%02i" % (i[0]*10+i[1]) for i in [password[k:k+2] for k in range(0,len(password),2)]),
66+
(display_width-16*7)//2, display_height // 2)
67+
5068
if selected_idx < max_idx:
5169
display.fill_rect(0, 0, 21, 9, 0x0000)
5270
display.text("Inc", 0, 0)
71+
display.text("^", (display_width-16*7)//2 - 1 + selected_idx*7, display_height // 2 + 10)
72+
display.rect((display_width-16*7)//2 - 1 + ((selected_idx>>1)<<1)*7, display_height//2 - 2 ,15,1,0xffff)
73+
5374
else:
5475
display.fill_rect(0, 0, 21, 9, 0x0000)
5576
display.fill_rect(0, 0, 14, 9, 0xffff)
@@ -60,4 +81,4 @@ def get(display):
6081
time.sleep_ms(100)
6182

6283
display.clear()
63-
return "".join(chr(chars[sep]) for idx, sep in enumerate(password))
84+
return "".join(chr(chars[j]) for j in [i[0]*10+i[1] for i in [password[k:k+2] for k in range(0,len(password),2)]])

0 commit comments

Comments
 (0)