mirror of
https://github.com/openkmip/pykmip
synced 2025-12-15 15:53:36 +00:00
Add support for asymmetric encryption and decryption
This change updates the encrypt/decrypt support in the cryptography engine to support asymmetric key algorithms, specifically RSA. Unit tests have been added to validate the new functionality.
This commit is contained in:
@@ -276,15 +276,15 @@ class CryptographyEngine(api.CryptographicEngine):
|
||||
plain_text,
|
||||
cipher_mode=None,
|
||||
padding_method=None,
|
||||
iv_nonce=None):
|
||||
iv_nonce=None,
|
||||
hashing_algorithm=None):
|
||||
"""
|
||||
Encrypt data using symmetric encryption.
|
||||
Encrypt data using symmetric or asymmetric encryption.
|
||||
|
||||
Args:
|
||||
encryption_algorithm (CryptographicAlgorithm): An enumeration
|
||||
specifying the symmetric encryption algorithm to use for
|
||||
encryption.
|
||||
encryption_key (bytes): The bytes of the symmetric key to use for
|
||||
specifying the encryption algorithm to use for encryption.
|
||||
encryption_key (bytes): The bytes of the encryption key to use for
|
||||
encryption.
|
||||
plain_text (bytes): The bytes to be encrypted.
|
||||
cipher_mode (BlockCipherMode): An enumeration specifying the
|
||||
@@ -299,6 +299,10 @@ class CryptographyEngine(api.CryptographicEngine):
|
||||
of the encryption algorithm. Optional, defaults to None. If
|
||||
required and not provided, it will be autogenerated and
|
||||
returned with the cipher text.
|
||||
hashing_algorithm (HashingAlgorithm): An enumeration specifying
|
||||
the hashing algorithm to use with the encryption algorithm,
|
||||
if needed. Required for OAEP-based asymmetric encryption.
|
||||
Optional, defaults to None.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the encrypted data, with at least
|
||||
@@ -334,10 +338,74 @@ class CryptographyEngine(api.CryptographicEngine):
|
||||
>>> result.iv_counter_nonce
|
||||
b'8qA\x05\xc4\x86\x03\xd9=\xef\xdf\xb8ke\x9a\xa2'
|
||||
"""
|
||||
|
||||
# Set up the algorithm
|
||||
if encryption_algorithm is None:
|
||||
raise exceptions.InvalidField("Encryption algorithm is required.")
|
||||
|
||||
if encryption_algorithm == enums.CryptographicAlgorithm.RSA:
|
||||
return self._encrypt_asymmetric(
|
||||
encryption_algorithm,
|
||||
encryption_key,
|
||||
plain_text,
|
||||
padding_method,
|
||||
hashing_algorithm=hashing_algorithm
|
||||
)
|
||||
else:
|
||||
return self._encrypt_symmetric(
|
||||
encryption_algorithm,
|
||||
encryption_key,
|
||||
plain_text,
|
||||
cipher_mode=cipher_mode,
|
||||
padding_method=padding_method,
|
||||
iv_nonce=iv_nonce
|
||||
)
|
||||
|
||||
def _encrypt_symmetric(
|
||||
self,
|
||||
encryption_algorithm,
|
||||
encryption_key,
|
||||
plain_text,
|
||||
cipher_mode=None,
|
||||
padding_method=None,
|
||||
iv_nonce=None):
|
||||
"""
|
||||
Encrypt data using symmetric encryption.
|
||||
|
||||
Args:
|
||||
encryption_algorithm (CryptographicAlgorithm): An enumeration
|
||||
specifying the symmetric encryption algorithm to use for
|
||||
encryption.
|
||||
encryption_key (bytes): The bytes of the symmetric key to use for
|
||||
encryption.
|
||||
plain_text (bytes): The bytes to be encrypted.
|
||||
cipher_mode (BlockCipherMode): An enumeration specifying the
|
||||
block cipher mode to use with the encryption algorithm.
|
||||
Required in the general case. Optional if the encryption
|
||||
algorithm is RC4 (aka ARC4). If optional, defaults to None.
|
||||
padding_method (PaddingMethod): An enumeration specifying the
|
||||
padding method to use on the data before encryption. Required
|
||||
if the cipher mode is for block ciphers (e.g., CBC, ECB).
|
||||
Optional otherwise, defaults to None.
|
||||
iv_nonce (bytes): The IV/nonce value to use to initialize the mode
|
||||
of the encryption algorithm. Optional, defaults to None. If
|
||||
required and not provided, it will be autogenerated and
|
||||
returned with the cipher text.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the encrypted data, with at least
|
||||
the following key/value fields:
|
||||
* cipher_text - the bytes of the encrypted data
|
||||
* iv_nonce - the bytes of the IV/counter/nonce used if it
|
||||
was needed by the encryption scheme and if it was
|
||||
automatically generated for the encryption
|
||||
|
||||
Raises:
|
||||
InvalidField: Raised when the algorithm is unsupported or the
|
||||
encryption key is incompatible with the algorithm.
|
||||
CryptographicFailure: Raised when the key generation process
|
||||
fails.
|
||||
"""
|
||||
|
||||
# Set up the algorithm
|
||||
algorithm = self._symmetric_key_algorithms.get(
|
||||
encryption_algorithm,
|
||||
None
|
||||
@@ -402,6 +470,89 @@ class CryptographyEngine(api.CryptographicEngine):
|
||||
else:
|
||||
return {'cipher_text': cipher_text}
|
||||
|
||||
def _encrypt_asymmetric(self,
|
||||
encryption_algorithm,
|
||||
encryption_key,
|
||||
plain_text,
|
||||
padding_method,
|
||||
hashing_algorithm=None):
|
||||
"""
|
||||
Encrypt data using asymmetric encryption.
|
||||
|
||||
Args:
|
||||
encryption_algorithm (CryptographicAlgorithm): An enumeration
|
||||
specifying the asymmetric encryption algorithm to use for
|
||||
encryption. Required.
|
||||
encryption_key (bytes): The bytes of the public key to use for
|
||||
encryption. Required.
|
||||
plain_text (bytes): The bytes to be encrypted. Required.
|
||||
padding_method (PaddingMethod): An enumeration specifying the
|
||||
padding method to use with the asymmetric encryption
|
||||
algorithm. Required.
|
||||
hashing_algorithm (HashingAlgorithm): An enumeration specifying
|
||||
the hashing algorithm to use with the encryption padding
|
||||
method. Required, if the padding method is OAEP. Optional
|
||||
otherwise, defaults to None.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the encrypted data, with at least
|
||||
the following key/value field:
|
||||
* cipher_text - the bytes of the encrypted data
|
||||
|
||||
Raises:
|
||||
InvalidField: Raised when the algorithm is unsupported or the
|
||||
length is incompatible with the algorithm.
|
||||
CryptographicFailure: Raised when the key generation process
|
||||
fails.
|
||||
"""
|
||||
if encryption_algorithm == enums.CryptographicAlgorithm.RSA:
|
||||
if padding_method == enums.PaddingMethod.OAEP:
|
||||
hash_algorithm = self._encryption_hash_algorithms.get(
|
||||
hashing_algorithm
|
||||
)
|
||||
if hash_algorithm is None:
|
||||
raise exceptions.InvalidField(
|
||||
"The hashing algorithm '{0}' is not supported for "
|
||||
"asymmetric encryption.".format(hashing_algorithm)
|
||||
)
|
||||
|
||||
padding_method = asymmetric_padding.OAEP(
|
||||
mgf=asymmetric_padding.MGF1(
|
||||
algorithm=hash_algorithm()
|
||||
),
|
||||
algorithm=hash_algorithm(),
|
||||
label=None
|
||||
)
|
||||
elif padding_method == enums.PaddingMethod.PKCS1v15:
|
||||
padding_method = asymmetric_padding.PKCS1v15()
|
||||
else:
|
||||
raise exceptions.InvalidField(
|
||||
"The padding method '{0}' is not supported for asymmetric "
|
||||
"encryption.".format(padding_method)
|
||||
)
|
||||
|
||||
backend = default_backend()
|
||||
|
||||
try:
|
||||
public_key = backend.load_der_public_key(encryption_key)
|
||||
except Exception:
|
||||
try:
|
||||
public_key = backend.load_pem_public_key(encryption_key)
|
||||
except Exception:
|
||||
raise exceptions.CryptographicFailure(
|
||||
"The public key bytes could not be loaded."
|
||||
)
|
||||
cipher_text = public_key.encrypt(
|
||||
plain_text,
|
||||
padding_method
|
||||
)
|
||||
return {'cipher_text': cipher_text}
|
||||
else:
|
||||
raise exceptions.InvalidField(
|
||||
"The cryptographic algorithm '{0}' is not supported for "
|
||||
"asymmetric encryption.".format(encryption_algorithm)
|
||||
)
|
||||
|
||||
def _handle_symmetric_padding(self,
|
||||
algorithm,
|
||||
plain_text,
|
||||
@@ -443,7 +594,8 @@ class CryptographyEngine(api.CryptographicEngine):
|
||||
cipher_text,
|
||||
cipher_mode=None,
|
||||
padding_method=None,
|
||||
iv_nonce=None):
|
||||
iv_nonce=None,
|
||||
hashing_algorithm=None):
|
||||
"""
|
||||
Decrypt data using symmetric decryption.
|
||||
|
||||
@@ -464,6 +616,10 @@ class CryptographyEngine(api.CryptographicEngine):
|
||||
Optional otherwise, defaults to None.
|
||||
iv_nonce (bytes): The IV/nonce value to use to initialize the mode
|
||||
of the decryption algorithm. Optional, defaults to None.
|
||||
hashing_algorithm (HashingAlgorithm): An enumeration specifying
|
||||
the hashing algorithm to use with the decryption algorithm,
|
||||
if needed. Required for OAEP-based asymmetric decryption.
|
||||
Optional, defaults to None.
|
||||
|
||||
Returns:
|
||||
bytes: the bytes of the decrypted data
|
||||
@@ -496,10 +652,66 @@ class CryptographyEngine(api.CryptographicEngine):
|
||||
>>> result
|
||||
b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'
|
||||
"""
|
||||
|
||||
# Set up the algorithm
|
||||
if decryption_algorithm is None:
|
||||
raise exceptions.InvalidField("Decryption algorithm is required.")
|
||||
|
||||
if decryption_algorithm == enums.CryptographicAlgorithm.RSA:
|
||||
return self._decrypt_asymmetric(
|
||||
decryption_algorithm,
|
||||
decryption_key,
|
||||
cipher_text,
|
||||
padding_method,
|
||||
hashing_algorithm=hashing_algorithm
|
||||
)
|
||||
else:
|
||||
return self._decrypt_symmetric(
|
||||
decryption_algorithm,
|
||||
decryption_key,
|
||||
cipher_text,
|
||||
cipher_mode=cipher_mode,
|
||||
padding_method=padding_method,
|
||||
iv_nonce=iv_nonce
|
||||
)
|
||||
|
||||
def _decrypt_symmetric(
|
||||
self,
|
||||
decryption_algorithm,
|
||||
decryption_key,
|
||||
cipher_text,
|
||||
cipher_mode=None,
|
||||
padding_method=None,
|
||||
iv_nonce=None):
|
||||
"""
|
||||
Decrypt data using symmetric decryption.
|
||||
|
||||
Args:
|
||||
decryption_algorithm (CryptographicAlgorithm): An enumeration
|
||||
specifying the symmetric decryption algorithm to use for
|
||||
decryption.
|
||||
decryption_key (bytes): The bytes of the symmetric key to use for
|
||||
decryption.
|
||||
cipher_text (bytes): The bytes to be decrypted.
|
||||
cipher_mode (BlockCipherMode): An enumeration specifying the
|
||||
block cipher mode to use with the decryption algorithm.
|
||||
Required in the general case. Optional if the decryption
|
||||
algorithm is RC4 (aka ARC4). If optional, defaults to None.
|
||||
padding_method (PaddingMethod): An enumeration specifying the
|
||||
padding method to use on the data after decryption. Required
|
||||
if the cipher mode is for block ciphers (e.g., CBC, ECB).
|
||||
Optional otherwise, defaults to None.
|
||||
iv_nonce (bytes): The IV/nonce value to use to initialize the mode
|
||||
of the decryption algorithm. Optional, defaults to None.
|
||||
|
||||
Returns:
|
||||
bytes: the bytes of the decrypted data
|
||||
|
||||
Raises:
|
||||
InvalidField: Raised when the algorithm is unsupported or the
|
||||
length is incompatible with the algorithm.
|
||||
CryptographicFailure: Raised when the key generation process
|
||||
fails.
|
||||
"""
|
||||
# Set up the algorithm
|
||||
algorithm = self._symmetric_key_algorithms.get(
|
||||
decryption_algorithm,
|
||||
None
|
||||
@@ -560,6 +772,96 @@ class CryptographyEngine(api.CryptographicEngine):
|
||||
|
||||
return plain_text
|
||||
|
||||
def _decrypt_asymmetric(
|
||||
self,
|
||||
decryption_algorithm,
|
||||
decryption_key,
|
||||
cipher_text,
|
||||
padding_method,
|
||||
hashing_algorithm=None):
|
||||
"""
|
||||
Encrypt data using asymmetric decryption.
|
||||
|
||||
Args:
|
||||
decryption_algorithm (CryptographicAlgorithm): An enumeration
|
||||
specifying the asymmetric decryption algorithm to use for
|
||||
decryption. Required.
|
||||
decryption_key (bytes): The bytes of the private key to use for
|
||||
decryption. Required.
|
||||
cipher_text (bytes): The bytes to be decrypted. Required.
|
||||
padding_method (PaddingMethod): An enumeration specifying the
|
||||
padding method to use with the asymmetric decryption
|
||||
algorithm. Required.
|
||||
hashing_algorithm (HashingAlgorithm): An enumeration specifying
|
||||
the hashing algorithm to use with the decryption padding
|
||||
method. Required, if the padding method is OAEP. Optional
|
||||
otherwise, defaults to None.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the decrypted data, with at least
|
||||
the following key/value field:
|
||||
* plain_text - the bytes of the decrypted data
|
||||
|
||||
Raises:
|
||||
InvalidField: Raised when the algorithm is unsupported or the
|
||||
length is incompatible with the algorithm.
|
||||
CryptographicFailure: Raised when the key generation process
|
||||
fails.
|
||||
"""
|
||||
if decryption_algorithm == enums.CryptographicAlgorithm.RSA:
|
||||
if padding_method == enums.PaddingMethod.OAEP:
|
||||
hash_algorithm = self._encryption_hash_algorithms.get(
|
||||
hashing_algorithm
|
||||
)
|
||||
if hash_algorithm is None:
|
||||
raise exceptions.InvalidField(
|
||||
"The hashing algorithm '{0}' is not supported for "
|
||||
"asymmetric decryption.".format(hashing_algorithm)
|
||||
)
|
||||
|
||||
padding_method = asymmetric_padding.OAEP(
|
||||
mgf=asymmetric_padding.MGF1(
|
||||
algorithm=hash_algorithm()
|
||||
),
|
||||
algorithm=hash_algorithm(),
|
||||
label=None
|
||||
)
|
||||
elif padding_method == enums.PaddingMethod.PKCS1v15:
|
||||
padding_method = asymmetric_padding.PKCS1v15()
|
||||
else:
|
||||
raise exceptions.InvalidField(
|
||||
"The padding method '{0}' is not supported for asymmetric "
|
||||
"decryption.".format(padding_method)
|
||||
)
|
||||
|
||||
backend = default_backend()
|
||||
|
||||
try:
|
||||
private_key = backend.load_der_private_key(
|
||||
decryption_key,
|
||||
None
|
||||
)
|
||||
except Exception:
|
||||
try:
|
||||
private_key = backend.load_pem_private_key(
|
||||
decryption_key,
|
||||
None
|
||||
)
|
||||
except Exception:
|
||||
raise exceptions.CryptographicFailure(
|
||||
"The private key bytes could not be loaded."
|
||||
)
|
||||
plain_text = private_key.decrypt(
|
||||
cipher_text,
|
||||
padding_method
|
||||
)
|
||||
return plain_text
|
||||
else:
|
||||
raise exceptions.InvalidField(
|
||||
"The cryptographic algorithm '{0}' is not supported for "
|
||||
"asymmetric decryption.".format(decryption_algorithm)
|
||||
)
|
||||
|
||||
def _create_rsa_key_pair(self, length, public_exponent=65537):
|
||||
"""
|
||||
Create an RSA key pair.
|
||||
|
||||
Reference in New Issue
Block a user