remove extraneous PITKOL file and add main.py for pyotp
This commit is contained in:
parent
0143c5ba48
commit
0fe75141b5
3 changed files with 218 additions and 0 deletions
23
CircuitPy_OTP/LICENSE.txt
Normal file
23
CircuitPy_OTP/LICENSE.txt
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
Some code based off https://github.com/pyotp/pyotp
|
||||||
|
-----------
|
||||||
|
Copyright (C) 2011-2017 Mark Percival <m@mdp.im>,
|
||||||
|
Nathan Reynolds <email@nreynolds.co.uk>, Andrey Kislyuk <kislyuk@gmail.com>,
|
||||||
|
and PyOTP contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
195
CircuitPy_OTP/main.py
Normal file
195
CircuitPy_OTP/main.py
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
import time
|
||||||
|
import machine
|
||||||
|
import network
|
||||||
|
import ntptime
|
||||||
|
import uhashlib
|
||||||
|
import ubinascii
|
||||||
|
import board
|
||||||
|
import bitbangio as io
|
||||||
|
import adafruit_ssd1306
|
||||||
|
|
||||||
|
|
||||||
|
totp = [("Discord ", 'JBSWY3DPEHPK3PXP'), # https://github.com/pyotp/pyotp exmple
|
||||||
|
("Gmail ", 'abcdefghijklmnopqrstuvwxyz234567'),
|
||||||
|
("Accounts", 'asfdkwefoaiwejfa323nfjkl')]
|
||||||
|
ssid = 'my_wifi_ssid'
|
||||||
|
password = 'my_wifi_password'
|
||||||
|
|
||||||
|
TEST = False # if you want to print out the tests the hashers
|
||||||
|
ALWAYS_ON = False # Set to true if you never want to go to sleep!
|
||||||
|
ON_SECONDS = 60 # how long to stay on if not in always_on mode
|
||||||
|
|
||||||
|
i2c = io.I2C(board.SCL, board.SDA)
|
||||||
|
oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
|
||||||
|
|
||||||
|
# Gimme a welcome screen!
|
||||||
|
oled.fill(0)
|
||||||
|
oled.text('CircuitPython', 0, 0)
|
||||||
|
oled.text('PyTOTP Pal!', 0, 10)
|
||||||
|
oled.text(' <3 adafruit <3 ', 0, 20)
|
||||||
|
oled.show()
|
||||||
|
time.sleep(0.25)
|
||||||
|
|
||||||
|
EPOCH_DELTA = 946684800 # seconds between year 2000 and year 1970
|
||||||
|
SECS_DAY = 86400
|
||||||
|
|
||||||
|
SHA1 = uhashlib.sha1
|
||||||
|
|
||||||
|
if TEST:
|
||||||
|
print("===========================================")
|
||||||
|
print("SHA1 test: ", ubinascii.hexlify(SHA1(b'hello world').digest()))
|
||||||
|
# should be 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed
|
||||||
|
|
||||||
|
|
||||||
|
# HMAC implementation, as hashlib/hmac wouldn't fit
|
||||||
|
# From https://en.wikipedia.org/wiki/Hash-based_message_authentication_code
|
||||||
|
def HMAC(k, m):
|
||||||
|
SHA1_BLOCK_SIZE = 64
|
||||||
|
KEY_BLOCK = k + (b'\0' * (SHA1_BLOCK_SIZE - len(k)))
|
||||||
|
KEY_INNER = bytes((x ^ 0x36) for x in KEY_BLOCK)
|
||||||
|
KEY_OUTER = bytes((x ^ 0x5C) for x in KEY_BLOCK)
|
||||||
|
inner_message = KEY_INNER + m
|
||||||
|
outer_message = KEY_OUTER + SHA1(inner_message).digest()
|
||||||
|
return SHA1(outer_message)
|
||||||
|
|
||||||
|
if TEST:
|
||||||
|
KEY = b'abcd'
|
||||||
|
MESSAGE = b'efgh'
|
||||||
|
print("===========================================")
|
||||||
|
print("HMAC test: ", ubinascii.hexlify(HMAC(KEY, MESSAGE).digest()))
|
||||||
|
# should be e5dbcf9263188f9fce90df572afeb39b66b27198
|
||||||
|
|
||||||
|
# Base32 decoder, since base64 lib wouldnt fit
|
||||||
|
def base32_decode(encoded):
|
||||||
|
missing_padding = len(encoded) % 8
|
||||||
|
if missing_padding != 0:
|
||||||
|
encoded += '=' * (8 - missing_padding)
|
||||||
|
encoded = encoded.upper()
|
||||||
|
chunks = [encoded[i:i+8] for i in range(0, len(encoded), 8)]
|
||||||
|
|
||||||
|
out = []
|
||||||
|
for chunk in chunks:
|
||||||
|
bits = 0
|
||||||
|
bitbuff = 0
|
||||||
|
for c in chunk:
|
||||||
|
if 'A' <= c <= 'Z':
|
||||||
|
n = ord(c) - ord('A')
|
||||||
|
elif '2' <= c <= '7':
|
||||||
|
n = ord(c) - ord('2') + 26
|
||||||
|
elif n == '=':
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise ValueError("Not base32")
|
||||||
|
# 5 bits per 8 chars of base32
|
||||||
|
bits += 5
|
||||||
|
# shift down and add the current value
|
||||||
|
bitbuff <<= 5
|
||||||
|
bitbuff |= n
|
||||||
|
# great! we have enough to extract a byte
|
||||||
|
if bits >= 8:
|
||||||
|
bits -= 8
|
||||||
|
byte = bitbuff >> bits # grab top 8 bits
|
||||||
|
bitbuff &= ~(0xFF << bits) # and clear them
|
||||||
|
out.append(byte) # store what we got
|
||||||
|
return out
|
||||||
|
|
||||||
|
if TEST:
|
||||||
|
print("===========================================")
|
||||||
|
print("Base32 test: ", bytes(base32_decode("IFSGCZTSOVUXIIJB")))
|
||||||
|
# should be "Adafruit!!"
|
||||||
|
|
||||||
|
# Turns an integer into a padded-with-0x0 bytestr
|
||||||
|
def int_to_bytestring(i, padding=8):
|
||||||
|
result = []
|
||||||
|
while i != 0:
|
||||||
|
result.insert(0, i & 0xFF)
|
||||||
|
i >>= 8
|
||||||
|
result = [0] * (padding - len(result)) + result
|
||||||
|
return bytes(result)
|
||||||
|
|
||||||
|
# HMAC -> OTP generator, pretty much same as
|
||||||
|
# https://github.com/pyotp/pyotp/blob/master/src/pyotp/otp.py
|
||||||
|
def generate_otp(input, secret, digits=6):
|
||||||
|
if input < 0:
|
||||||
|
raise ValueError('input must be positive integer')
|
||||||
|
hmac_hash = bytearray(HMAC(bytes(base32_decode(secret)), int_to_bytestring(input)).digest())
|
||||||
|
offset = hmac_hash[-1] & 0xf
|
||||||
|
code = ((hmac_hash[offset] & 0x7f) << 24 |
|
||||||
|
(hmac_hash[offset + 1] & 0xff) << 16 |
|
||||||
|
(hmac_hash[offset + 2] & 0xff) << 8 |
|
||||||
|
(hmac_hash[offset + 3] & 0xff))
|
||||||
|
str_code = str(code % 10 ** digits)
|
||||||
|
while len(str_code) < digits:
|
||||||
|
str_code = '0' + str_code
|
||||||
|
|
||||||
|
return str_code
|
||||||
|
|
||||||
|
print("===========================================")
|
||||||
|
|
||||||
|
# Set up networking
|
||||||
|
sta_if = network.WLAN(network.STA_IF)
|
||||||
|
|
||||||
|
oled.fill(0)
|
||||||
|
oled.text('Connecting to', 0, 0)
|
||||||
|
oled.text(ssid, 0, 10)
|
||||||
|
oled.show()
|
||||||
|
|
||||||
|
if not sta_if.isconnected():
|
||||||
|
print("Connecting to SSID", ssid)
|
||||||
|
sta_if.active(True)
|
||||||
|
sta_if.connect(ssid, password)
|
||||||
|
while not sta_if.isconnected():
|
||||||
|
pass
|
||||||
|
print("Connected! IP = ", sta_if.ifconfig()[0])
|
||||||
|
|
||||||
|
# Done! Let them know we made it
|
||||||
|
oled.text("IP: " + sta_if.ifconfig()[0], 0, 20)
|
||||||
|
oled.show()
|
||||||
|
time.sleep(0.25)
|
||||||
|
|
||||||
|
# Get the latest time from NTP
|
||||||
|
t = None
|
||||||
|
while not t:
|
||||||
|
try:
|
||||||
|
t = ntptime.time()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# NTP time is seconds-since-2000
|
||||||
|
print("NTP time: ", t)
|
||||||
|
|
||||||
|
# But we need Unix time, which is seconds-since-1970
|
||||||
|
t += EPOCH_DELTA
|
||||||
|
print("Unix time: ", t)
|
||||||
|
|
||||||
|
# Instead of using RTC which means converting back and forth
|
||||||
|
# we'll just keep track of seconds-elapsed-since-NTP-call
|
||||||
|
mono_time = int(time.monotonic())
|
||||||
|
print("Monotonic time", mono_time)
|
||||||
|
|
||||||
|
countdown = ON_SECONDS # how long to stay on if not in always_on mode
|
||||||
|
while ALWAYS_ON or (countdown > 0):
|
||||||
|
# Calculate current time based on NTP + monotonic
|
||||||
|
unix_time = t - mono_time + int(time.monotonic())
|
||||||
|
print("Unix time: ", unix_time)
|
||||||
|
|
||||||
|
# Clear the screen
|
||||||
|
oled.fill(0)
|
||||||
|
y = 0
|
||||||
|
# We can do up to 3 per line on the Feather OLED
|
||||||
|
for name,secret in totp:
|
||||||
|
otp = generate_otp(unix_time//30, secret)
|
||||||
|
print(name + " OTP output: ", otp) # serial debugging output
|
||||||
|
oled.text(name + ": "+ str(otp), 0, y) # display name & OTP on OLED
|
||||||
|
y += 10 # Go to next line on OLED
|
||||||
|
# We'll display a little bar that 'counts down' how many seconds you have left
|
||||||
|
oled.framebuf.line(0,31, 128 - (unix_time % 30)*4,31, True)
|
||||||
|
oled.show()
|
||||||
|
# We'll update every 1/4 second, we can hash very fast so its no biggie!
|
||||||
|
countdown -= 0.25
|
||||||
|
time.sleep(0.25)
|
||||||
|
|
||||||
|
# All these hashes will be lost in time(), like tears in rain. Time to die
|
||||||
|
oled.fill(0)
|
||||||
|
oled.show()
|
||||||
BIN
Piano_In_The_Key_Of_Lime/.DS_Store
vendored
BIN
Piano_In_The_Key_Of_Lime/.DS_Store
vendored
Binary file not shown.
Loading…
Reference in a new issue