Why do these passwords with the same salt and encryption not have the same output?

Question:

The output of the 2 variables "x" and "l" should be the same as they use the exact same salt and encrypting process.

I’d like some info on why it is not the same and how I can fix it for a simple login algorithm.

Code:

import os
from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64


def encrypt_password(password, salt=None):
    # Create a password hash
    if not salt:
        salt = os.urandom(64)
        print("...")
        token = base64.b64encode(salt).decode('utf-8')



    password = password.encode()
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256,
        iterations=100000,
        length=32,
        salt=salt,
        backend=default_backend()
    )
    key = base64.urlsafe_b64encode(kdf.derive(password))
    # Encrypt the password
    f = Fernet(key)
    encrypted_password = f.encrypt(password)
    return encrypted_password, salt

x, salt2 = encrypt_password("Hello")
print(x)

l, salt1 = encrypt_password("Hello", salt2)
print(l)

print(salt1 == salt2)

I’ve checked the salts like 20 times and I’m almost certain they don’t change during any of the process. All I can think of is that there is something I don’t know about any of the libraries used in the process that are inconsistent.

Asked By: Jordy Dongen

||

Answers:

Fernet tokens are not stable for two reason:

  1. Fernet supports token expiration, which it handles by embedding the current time (as a 64 bits unsigned integer) in the token, as documented:

    The encrypted message contains the current time when it was generated in plaintext, the time a message was created will therefore be visible to a possible attacker.

  2. Fernet uses CBC internally, CBC encryption needs a key, a plaintext, and an initialisation vector, for security Fernet always generates the IV on its own internally then embeds that in the token.

So even if you create Fernet tokens within the same second (or use encrypt_at_time to fix it), the content is randomised via the IV which you are not given any control over. By design, you can not reasonably get the same token twice out of Fernet, even if you provide the same key, data, and fixed creation time.

Answered By: Masklinn

The encrypted Passwords are not deterministically the same with this encryption method.

This can be proven if we put the input salt to a constant.

Everytime you run the code the generated encrypted_password will differ.
In the example bellow our input salt1 = b"abc" stays the same. The generated passwords are different. This encryption method is not suitable for deterministic key generation.

import base64
import os

from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC


def encrypt_password(password, salt=None):
    # Create a password hash
    if not salt:
        salt = os.urandom(64)
        print("...")
        token = base64.b64encode(salt).decode('utf-8')

    password = password.encode()
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256,
        iterations=100000,
        length=32,
        salt=salt,
        backend=default_backend()
    )
    key = base64.urlsafe_b64encode(kdf.derive(password))
    # Encrypt the password
    f = Fernet(key)
    encrypted_password = f.encrypt(password)
    return encrypted_password, salt


salt1 = b"abc"
x, salt2 = encrypt_password("Hello", salt1)
print(x)

y, salt3 = encrypt_password("Hello", salt1)
print(y)
print(x == y)
Answered By: tetris programming
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.