From e2db8571c2bcf74ca7cbc1e33d2b4f9e161f4b82 Mon Sep 17 00:00:00 2001 From: Jakob Schlyter Date: Tue, 12 Apr 2022 20:35:38 +0200 Subject: [PATCH 1/3] - allow raw 32 byte keys in FernetEncrypter (bypass PBKDF2HMAC) - allow no key (generate random key) --- src/cryptojwt/jwe/fernet.py | 25 +++++++++++++++++-------- tests/test_07_jwe.py | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/cryptojwt/jwe/fernet.py b/src/cryptojwt/jwe/fernet.py index 3cca512..556ca42 100644 --- a/src/cryptojwt/jwe/fernet.py +++ b/src/cryptojwt/jwe/fernet.py @@ -16,7 +16,8 @@ class FernetEncrypter(Encrypter): def __init__( self, - password: str, + password: Optional[str] = None, + key: Optional[bytes] = None, salt: Optional[bytes] = "", hash_alg: Optional[str] = "SHA256", digest_size: Optional[int] = 0, @@ -28,14 +29,22 @@ def __init__( else: salt = as_bytes(salt) - _alg = getattr(hashes, hash_alg) - # A bit special for SHAKE* and BLAKE* hashes - if hash_alg.startswith("SHAKE") or hash_alg.startswith("BLAKE"): - _algorithm = _alg(digest_size) + if password is not None: + _alg = getattr(hashes, hash_alg) + # A bit special for SHAKE* and BLAKE* hashes + if hash_alg.startswith("SHAKE") or hash_alg.startswith("BLAKE"): + _algorithm = _alg(digest_size) + else: + _algorithm = _alg() + kdf = PBKDF2HMAC(algorithm=_algorithm, length=32, salt=salt, iterations=iterations) + self.key = base64.urlsafe_b64encode(kdf.derive(as_bytes(password))) + elif key is not None: + if len(key) != 32: + raise ValueError("Raw key must be 32 bytes") + self.key = base64.urlsafe_b64encode(as_bytes(key)) else: - _algorithm = _alg() - kdf = PBKDF2HMAC(algorithm=_algorithm, length=32, salt=salt, iterations=iterations) - self.key = base64.urlsafe_b64encode(kdf.derive(as_bytes(password))) + self.key = Fernet.generate_key() + self.core = Fernet(self.key) def encrypt(self, msg: Union[str, bytes], **kwargs) -> bytes: diff --git a/tests/test_07_jwe.py b/tests/test_07_jwe.py index ed6197b..6cb94a7 100644 --- a/tests/test_07_jwe.py +++ b/tests/test_07_jwe.py @@ -648,10 +648,39 @@ def test_invalid(): decrypter.decrypt("a.b.c.d.e", keys=[encryption_key]) -def test_fernet(): +def test_fernet_password(): + encrypter = FernetEncrypter(password="DukeofHazardpass") + _token = encrypter.encrypt(plain) + + decrypter = encrypter + resp = decrypter.decrypt(_token) + assert resp == plain + + +def test_fernet_symkey(): encryption_key = SYMKey(use="enc", key="DukeofHazardpass", kid="some-key-id") - encrypter = FernetEncrypter(encryption_key.key) + encrypter = FernetEncrypter(password=encryption_key.key) + _token = encrypter.encrypt(plain) + + decrypter = encrypter + resp = decrypter.decrypt(_token) + assert resp == plain + + +def test_fernet_bytes(): + key = os.urandom(32) + + encrypter = FernetEncrypter(key=key) + _token = encrypter.encrypt(plain) + + decrypter = encrypter + resp = decrypter.decrypt(_token) + assert resp == plain + + +def test_fernet_default_key(): + encrypter = FernetEncrypter() _token = encrypter.encrypt(plain) decrypter = encrypter @@ -662,7 +691,7 @@ def test_fernet(): def test_fernet_sha512(): encryption_key = SYMKey(use="enc", key="DukeofHazardpass", kid="some-key-id") - encrypter = FernetEncrypter(encryption_key.key, hash_alg="SHA512") + encrypter = FernetEncrypter(password=encryption_key.key, hash_alg="SHA512") _token = encrypter.encrypt(plain) decrypter = encrypter @@ -674,7 +703,7 @@ def test_fernet_blake2s(): encryption_key = SYMKey(use="enc", key="DukeofHazardpass", kid="some-key-id") encrypter = FernetEncrypter( - encryption_key.key, hash_alg="BLAKE2s", digest_size=32, iterations=1000 + password=encryption_key.key, hash_alg="BLAKE2s", digest_size=32, iterations=1000 ) _token = encrypter.encrypt(plain) From 03af3f58a4f6cefc72924cdc12a456bb52fcaf06 Mon Sep 17 00:00:00 2001 From: Jakob Schlyter Date: Tue, 12 Apr 2022 20:36:57 +0200 Subject: [PATCH 2/3] bump --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b51657a..48ec79d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ exclude_lines = [ [tool.poetry] name = "cryptojwt" -version = "1.8.0" +version = "1.8.1" description = "Python implementation of JWT, JWE, JWS and JWK" authors = ["Roland Hedberg "] license = "Apache-2.0" From d4908be50cce0a1010822bd53fc157039818097e Mon Sep 17 00:00:00 2001 From: Jakob Schlyter Date: Tue, 12 Apr 2022 21:47:58 +0200 Subject: [PATCH 3/3] better tests set salt on password only --- src/cryptojwt/jwe/fernet.py | 11 +++++------ tests/test_07_jwe.py | 7 +++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/cryptojwt/jwe/fernet.py b/src/cryptojwt/jwe/fernet.py index 556ca42..90b02d3 100644 --- a/src/cryptojwt/jwe/fernet.py +++ b/src/cryptojwt/jwe/fernet.py @@ -17,17 +17,13 @@ class FernetEncrypter(Encrypter): def __init__( self, password: Optional[str] = None, - key: Optional[bytes] = None, salt: Optional[bytes] = "", + key: Optional[bytes] = None, hash_alg: Optional[str] = "SHA256", digest_size: Optional[int] = 0, iterations: Optional[int] = DEFAULT_ITERATIONS, ): Encrypter.__init__(self) - if not salt: - salt = os.urandom(16) - else: - salt = as_bytes(salt) if password is not None: _alg = getattr(hashes, hash_alg) @@ -36,12 +32,15 @@ def __init__( _algorithm = _alg(digest_size) else: _algorithm = _alg() + salt = as_bytes(salt) if salt else os.urandom(16) kdf = PBKDF2HMAC(algorithm=_algorithm, length=32, salt=salt, iterations=iterations) self.key = base64.urlsafe_b64encode(kdf.derive(as_bytes(password))) elif key is not None: + if not isinstance(key, bytes): + raise TypeError("Raw key must be bytes") if len(key) != 32: raise ValueError("Raw key must be 32 bytes") - self.key = base64.urlsafe_b64encode(as_bytes(key)) + self.key = base64.urlsafe_b64encode(key) else: self.key = Fernet.generate_key() diff --git a/tests/test_07_jwe.py b/tests/test_07_jwe.py index 6cb94a7..82a3160 100644 --- a/tests/test_07_jwe.py +++ b/tests/test_07_jwe.py @@ -668,6 +668,13 @@ def test_fernet_symkey(): assert resp == plain +def test_fernet_bad(): + with pytest.raises(TypeError): + encrypter = FernetEncrypter(key="xyzzy") + with pytest.raises(ValueError): + encrypter = FernetEncrypter(key=os.urandom(16)) + + def test_fernet_bytes(): key = os.urandom(32)