def decrypt(algorithm, key, ciphertext, iv=None): """Decrypt the ciphertext and return the plaintext value.""" from pskc.exceptions import DecryptionError if key is None: raise DecryptionError('No key available') if algorithm is None: raise DecryptionError('No algorithm specified') if len(key) not in algorithm_key_lengths(algorithm): raise DecryptionError('Invalid key length') if algorithm.endswith('#aes128-cbc') or \ algorithm.endswith('#aes192-cbc') or \ algorithm.endswith('#aes256-cbc'): from Crypto.Cipher import AES from pskc.crypto import unpad if not iv: iv = ciphertext[:AES.block_size] ciphertext = ciphertext[AES.block_size:] cipher = AES.new(key, AES.MODE_CBC, iv) return unpad(cipher.decrypt(ciphertext), AES.block_size) elif algorithm.endswith('#tripledes-cbc'): from Crypto.Cipher import DES3 from pskc.crypto import unpad if not iv: iv = ciphertext[:DES3.block_size] ciphertext = ciphertext[DES3.block_size:] cipher = DES3.new(key, DES3.MODE_CBC, iv) return unpad(cipher.decrypt(ciphertext), DES3.block_size) elif algorithm.endswith('#kw-aes128') or \ algorithm.endswith('#kw-aes192') or \ algorithm.endswith('#kw-aes256'): from pskc.crypto.aeskw import unwrap return unwrap(ciphertext, key) elif algorithm.endswith('#kw-tripledes'): # pragma: no branch from pskc.crypto.tripledeskw import unwrap return unwrap(ciphertext, key)
def decrypt(algorithm, key, ciphertext, iv=None): """Decrypt the ciphertext and return the plaintext value.""" from cryptography.hazmat.primitives.ciphers import algorithms from pskc.exceptions import DecryptionError if key is None: raise DecryptionError('No key available') if algorithm is None: raise DecryptionError('No algorithm specified') if len(key) not in algorithm_key_lengths(algorithm): raise DecryptionError('Invalid key length') if algorithm.endswith('#aes128-cbc') or \ algorithm.endswith('#aes192-cbc') or \ algorithm.endswith('#aes256-cbc'): return _decrypt_cbc(algorithms.AES, key, ciphertext, iv) elif algorithm.endswith('#tripledes-cbc'): return _decrypt_cbc(algorithms.TripleDES, key, ciphertext, iv) elif algorithm.endswith('#kw-aes128') or \ algorithm.endswith('#kw-aes192') or \ algorithm.endswith('#kw-aes256'): from pskc.crypto.aeskw import unwrap return unwrap(ciphertext, key) elif algorithm.endswith('#kw-tripledes'): from pskc.crypto.tripledeskw import unwrap return unwrap(ciphertext, key) elif (algorithm.endswith('#camellia128-cbc') or algorithm.endswith('#camellia192-cbc') or algorithm.endswith('#camellia256-cbc')): return _decrypt_cbc(algorithms.Camellia, key, ciphertext, iv) elif (algorithm.endswith('#kw-camellia128') or # pragma: no branch algorithm.endswith('#kw-camellia192') or algorithm.endswith('#kw-camellia256')): from pskc.crypto.aeskw import unwrap return unwrap(ciphertext, key, algorithm=algorithms.Camellia)
def algorithm_key_lengths(algorithm): """Return the possible key lengths for the configured algorithm.""" from pskc.exceptions import DecryptionError if algorithm is None: raise DecryptionError('No algorithm specified') elif algorithm.endswith('#aes128-cbc') or \ algorithm.endswith('#aes192-cbc') or \ algorithm.endswith('#aes256-cbc'): return [int(algorithm[-7:-4]) // 8] elif algorithm.endswith('#tripledes-cbc') or \ algorithm.endswith('#kw-tripledes'): return [16, 24] elif algorithm.endswith('#kw-aes128') or \ algorithm.endswith('#kw-aes192') or \ algorithm.endswith('#kw-aes256'): return [int(algorithm[-3:]) // 8] elif (algorithm.endswith('#camellia128-cbc') or algorithm.endswith('#camellia192-cbc') or algorithm.endswith('#camellia256-cbc')): return [int(algorithm[-7:-4]) // 8] elif (algorithm.endswith('#kw-camellia128') or algorithm.endswith('#kw-camellia192') or algorithm.endswith('#kw-camellia256')): return [int(algorithm[-3:]) // 8] else: raise DecryptionError('Unsupported algorithm: %r' % algorithm)
def get_mac(algorithm, key, value): """Generate the MAC value over the specified value.""" from pskc.exceptions import DecryptionError if algorithm is None: raise DecryptionError('No MAC algorithm set') hmacfn = get_hmac(algorithm) if hmacfn is None: raise DecryptionError('Unsupported MAC algorithm: %r' % algorithm) return hmacfn(key, value)
def unwrap(ciphertext, key): """Unwrap a key (typically Triple DES key ) with another Triple DES key. This uses the algorithm from RFC 3217 to decrypt the ciphertext (the previously wrapped key) using the provided key.""" if len(ciphertext) % DES3.block_size != 0: raise DecryptionError('Ciphertext length wrong') cipher = DES3.new(key, DES3.MODE_CBC, RFC3217_IV) tmp = cipher.decrypt(ciphertext)[::-1] cipher = DES3.new(key, DES3.MODE_CBC, tmp[:8]) tmp = cipher.decrypt(tmp[8:]) if tmp[-8:] == _cms_hash(tmp[:-8]): return tmp[:-8] raise DecryptionError('CMS key checksum error')
def unwrap(ciphertext, key, iv=None, pad=None, algorithm=algorithms.AES): """Apply the AES key unwrap algorithm to the ciphertext. The iv can specify an initial value, otherwise the value from RFC 3394 or RFC 5649 will be used, depending on the value of pad. If pad is False, unpadding as described in RFC 5649 will be disabled, otherwise checking and removing the padding is automatically done. """ if iv is not None: pad = False if len(ciphertext) % 8 != 0 or (pad is False and len(ciphertext) < 24): raise DecryptionError('Ciphertext length wrong') cipher = Cipher(algorithm(key), modes.ECB(), default_backend()) decryptor = cipher.decryptor() n = len(ciphertext) // 8 - 1 if n == 1: A, plaintext = _split(decryptor.update(ciphertext)) # noqa: N806 else: A = ciphertext[:8] # noqa: N806 R = [ ciphertext[(i + 1) * 8:(i + 2) * 8] # noqa: N806 for i in range(n) ] for j in reversed(range(6)): for i in reversed(range(n)): A = _strxor(A, struct.pack('>Q', n * j + i + 1)) # noqa: N806 A, R[i] = _split(decryptor.update(A + R[i])) # noqa: N806 plaintext = b''.join(R) if iv is None: if A == RFC3394_IV and pad is not True: return plaintext elif A[:4] == RFC5649_IV and pad is not False: mli = struct.unpack('>I', A[4:])[0] # check padding length is valid and plaintext only contains zeros if 8 * (n - 1) < mli <= 8 * n and \ plaintext.endswith((len(plaintext) - mli) * b'\0'): return plaintext[:mli] elif A == iv: return plaintext raise DecryptionError('IV does not match')
def unpad(value, block_size): """Remove padding from the plaintext.""" from pskc.exceptions import DecryptionError padding = ord(value[-1:]) # only unpad if all padding bytes are the same if (padding > 0 and padding <= block_size and value[-padding:] == padding * chr(padding).encode('ascii')): return value[:-padding] raise DecryptionError('Invalid padding')
def unwrap(ciphertext, key): """Unwrap a key (typically Triple DES key ) with another Triple DES key. This uses the algorithm from RFC 3217 to decrypt the ciphertext (the previously wrapped key) using the provided key. """ if 8 * len(ciphertext) % algorithms.TripleDES.block_size != 0: raise DecryptionError('Ciphertext length wrong') backend = default_backend() cipher = Cipher(algorithms.TripleDES(key), modes.CBC(RFC3217_IV), backend) decryptor = cipher.decryptor() tmp = (decryptor.update(ciphertext) + decryptor.finalize())[::-1] cipher = Cipher(algorithms.TripleDES(key), modes.CBC(tmp[:8]), backend) decryptor = cipher.decryptor() tmp = decryptor.update(tmp[8:]) + decryptor.finalize() if tmp[-8:] == _cms_hash(tmp[:-8]): return tmp[:-8] raise DecryptionError('CMS key checksum error')
def algorithm_key_lengths(algorithm): """Return the possible key lengths for the configured algorithm.""" from pskc.exceptions import DecryptionError if algorithm is None: raise DecryptionError('No algorithm specified') elif algorithm.endswith('#aes128-cbc') or \ algorithm.endswith('#aes192-cbc') or \ algorithm.endswith('#aes256-cbc'): return [int(algorithm[-7:-4]) // 8] elif algorithm.endswith('#tripledes-cbc') or \ algorithm.endswith('#kw-tripledes'): from Crypto.Cipher import DES3 return list(DES3.key_size) elif algorithm.endswith('#kw-aes128') or \ algorithm.endswith('#kw-aes192') or \ algorithm.endswith('#kw-aes256'): return [int(algorithm[-3:]) // 8] else: raise DecryptionError('Unsupported algorithm: %r' % algorithm)
def unwrap(ciphertext, key, iv=None, pad=None): """Apply the AES key unwrap algorithm to the ciphertext. The iv can specify an initial value, otherwise the value from RFC 3394 or RFC 5649 will be used, depending on the value of pad. If pad is False, unpadding as described in RFC 5649 will be disabled, otherwise checking and removing the padding is automatically done.""" if iv is not None: pad = False if len(ciphertext) % 8 != 0 or (pad is False and len(ciphertext) < 24): raise DecryptionError('Ciphertext length wrong') decrypt = AES.new(key).decrypt n = len(ciphertext) // 8 - 1 if n == 1: A, plaintext = _split(decrypt(ciphertext)) else: A = ciphertext[:8] R = [ciphertext[(i + 1) * 8:(i + 2) * 8] for i in range(n)] for j in reversed(range(6)): for i in reversed(range(n)): A = strxor(A, long_to_bytes(n * j + i + 1, 8)) A, R[i] = _split(decrypt(A + R[i])) plaintext = b''.join(R) if iv is None: if A == RFC3394_IV and pad is not True: return plaintext elif A[:4] == RFC5649_IV and pad is not False: mli = bytes_to_long(A[4:]) # check padding length is valid and plaintext only contains zeros if 8 * (n - 1) < mli <= 8 * n and \ plaintext.endswith((len(plaintext) - mli) * b'\0'): return plaintext[:mli] elif A == iv: return plaintext raise DecryptionError('IV does not match')
def _get_hash_obj(algorithm, *args): """Return an instantiated hash object.""" import hashlib from pskc.algorithms import normalise_algorithm from pskc.exceptions import DecryptionError match = _hmac_url_re.search(normalise_algorithm(algorithm) or '') if match: try: return hashlib.new(match.group('hash'), *args) except ValueError: pass raise DecryptionError('Unsupported MAC algorithm: %r' % algorithm)
def get_value(self, pskc): """Provide the decrypted value.""" from pskc.exceptions import DecryptionError plaintext = pskc.encryption.decrypt_value( self.cipher_value, self.algorithm) # allow MAC over plaintext or cipertext # (RFC 6030 implies MAC over ciphertext but older draft used # MAC over plaintext) if self.mac_value and self.mac_value not in ( pskc.mac.generate_mac(self.cipher_value), pskc.mac.generate_mac(plaintext)): raise DecryptionError('MAC value does not match') return plaintext
def get_value(self): """Provide the attribute value, decrypting as needed.""" from pskc.exceptions import DecryptionError if self.value is not None: return self.value if self.cipher_value: plaintext = self.pskc.encryption.decrypt_value( self.cipher_value, self.algorithm) # allow MAC over plaintext or cipertext # (RFC6030 implies MAC over ciphertext but older draft used # MAC over plaintext) if self.value_mac and self.value_mac not in ( self.pskc.mac.generate_mac(self.cipher_value), self.pskc.mac.generate_mac(plaintext)): raise DecryptionError('MAC value does not match') return self._from_bin(plaintext)
def _decrypt_cbc(algorithm, key, ciphertext, iv=None): """Decrypt the ciphertext and return the plaintext value.""" from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives.ciphers import Cipher, modes from pskc.exceptions import DecryptionError if not iv: iv = ciphertext[:algorithm.block_size // 8] ciphertext = ciphertext[algorithm.block_size // 8:] cipher = Cipher(algorithm(key), modes.CBC(iv), backend=default_backend()) decryptor = cipher.decryptor() unpadder = padding.PKCS7(algorithm.block_size).unpadder() try: return unpadder.update( decryptor.update(ciphertext) + decryptor.finalize()) + unpadder.finalize() except ValueError: raise DecryptionError('Invalid padding')