On February 20, 2018 at 4:21:32 AM, Andrew W. Donoho ( andrew.don...@gmail.com) wrote:
Gentlemen, Thank you for your fine answers. They comport with my expectations. Without boring you with my details, I am doing something similar to an ephemeral ECDH exchange. The secrets have a very short lifetime. Hence, I’m not worried about swapping to disk. (Though most of the cloud vendors claim the swap vm is encrypted.) For example: secret_bytes = os.urandom(384//8) secret_int = int.from_bytes(secret_bytes, byteorder='big') private_key = ec.derive_private_key(secret_int, ec.SECP384R1(), backend) I would like secret_bytes, secret_int, and private_key to be zeroed-out before I exit the function. I want as precise an object lifetime as possible. While I fully understand the values of a library maintainer wishing to have a robust solution for all supported versions and runtimes, I have explicit control of this function and its execution environment. I’m more concerned about revealing the secret in a hostile cloud. In particular, I don’t have the string aliasing problem. Yes, this is a fragile and extremely hazardous solution. OTOH, the industry just freaked out about Meltdown. It behooves us all to manage key lifetimes carefully. As I dig into examining this problem further, I assume the ec.derive_private_key() function takes a conservative approach and doesn’t ever copy the secret_int? If I’m living well, banging the bytes in secret_bytes might hit the only copy of the secret. Depends upon whether int.from_bytes() copies the storage of an immutable source. Access to Python's memory (via side channel or dumping as root) is not part of pyca/cryptography's threat model at this time so we don't attempt to protect against it. Making it part of our threat model would be difficult due in part to the reasons you stated above as well as the difficulty in writing tests to prevent regression, but let's talk about what CPython does in this case. int.from_bytes will unfortunately make a copy (to a Python integer). That int will then be copied into a BN via _int_to_bn ( https://github.com/pyca/cryptography/blob/master/src/cryptography/hazmat/backends/openssl/backend.py#L317-L346) when you call derive_private_key. It will actually be converted twice (a thing we should fix) ( https://github.com/pyca/cryptography/blob/master/src/cryptography/hazmat/backends/openssl/backend.py#L1383-L1419). Although the resulting BNs will themselves be zeroed as freed, this means a secret scalar bytestring created in Python will be resident in memory no less than 5 times (3 byte strings, 2 numbers). Obviously the next logical question is why you'd provide a Python integer when we're just going to convert it back to big endian bytes anyway. Disregarding the memory clearing issue it's also inefficient. When originally designing some of the APIs we made a mistake and chose integers instead of big endian bytes (see: numbers classes). We have not yet added alternate APIs to potentially enable us to deprecate numbers because the improvement in efficiency probably isn't worth the pain of trying to convert the huge number of users of those classes. ec.derive_private_key_from_bytes(secret_bytes, ec.SECP384R1(), backend) could potentially be a way to do this specific operation while reducing the number of copies (to zero in Python and 2-3 in OpenSSL, although the latter are zeroed), but without tests that can detect non-required copies of secret material it would be extremely hard to prevent regression in the long term as the code is updated. Given your chosen constraints have you considered deriving a key in subprocess, serializing it, and reading it from stdout in the parent process? By doing this you'd have a precisely defined intermediate object lifetime and the only secret in the parent process's memory would be a single DER or PEM bytestring containing the EC key. -Paul Kehrer (reaperhulk)
_______________________________________________ Cryptography-dev mailing list Cryptography-dev@python.org https://mail.python.org/mailman/listinfo/cryptography-dev