pyca / cryptography

cryptography is a package designed to expose cryptographic primitives and recipes to Python developers.
https://cryptography.io
Other
6.67k stars 1.53k forks source link

copy.copy for private keys #11859

Open joakimnordling opened 2 weeks ago

joakimnordling commented 2 weeks ago

Private keys (at least RSAPrivateKey) can not be copied in cryptography 43.0.3:

>>> import copy
>>> from cryptography.hazmat.primitives.asymmetric import rsa
>>> private_key = rsa.generate_private_key(
...     public_exponent=65537,
...     key_size=2048,
... )
>>> copied_key = copy.copy(private_key)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/jocke/.pyenv/versions/3.11.7/lib/python3.11/copy.py", line 92, in copy
    rv = reductor(4)
         ^^^^^^^^^^^
TypeError: cannot pickle 'cryptography.hazmat.bindings._rust.openssl.rsa.RSAPrivateKey' object

There was a similar issue for public keys reported in https://github.com/pyca/cryptography/issues/9403 and it seems like the fix to that issue only made it possible to copy all the public keys, not any of the private ones.

alex commented 2 weeks ago

As I noted there, we've never intentionally supported copying keys. I'm frankly not even sure how Python's default copy implementation could work correctly.

I'm a bit ambivalent about supporting copy with private keys. The same logic about them being immutable holds. Though copy may be more difficult to support for out of process keys, and we certainly don't want to preclude those.

What is the actual use case here?

WDYT @reaperhulk?

joakimnordling commented 1 week ago

The actual use case I had was to make my own RsaPrivateKey (note the lower case sa) subclass of the SecretStr from pydantic so that I could have an environment variable with a private key in PEM format and get it parsed to this "RsaPrivateKey" subclass when loading the settings for my app. My thought was that I would in the __init__ use the load_pem_private_key to parse the key to the RSAPrivateKey (the one from cryptography with capital SA) and store it as an instance variable for later use, similar to how for example the last4 is done in the PaymentCardNumber. The problem is that pydantic decides that it wants to copy the object for some reason when initing the settings (I didn't investigate deeply why it wants to do that). And the reason I want to do it in the init and save it is to avoid doing the load_pem_private_key multiple times, as it's somewhat slow on larger keys. I hope this was not too confusing and I can try to explain in more detail if needed. But in short it's somewhat similar to wrapping it into a dataclass.

alex commented 1 week ago

I'm not familiar with pydantic, but it's surprising to me that it'd be invoking copy on arbitrary types. To me, that seems like the point worth investigating.