diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_client.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_client.py index 8daff45a9a23..6653fbde00fe 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_client.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_client.py @@ -223,14 +223,14 @@ def encrypt(self, algorithm, plaintext, **kwargs): :type algorithm: :class:`~azure.keyvault.keys.crypto.EncryptionAlgorithm` :param bytes plaintext: Bytes to encrypt :keyword bytes iv: Initialization vector. Required for only AES-CBC(PAD) encryption. If you pass your own IV, - make sure you use a cryptographically random, non-repeating IV. If omitted for remote cryptography, Key - Vault will generate an IV. + make sure you use a cryptographically random, non-repeating IV. If omitted, an attempt will be made to + generate an IV via `os.urandom `_ for local + cryptography; for remote cryptography, Key Vault will generate an IV. :keyword bytes additional_authenticated_data: Optional data that is authenticated but not encrypted. For use with AES-GCM encryption. :rtype: :class:`~azure.keyvault.keys.crypto.EncryptResult` - :raises: - ValueError if parameters that are incompatible with the specified algorithm are provided, - RuntimeError if an IV cannot be generated + :raises ValueError: if parameters that are incompatible with the specified algorithm are provided, or if + generating an IV fails on the current platform. .. literalinclude:: ../tests/test_examples_crypto.py :start-after: [START encrypt] diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_providers/local_provider.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_providers/local_provider.py index df8c03638bc1..f9073924e026 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_providers/local_provider.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_providers/local_provider.py @@ -3,6 +3,7 @@ # Licensed under the MIT License. # ------------------------------------ import abc +import os from typing import TYPE_CHECKING from azure.core.exceptions import AzureError @@ -63,6 +64,17 @@ def _raise_if_unsupported(self, operation, algorithm): def encrypt(self, algorithm, plaintext, iv=None): # type: (EncryptionAlgorithm, bytes, Optional[bytes]) -> EncryptResult self._raise_if_unsupported(KeyOperation.encrypt, algorithm) + + # If an IV isn't provided with AES-CBCPAD encryption, try to create one + if iv is None and algorithm.value.endswith("CBCPAD"): + try: + iv = os.urandom(16) + except NotImplementedError as ex: + raise ValueError( + "An IV could not be generated on this OS. Please provide your own cryptographically random, " + "non-repeating IV for local cryptography." + ) from ex + ciphertext = self._internal_key.encrypt(plaintext, algorithm=algorithm.value, iv=iv) return EncryptResult( key_id=self._key.kid, algorithm=algorithm, ciphertext=ciphertext, iv=iv # type: ignore[attr-defined] diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/aio/_client.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/aio/_client.py index 513f1b746ca0..b8455c6c7c8f 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/aio/_client.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/aio/_client.py @@ -166,14 +166,14 @@ async def encrypt(self, algorithm: "EncryptionAlgorithm", plaintext: bytes, **kw :type algorithm: :class:`~azure.keyvault.keys.crypto.EncryptionAlgorithm` :param bytes plaintext: Bytes to encrypt :keyword bytes iv: Initialization vector. Required for only AES-CBC(PAD) encryption. If you pass your own IV, - make sure you use a cryptographically random, non-repeating IV. If omitted for remote cryptography, Key - Vault will generate an IV. + make sure you use a cryptographically random, non-repeating IV. If omitted, an attempt will be made to + generate an IV via `os.urandom `_ for local + cryptography; for remote cryptography, Key Vault will generate an IV. :keyword bytes additional_authenticated_data: Optional data that is authenticated but not encrypted. For use with AES-GCM encryption. :rtype: :class:`~azure.keyvault.keys.crypto.EncryptResult` - :raises: - ValueError if parameters that are incompatible with the specified algorithm are provided, - RuntimeError if an IV cannot be generated + :raises ValueError: if parameters that are incompatible with the specified algorithm are provided, or if + generating an IV fails on the current platform. .. literalinclude:: ../tests/test_examples_crypto_async.py :start-after: [START encrypt] diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_local_crypto.py b/sdk/keyvault/azure-keyvault-keys/tests/test_local_crypto.py index edcb4e739779..52ec32992543 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_local_crypto.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_local_crypto.py @@ -64,6 +64,15 @@ def test_symmetric_encrypt_decrypt(algorithm, key_size): encrypt_result = provider.encrypt(algorithm, plaintext, iv=iv) assert encrypt_result.key_id == key.id + assert encrypt_result.iv == iv + + decrypt_result = provider.decrypt(encrypt_result.algorithm, encrypt_result.ciphertext, iv=encrypt_result.iv) + assert decrypt_result.plaintext == plaintext + + # Try with no IV to verify that we generate one for local crypto + encrypt_result = provider.encrypt(algorithm, plaintext, iv=None) + assert encrypt_result.key_id == key.id + assert len(encrypt_result.iv) == 16 decrypt_result = provider.decrypt(encrypt_result.algorithm, encrypt_result.ciphertext, iv=encrypt_result.iv) assert decrypt_result.plaintext == plaintext