Last active
November 27, 2023 03:36
-
-
Save DavesCodeMusings/5028da2b532f810d6a48844f7932f2b5 to your computer and use it in GitHub Desktop.
Password Hashing for MicroPython
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Password hashing class that works with MicroPython v1.21 | |
| # There's no SHA512 or bcrypt in MicroPython, so use SHA256 and AES. | |
| # Close to a Unix-like system password, but not compatible. | |
| from random import choice, seed | |
| from cryptolib import aes | |
| from hashlib import sha256 | |
| from binascii import b2a_base64 | |
| class SHA256AES: | |
| """ | |
| Password hashing for MicroPython. | |
| """ | |
| _method_token = "5a" # This is made up. Not a standard. | |
| @staticmethod | |
| def generate_salt(length): | |
| """ | |
| Generate a random-character salt suitable for password hashing. | |
| """ | |
| valid_chars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | |
| seed() | |
| salt = "" | |
| for i in range(length): | |
| salt += choice(valid_chars) | |
| return salt | |
| @staticmethod | |
| def create_salted_hash(salt, cleartext): | |
| """ | |
| Given a salt value and password in cleartext, return a hashed password. | |
| """ | |
| assert(len(salt) % 16 == 0) | |
| cleartext = bytes(cleartext, "utf8") | |
| cleartext += bytearray(16 - (len(cleartext) % 16)) # Pad out to 16-byte boundry | |
| cipher = aes(salt, 1) # 1 is MODE_ECB, the only one supported by MicroPython | |
| salted_pw = cipher.encrypt(cleartext) | |
| return b2a_base64(sha256(salted_pw).digest()).decode('utf8').rstrip('\n') | |
| @staticmethod | |
| def create_passwd_entry(cleartext): | |
| salt = SHA256AES.generate_salt(16) # Must be evenly divisible by 16 for AES | |
| hashed_pw = SHA256AES.create_salted_hash(salt, cleartext) | |
| return f"${SHA256AES._method_token}${salt}${hashed_pw}" | |
| @staticmethod | |
| def verify_passwd_entry(hashed, cleartext): | |
| """ | |
| Given a hashed password, verify the cleartext password is valid. | |
| """ | |
| if hashed.count("$") != 3: | |
| print("Invalid hashed password format.") | |
| return False | |
| else: | |
| _, hash_alg, salt, hashed_pw = hashed.split("$") | |
| if hash_alg != SHA256AES._method_token: | |
| print("Unsupported hash algorithm.") | |
| else: | |
| rehashed_pw = SHA256AES.create_salted_hash(salt, cleartext) | |
| if hashed_pw != rehashed_pw: | |
| return False | |
| else: | |
| return True | |
| # Hash a password for a user | |
| user_name = "craig" | |
| user_pw = "ByeFelisha" | |
| passwd_entry = SHA256AES.create_passwd_entry(user_pw) | |
| print(f"Credential entry: {user_name}:{passwd_entry}") | |
| # Verify using wrong cleartext password | |
| authorized = SHA256AES.verify_passwd_entry(passwd_entry, "BadPassword") | |
| print("Verify wrong password (should be false):", authorized) | |
| # Verify using same cleartext password | |
| authorized = SHA256AES.verify_passwd_entry(passwd_entry, user_pw) | |
| print("Verify correct password (should be true):", authorized) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment