Python cryptography unexpected error deriving a key

When I updated a test system to Fedora 39 Beta, all of my encryption stuff started failing with:
Expected instance of hashes.HashAlgorithm
deep in rust code.
This worked with Fedora 38 and cryptography 37.0.2 but fails with Fedora 39 and Cryptography 41.0.3 (and 41.o.4).

#! /usr/bin/env python                                                                                                                                                                        
                                                                                                                                                                                              
import os                                                                                                                                                                                     
import sys                                                                                                                                                                                    
import uuid                                                                                                                                                                                   
import base64                                                                                                                                                                                 
import traceback                                                                                                                                                                              
                                                                                                                                                                                              
from base64 import b64encode                                                                                                                                                                  
                                                                                                                                                                                              
import cryptography                                                                                                                                                                           
from cryptography.fernet                       import InvalidToken, Fernet                                                                                                                    
from cryptography.hazmat.primitives            import hashes                                                                                                                                  
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC                                                                                                                              
                                                                                                                                                                                              
class FCrypt:                                                                                                                                                                                 
                                                                                                                                                                                              
    def __init__(self,                                                                                                                                                                        
                 pswd = "123456789012345678890"):                                                                                                                                             
        self.charset  = 'ASCII'                                                                                                                                                               
        slt           = os.urandom(16)                                                                                                                                                        
        kdf           = PBKDF2HMAC(algorithm=hashes.SHA256, length=32,                                                                                                                        
                                   salt=slt, iterations=480000)                                                                                                                               
        pwd           = b64encode(bytes(pswd, self.charset))                                                                                                                                  
        pwd           = base64.urlsafe_b64encode(kdf.derive(pwd))                                                                                                                             
        self.cryptObj = Fernet(pwd)                                                                                                                                                           
        pass                                                                                                                                                                                  
                                                                                                                                                                                              
    def encrypt(self, value):                                                                                                                                                                 
        try:                                                                                                                                                                                  
            t1  = bytes(value,self.charset)                                                                                                                                                   
            t2  = self.cryptObj.encrypt(t1)                                                                                                                                                   
            rtn = str(t2, self.charset)                                                                                                                                                       
            return rtn                                                                                                                                                                        
        except TypeError:                                                                                                                                                                     
            print("TypeError: bytes expected not str", file=sys.stderr)                                                                                                                       
            raise TypeError()                                                                                                                                                                 
        except Exception as error:                                                                                                                                                            
            print(f"Encode Exception: {error}", file=sys.stderr)                                                                                                                              
            traceback.print_stack()                                                                                                                                                           
            raise Exception(error)                                                                                                                                                            

    def decrypt(self, value):                                                                                                                                                                 
        try:                                                                                                                                                                                  
            t1  = bytes(value, self.charset)                                                                                                                                                  
            t2  = self.cryptObj.decrypt(t1, ttl=None)                                                                                                                                         
            rtn = str(t2, self.charset)                                                                                                                                                       
            return rtn                                                                                                                                                                        
        except InvalidToken:                                                                                                                                                                  
            print(f"Invalid cryptography token: {value}", file=sys.stderr)                                                                                                                    
            raise(InvalidToken(value))                                                                                                                                                        
            pass                                                                                                                                                                              
        except TypeError:                                                                                                                                                                     
            print("TypeError: bytes expected not str", file=sys.stderr)                                                                                                                       
            raise TypeError()                                                                                                                                                                 
        except Exception as err:                                                                                                                                                              
            print(f"Decode Exception: {err}", file=sys.stderr)                                                                                                                                
            traceback.print_stack()                                                                                                                                                           
            raise Exception(err)                                                                                                                                                              
        pass                                                                                                                                                                                  
    pass                                                                                                                                                                                      
                                                                                                                                                                                              
                                                                                                                                                                                              if __name__ == "__main__":                                                                                                                                                                    
    print("Test FCrypt")                                                                                                                                                                      
    print(f"Using cryptography version: {cryptography.__about__.__version__}")                                                                                                                
                                                                                                                                                                                              
    fname     = f"{os.environ['HOME']}/testfoo.key"                                                                                                                                           
    mypwd     = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"                                                                                                                        
    test_text = "Test String"                                                                                                                                                                 
                                                                                                                                                                                              
    test_crpt = FCrypt(pswd=mypwd)                                                                                                                                                            
                                                                                                                                                                                              
    test_enc  = test_crpt.encrypt(test_text)                                                                                                                                                  
                                                                                                                                                                                              
    f = open(fname, "w")                                                                                                                                                                      
    print(f'PASSWD = "{test_enc}"', file=f)                                                                                                                                                   
    f.close()                                                                                                                                                                                 
                                                                                                                                                                                              
    f = open(fname, "r")                                                                                                                                                                      
    read_enc  = f.read().split(' = ')                                                                                                                                                         
    read_enc  = read_enc[1].strip()                                                                                                                                                           
    f.close()                                                                                                                                                                                 
                                                                                                                                                                                              
    test_dec  = test_crpt.decrypt(read_enc)                                                                                                                                                   
                                                                                                                                                                                              
    print(f'Original:{test_text}\nEncrypted:{test_enc}\nDecrypted:{test_dec}')                                                                                                                
                                                                                                                                                                                              
    os.remove(fname)                                                                                                                                                                          
                                                                                                                                                                                              
    pass                                                                                                                                                                                      

This produces:

Test FCrypt
Using cryptography version: 41.0.4

Traceback (most recent call last):
  File "/home/norm/bin/TestFCrypt.py", line 79, in <module>
    test_crpt = FCrypt(pswd=mypwd)
                ^^^^^^^^^^^^^^^^^^
  File "/home/norm/bin/TestFCrypt.py", line 31, in __init__
    pwd           = base64.urlsafe_b64encode(kdf.derive(pwd))
                                             ^^^^^^^^^^^^^^^
  File "/home/norm/.local/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/pbkdf2.py", line 53, in derive
    return rust_openssl.kdf.derive_pbkdf2_hmac(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Expected instance of hashes.HashAlgorithm.

When I expect:

Test FCrypt
Using cryptography version: 37.0.2
Original:Test String
Encrypted:gAAAAABlHrkL0GRgu3hupgrRvrlIBxXTztTAU_rdeauRN9-l4E6nvbA0k-oUM74MCodR4kcc2-sumZG0rzNoECfSt2Y2toMJ_w==
Decrypted:Test String

Am I doing something wrong that is finally being caught with the newer software?

>Solution :

You need to provide an object of type hashes.HashAlgorithm, which you can do by constructing it. So do

kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=slt, iterations=480000)

instead of

kdf = PBKDF2HMAC(algorithm=hashes.SHA256, length=32,  salt=slt, iterations=480000)

Leave a Reply