diff --git a/CircuitPy_OTP/LICENSE.txt b/CircuitPy_OTP/LICENSE.txt new file mode 100644 index 00000000..1b73203c --- /dev/null +++ b/CircuitPy_OTP/LICENSE.txt @@ -0,0 +1,23 @@ +Some code based off https://github.com/pyotp/pyotp +----------- +Copyright (C) 2011-2017 Mark Percival , +Nathan Reynolds , Andrey Kislyuk , +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. \ No newline at end of file diff --git a/CircuitPy_OTP/main.py b/CircuitPy_OTP/main.py new file mode 100644 index 00000000..e596f243 --- /dev/null +++ b/CircuitPy_OTP/main.py @@ -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() diff --git a/Piano_In_The_Key_Of_Lime/.DS_Store b/Piano_In_The_Key_Of_Lime/.DS_Store deleted file mode 100644 index 5008ddfc..00000000 Binary files a/Piano_In_The_Key_Of_Lime/.DS_Store and /dev/null differ