@ -189,23 +189,19 @@ def _hash_password(password: Union[bytes, str], *, version: int) -> bytes:
raise UnexpectedPasswordHashVersion ( version )
def pw_encode_bytes ( data : bytes , password : Union [ bytes , str ] , * , version : int ) - > str :
""" plaintext bytes -> base64 ciphertext """
def _pw_encode_raw ( data : bytes , password : Union [ bytes , str ] , * , version : int ) - > bytes :
if version not in KNOWN_PW_HASH_VERSIONS :
raise UnexpectedPasswordHashVersion ( version )
# derive key from password
secret = _hash_password ( password , version = version )
# encrypt given data
ciphertext = EncodeAES_bytes ( secret , data )
ciphertext_b64 = base64 . b64encode ( ciphertext )
return ciphertext_b64 . decode ( ' utf8 ' )
return ciphertext
def pw_decode_bytes ( data : str , password : Union [ bytes , str ] , * , version : int ) - > bytes :
""" base64 ciphertext -> plaintext bytes """
def _pw_decode_raw ( data_bytes : bytes , password : Union [ bytes , str ] , * , version : int ) - > bytes :
if version not in KNOWN_PW_HASH_VERSIONS :
raise UnexpectedPasswordHashVersion ( version )
data_bytes = bytes ( base64 . b64decode ( data ) )
# derive key from password
secret = _hash_password ( password , version = version )
# decrypt given data
@ -216,6 +212,46 @@ def pw_decode_bytes(data: str, password: Union[bytes, str], *, version: int) ->
return d
def pw_encode_bytes ( data : bytes , password : Union [ bytes , str ] , * , version : int ) - > str :
""" plaintext bytes -> base64 ciphertext """
ciphertext = _pw_encode_raw ( data , password , version = version )
ciphertext_b64 = base64 . b64encode ( ciphertext )
return ciphertext_b64 . decode ( ' utf8 ' )
def pw_decode_bytes ( data : str , password : Union [ bytes , str ] , * , version : int ) - > bytes :
""" base64 ciphertext -> plaintext bytes """
if version not in KNOWN_PW_HASH_VERSIONS :
raise UnexpectedPasswordHashVersion ( version )
data_bytes = bytes ( base64 . b64decode ( data ) )
return _pw_decode_raw ( data_bytes , password , version = version )
def pw_encode_with_version_and_mac ( data : bytes , password : Union [ bytes , str ] ) - > str :
""" plaintext bytes -> base64 ciphertext """
# https://crypto.stackexchange.com/questions/202/should-we-mac-then-encrypt-or-encrypt-then-mac
# Encrypt-and-MAC. The MAC will be used to detect invalid passwords
version = PW_HASH_VERSION_LATEST
mac = sha256 ( data ) [ 0 : 4 ]
ciphertext = _pw_encode_raw ( data , password , version = version )
ciphertext_b64 = base64 . b64encode ( bytes ( [ version ] ) + ciphertext + mac )
return ciphertext_b64 . decode ( ' utf8 ' )
def pw_decode_with_version_and_mac ( data : str , password : Union [ bytes , str ] ) - > bytes :
""" base64 ciphertext -> plaintext bytes """
data_bytes = bytes ( base64 . b64decode ( data ) )
version = int ( data_bytes [ 0 ] )
encrypted = data_bytes [ 1 : - 4 ]
mac = data_bytes [ - 4 : ]
if version not in KNOWN_PW_HASH_VERSIONS :
raise UnexpectedPasswordHashVersion ( version )
decrypted = _pw_decode_raw ( encrypted , password , version = version )
if sha256 ( decrypted ) [ 0 : 4 ] != mac :
raise InvalidPassword ( )
return decrypted
def pw_encode ( data : str , password : Union [ bytes , str , None ] , * , version : int ) - > str :
""" plaintext str -> base64 ciphertext """
if not password :