Add base support for keys, including AES and EC keys.
Review Request #15168 — Created July 14, 2026 and updated
This introduces a foundation for encryption/signing keys, with specific
implementations for AES symmetric keys and EC public/private keys.All keys derive from
BaseKey, which can be deserialized from bytes or
serialized to bytes, can optionally perform encryption/decryption and
key wrapping/unwrapping, and has a SHA256 fingerprint and optional key
ID.There are subclasses for bytes-based keys and for public/private keys.
AES keys are a bytes-based key. They can be generated for a supported
key size, can be derived from another key, or derived from a key
exchange. They support CFB8, GCM, and GCM-SIV modes.EC public/private key pairs support ECDH key exchange.
Keys can be invalidated, which will clear out state on the key. While
optional, keys should be used as a context manager, which will
automatically invalidate the key once the context manager exits. This is
a precaution to keep key material in memory for as little time as
possible.All bytes storage for key material is done using
bytearrays. These can
be cleared out, affecting all holders of the state. This can help
prevent keys from accidentally staying around in memory long-term (as
best as can be prevented in Python).Encryption returns a a representation of the encrypted state, providing
a full resulting ciphertext and the component parts. Key wrapping does
similar. This will be used to help with secrets storage and defining
envelopes (in a future change).All crypto operations take an algorithm, which are standardized. Keys
can indicate their support for different algorithms (which will be used
for secrets management later), validate against them when performing
the operation, and select the appropriate parameters for the operation.An upcoming change will build upon this to provide higher-level
operations for secrets storage and key type registries, managing
envelope creation and parsing.
Unit tests pass.
| Summary | ID |
|---|---|
| 24628ad7fee460bf2460c6b1d8bbbc7ea11f979c |
| Description | From | Last Updated |
|---|---|---|
|
Can we add a round-trip test for each supported algorithm that encrypts with a random nonce and decrypts back to … |
|
|
|
'cryptozoology.keys.aes.AESKey' imported but unused Column: 1 Error code: F401 |
|
|
|
'cryptozoology.keys.ec.ECPrivateKey' imported but unused Column: 1 Error code: F401 |
|
|
|
'cryptozoology.keys.ec.ECPublicKey' imported but unused Column: 1 Error code: F401 |
|
|
|
blank line at end of file Column: 1 Error code: W391 |
|
|
|
local variable 'e' is assigned to but never used Column: 9 Error code: F841 |
|
|
|
This is the only one of these that doesn't inherit from CryptozoologyError. Should we use that here so callers can … |
|
|
|
This should fit on one line. |
|
|
|
Typo: bibts -> bits |
|
|
|
If this isn't passed in, we generate a salt and then discard it (only the key gets returned), making it … |
|
|
|
Do we want to raise ... from e? |
|
|
|
Can we recommend in here that callers not use this parameter and keep it reserved for unit tests? If the … |
|
|
|
With a malformed input, we could get a ValueError. Should we catch that as well to turn it into a … |
|
|
|
Typo: alt -> alg |
|
|
|
Can we wrap as: from cryptozoology.utils.invalidation import ( InvalidatableMixin, SensitiveBytes, ) |
|
|
|
Seems like this got truncated. |
|
|
|
This is returning the internal mutable bytearray. Can we return a copy instead? |
|
|
|
The standard term is "Elliptic Curve", not "Elliptical Curve". Several throughout the change. |
|
|
|
The registry isn't part of this change so I can't tell, but this key is duplicated on both ECPublicKey and … |
|
|
|
Do we want to raise ... from e? |
|
|
|
Since this is greenfield, can we use pytest test cases and real docstrings instead of a UnitTest subclass? Same for … |
|
|
|
This docstring doesn't seem to match the function name. |
|
|
|
This seems like a copy from the test just below. This is hitting the expected error before that triggers, but … |
|
|
|
There aren't any tests for the methods in this file. Since they sit on the crypto path, it would be … |
|
|
|
'cryptozoology.utils.random.generate_aes_salt' imported but unused Column: 1 Error code: F401 |
|
|
|
redefinition of unused 'BytesLike' from line 67 Column: 5 Error code: F811 |
|
|
|
'typing.TypeAlias' imported but unused Column: 5 Error code: F401 |
|
- Change Summary:
-
- Fleshed out the
cryptozoology.basemodule. - Removed an unused variable.
- Fleshed out the
- Commits:
-
Summary ID a88b39f4db40ab39258ad6b6b3ea873fc3672ca7 6122b473b03b2d05890b3456b28b90daf2ce2892
Checks run (2 succeeded)
- Change Summary:
-
Updated to use
InvalidatableMixin. - Commits:
-
Summary ID 6122b473b03b2d05890b3456b28b90daf2ce2892 a2af5cad3f78c8ff07335d2a71ed2cda1c7da1f3 - Depends On:
-
- Diff:
Revision 3 (+8320)
Checks run (2 succeeded)
flake8 passed.JSHint passed.
-
-
Can we add a round-trip test for each supported algorithm that encrypts with a random nonce and decrypts back to plaintext?
-
This is the only one of these that doesn't inherit from
CryptozoologyError. Should we use that here so callers can just catch that one thing? -
-
If this isn't passed in, we generate a salt and then discard it (only the key gets returned), making it so the derived key can never be reproduced. This means data encrypted with this key could never be decrypted.
We should either require that the salt be passed in, or return it along with the key so the caller can save it. I'd lean towards requiring it.
Same with the
derive_from_key_exchangeAPI. -
-
Can we recommend in here that callers not use this parameter and keep it reserved for unit tests? If the nonce isn't properly random, it's Very Bad™ for GCM and CFB8.
Even better would be for this public method to not have the parameter, and delegate (mostly) to a private method that we can use for the tests.
-
With a malformed input, we could get a
ValueError. Should we catch that as well to turn it into aDecryptionError? -
-
-
-
-
The registry isn't part of this change so I can't tell, but this key is duplicated on both
ECPublicKeyandECPrivateKey. Is that ok? -
-
-
This seems like a copy from the test just below. This is hitting the expected error before that triggers, but can we change this to be AES-256-GCM just to keep it so the only "incorrect" thing about this is the key?
-
There aren't any tests for the methods in this file. Since they sit on the crypto path, it would be nice to have (especially the padding calc in
b64u_decode)
- Depends On:
- Change Summary:
-
- Moved the utils code out of this change.
- An explicit salt is now required for key derivation and exchange.
- Documentation for
encrypt()now has strong guidance on randomly-generating nonces. - Fixed
DecryptionErrorto inherit fromCryptozoologyError. - Fixed error handling (and a test) when a ciphertext is truncated.
- Some exceptions now
raise .. from e. - Fixed several typos.
- Commits:
-
Summary ID a2af5cad3f78c8ff07335d2a71ed2cda1c7da1f3 4600299fef0f9eba6b51d0f8cefc777a6c582b4d
Checks run (1 failed, 1 succeeded)
flake8
- Change Summary:
-
- Removes unused/duplicate imports.
Also missed in the previous update:
- Removed
KeyBytesLikein favor of the centralizedBytesLike. - Used
SensitiveBytesfor storage and sensitive results to support invalidation. - More functions take a
BytesLikeinstead of justbytes.
- Commits:
-
Summary ID 4600299fef0f9eba6b51d0f8cefc777a6c582b4d 24628ad7fee460bf2460c6b1d8bbbc7ea11f979c