2
0
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:
Peter Hamilton
2017-08-14 21:10:12 -04:00
parent 5758c6dd1e
commit 89c997c337
2 changed files with 728 additions and 53 deletions

View File

@@ -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.