import base64 import hashlib import os def hash_with_salt(host, salt): """hash_with_salt(bytes, bytes) → bytes[32] Hash the host using sha256 and the give salt""" assert type(host) == bytes assert type(salt) == bytes m = hashlib.sha256() m.update(host) m.update(salt) return m.digest() def generate_salt(): """generate_salt() → bytes[32] Generates 32 bytes of randomness using the system urandom""" return os.urandom(32) def hash_host(host): """hash_host(bytes) → (bytes[32]: salt, bytes[32]: hashed_host) Generates a salt and hashes the host with it""" assert type(host) == bytes salt = generate_salt() hashed_host = hash_with_salt(host, salt) return salt, hashed_host def base64enc(b): """base64enc(bytes) → bytes Uses no padding""" # Base 64 encodes 3 bytes as 4 characters # /byte 1\/byte 2\/byte 3\ # ABCDEFGHijklmnopQRSTUVWX # \64 1/\64 2/\64 3/\64 4/ # # If you have only one or two bytes, you don't have enough bits to # fill all of the characters. The rest of the bits will be taken to # be zeroes. # /byte 1\ # ABCDEFGH0000 # \64 1/\64 2/ # /byte 1\ # # /byte 1\/byte 2\ # ABCDEFGHijklmnop00 # \64 1/\64 2/\64 3/ # # This way you end up with only 2 or 3 characters containing info. # This usually gets padded into a multiple of 4 with =. However, # since the amount of bytes left over mod 4 is enough to generate # the padding back, we can strip it out return base64.b64encode(b).replace(b'=', b'') def base64dec(b64): """base64dec(bytes) → bytes Can handle lack of padding.""" assert type(b64) == bytes # Padded base64 is always a multiple of 4 bytes in length. The # reasoning for this is because base64 decoding operates in groups # of 4 base64 characters. # Since we know the length of the string minus the padding, we can # just pad it to the nearest multiple of 4 missing_padding_len = (4 - len(b64)%4) % 4 padding = b'=' * missing_padding_len return base64.b64decode(b64 + padding, validate = True)