def decrypt(enc, key, iv, aad, ct, tag): """ Decrypt JWE content. @type enc: string @param enc: The JWE "enc" value specifying the encryption algorithm @type key: bytes @param key: Key (CEK) @type iv : bytes @param iv : Initialization vector @type aad: bytes @param aad: Additional authenticated data @type ct : bytes @param ct : Ciphertext @type tag: bytes @param tag: Authentication tag @rtype: tuple @return: (ciphertext, tag), both as bytes """ if enc in ["A128GCM", "A192GCM", "A256GCM"]: gcm = AES_GCM(bytes_to_long(key)) try: pt = gcm.decrypt(bytes_to_long(iv), ct, bytes_to_long(tag), aad) return (pt, True) except InvalidTagException: return (None, False) elif enc in ["A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512"]: return polyfills.AES_CBC_HMAC_decrypt( key, iv, aad, ct, tag ) else: raise Exception("Unsupported encryption algorithm {}".format(enc))
def encrypt(enc, key, iv, aad, pt): """ Encrypt JWE content. @type enc: string @param enc: The JWE "enc" value specifying the encryption algorithm @type key: bytes @param key: Key (CEK) @type iv : bytes @param iv : Initialization vector @type aad: bytes @param aad: Additional authenticated data @type pt : bytes @param pt : Plaintext @rtype: tuple @return: (ciphertext, tag), both as bytes """ if enc in ["A128GCM", "A192GCM", "A256GCM"]: gcm = AES_GCM(bytes_to_long(key)) (ct, tag) = gcm.encrypt(bytes_to_long(iv), pt, aad) return (ct, long_to_bytes(tag, 16)) elif enc in ["A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512"]: return polyfills.AES_CBC_HMAC_encrypt( key, iv, aad, pt ) else: raise Exception("Unsupported encryption algorithm {}".format(enc))
def _decrypt(enc, key, ctxt, auth_data, iv, tag): """ Decrypt JWE content. :param enc: The JWE "enc" value specifying the encryption algorithm :param key: Key (CEK) :param iv : Initialization vector :param auth_data: Additional authenticated data :param ctxt : Ciphertext :param tag: Authentication tag :return: plain text message or None if decryption failed """ if enc in ["A128GCM", "A192GCM", "A256GCM"]: gcm = AES_GCM(bytes_to_long(key)) try: text = gcm.decrypt(bytes_to_long(iv), ctxt, bytes_to_long(tag), auth_data) return text, True except DecryptionFailed: return None, False elif enc in ["A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512"]: return aes_cbc_hmac_decrypt(key, iv, auth_data, ctxt, tag) else: raise Exception("Unsupported encryption algorithm %s" % enc)
def enc_setup(self, enc_alg, msg, auth_data, key=None, iv=""): """ Encrypt JWE content. :param enc_alg: The JWE "enc" value specifying the encryption algorithm :param msg: The plain text message :param auth_data: Additional authenticated data :param key: Key (CEK) :return: Tuple (ciphertext, tag), both as bytes """ key, iv = self._generate_key_and_iv(enc_alg, key, iv) if enc_alg == "A256GCM": gcm = AES_GCM(bytes_to_long(key)) ctxt, tag = gcm.encrypt(bytes_to_long(iv), msg, auth_data) tag = long_to_bytes(tag) elif enc_alg in ["A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512"]: assert enc_alg in SUPPORTED["enc"] #ealg, hashf = enc.split("-") ctxt, tag = aes_cbc_hmac_encrypt(key, iv, auth_data, msg) else: raise NotSupportedAlgorithm(enc_alg) return ctxt, tag, key
def test_jwe_09_a1(): # RSAES OAEP and AES GCM msg = "The true sign of intelligence is not knowledge but imagination." # A.1.1 header = '{"alg":"RSA-OAEP","enc":"A256GCM"}' b64_header = b64e(header) # A.1.2 assert b64_header == "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ" # A.1.3 cek = intarr2long([ 177, 161, 244, 128, 84, 143, 225, 115, 63, 180, 3, 255, 107, 154, 212, 246, 138, 7, 110, 91, 112, 46, 34, 105, 47, 130, 203, 46, 122, 234, 64, 252 ]) # A.1.4 Key Encryption enc_key = [ 56, 163, 154, 192, 58, 53, 222, 4, 105, 218, 136, 218, 29, 94, 203, 22, 150, 92, 129, 94, 211, 232, 53, 89, 41, 60, 138, 56, 196, 216, 82, 98, 168, 76, 37, 73, 70, 7, 36, 8, 191, 100, 136, 196, 244, 220, 145, 158, 138, 155, 4, 117, 141, 230, 199, 247, 173, 45, 182, 214, 74, 177, 107, 211, 153, 11, 205, 196, 171, 226, 162, 128, 171, 182, 13, 237, 239, 99, 193, 4, 91, 219, 121, 223, 107, 167, 61, 119, 228, 173, 156, 137, 134, 200, 80, 219, 74, 253, 56, 185, 91, 177, 34, 158, 89, 154, 205, 96, 55, 18, 138, 43, 96, 218, 215, 128, 124, 75, 138, 243, 85, 25, 109, 117, 140, 26, 155, 249, 67, 167, 149, 231, 100, 6, 41, 65, 214, 251, 232, 87, 72, 40, 182, 149, 154, 168, 31, 193, 126, 215, 89, 28, 111, 219, 125, 182, 139, 235, 195, 197, 23, 234, 55, 58, 63, 180, 68, 202, 206, 149, 75, 205, 248, 176, 67, 39, 178, 60, 98, 193, 32, 238, 122, 96, 158, 222, 57, 183, 111, 210, 55, 188, 215, 206, 180, 166, 150, 166, 106, 250, 55, 229, 72, 40, 69, 214, 216, 104, 23, 40, 135, 212, 28, 127, 41, 80, 175, 174, 168, 115, 171, 197, 89, 116, 92, 103, 246, 83, 216, 182, 176, 84, 37, 147, 35, 45, 219, 172, 99, 226, 233, 73, 37, 124, 42, 72, 49, 242, 35, 127, 184, 134, 117, 114, 135, 206 ] b64_ejek = "ApfOLCaDbqs_JXPYy2I937v_xmrzj-Iss1mG6NAHmeJViM6j2l0MHvfseIdHVyU2BIoGVu9ohvkkWiRq5DL2jYZTPA9TAdwq3FUIVyoH-Pedf6elHIVFi2KGDEspYMtQARMMSBcS7pslx6flh1Cfh3GBKysztVMEhZ_maFkm4PYVCsJsvq6Ct3fg2CJPOs0X1DHuxZKoIGIqcbeK4XEO5a0h5TAuJObKdfO0dKwfNSSbpu5sFrpRFwV2FTTYoqF4zI46N9-_hMIznlEpftRXhScEJuZ9HG8C8CHB1WRZ_J48PleqdhF4o7fB5J1wFqUXBtbtuGJ_A2Xe6AEhrlzCOw" iv = intarr2long([227, 197, 117, 252, 2, 219, 233, 68, 180, 225, 77, 219]) aadp = b64_header + b'.' + b64_ejek gcm = AES_GCM(cek) ctxt, tag = gcm.encrypt(iv, msg, aadp) _va = [ord(c) for c in ctxt] assert _va == [ 229, 236, 166, 241, 53, 191, 115, 196, 174, 43, 73, 109, 39, 122, 233, 96, 140, 206, 120, 52, 51, 237, 48, 11, 190, 219, 186, 80, 111, 104, 50, 142, 47, 167, 59, 61, 181, 127, 196, 21, 40, 82, 242, 32, 123, 143, 168, 226, 73, 216, 176, 144, 138, 247, 106, 60, 16, 205, 160, 109, 64, 63, 192 ] assert byte_arr(tag) == [ 130, 17, 32, 198, 120, 167, 144, 113, 0, 50, 158, 49, 102, 208, 118, 152 ] tag = long2hexseq(tag) iv = long2hexseq(iv) res = b".".join([b64_header, b64_ejek, b64e(iv), b64e(ctxt), b64e(tag)]) assert res == "".join([ "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.", "ApfOLCaDbqs_JXPYy2I937v_xmrzj-Iss1mG6NAHmeJViM6j2l0MHvfseIdHVyU2", "BIoGVu9ohvkkWiRq5DL2jYZTPA9TAdwq3FUIVyoH-Pedf6elHIVFi2KGDEspYMtQ", "ARMMSBcS7pslx6flh1Cfh3GBKysztVMEhZ_maFkm4PYVCsJsvq6Ct3fg2CJPOs0X", "1DHuxZKoIGIqcbeK4XEO5a0h5TAuJObKdfO0dKwfNSSbpu5sFrpRFwV2FTTYoqF4", "zI46N9-_hMIznlEpftRXhScEJuZ9HG8C8CHB1WRZ_J48PleqdhF4o7fB5J1wFqUX", "BtbtuGJ_A2Xe6AEhrlzCOw.", "48V1_ALb6US04U3b.", "5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji", "SdiwkIr3ajwQzaBtQD_A.", "ghEgxninkHEAMp4xZtB2mA" ])
def test_jwe_09_a1(): # RSAES OAEP and AES GCM msg = "The true sign of intelligence is not knowledge but imagination." # A.1.1 header = '{"alg":"RSA-OAEP","enc":"A256GCM"}' b64_header = b64e(header) # A.1.2 assert b64_header == "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ" # A.1.3 cek = intarr2long([177, 161, 244, 128, 84, 143, 225, 115, 63, 180, 3, 255, 107, 154, 212, 246, 138, 7, 110, 91, 112, 46, 34, 105, 47, 130, 203, 46, 122, 234, 64, 252]) # A.1.4 Key Encryption enc_key = [ 56, 163, 154, 192, 58, 53, 222, 4, 105, 218, 136, 218, 29, 94, 203, 22, 150, 92, 129, 94, 211, 232, 53, 89, 41, 60, 138, 56, 196, 216, 82, 98, 168, 76, 37, 73, 70, 7, 36, 8, 191, 100, 136, 196, 244, 220, 145, 158, 138, 155, 4, 117, 141, 230, 199, 247, 173, 45, 182, 214, 74, 177, 107, 211, 153, 11, 205, 196, 171, 226, 162, 128, 171, 182, 13, 237, 239, 99, 193, 4, 91, 219, 121, 223, 107, 167, 61, 119, 228, 173, 156, 137, 134, 200, 80, 219, 74, 253, 56, 185, 91, 177, 34, 158, 89, 154, 205, 96, 55, 18, 138, 43, 96, 218, 215, 128, 124, 75, 138, 243, 85, 25, 109, 117, 140, 26, 155, 249, 67, 167, 149, 231, 100, 6, 41, 65, 214, 251, 232, 87, 72, 40, 182, 149, 154, 168, 31, 193, 126, 215, 89, 28, 111, 219, 125, 182, 139, 235, 195, 197, 23, 234, 55, 58, 63, 180, 68, 202, 206, 149, 75, 205, 248, 176, 67, 39, 178, 60, 98, 193, 32, 238, 122, 96, 158, 222, 57, 183, 111, 210, 55, 188, 215, 206, 180, 166, 150, 166, 106, 250, 55, 229, 72, 40, 69, 214, 216, 104, 23, 40, 135, 212, 28, 127, 41, 80, 175, 174, 168, 115, 171, 197, 89, 116, 92, 103, 246, 83, 216, 182, 176, 84, 37, 147, 35, 45, 219, 172, 99, 226, 233, 73, 37, 124, 42, 72, 49, 242, 35, 127, 184, 134, 117, 114, 135, 206] b64_ejek = "ApfOLCaDbqs_JXPYy2I937v_xmrzj-Iss1mG6NAHmeJViM6j2l0MHvfseIdHVyU2BIoGVu9ohvkkWiRq5DL2jYZTPA9TAdwq3FUIVyoH-Pedf6elHIVFi2KGDEspYMtQARMMSBcS7pslx6flh1Cfh3GBKysztVMEhZ_maFkm4PYVCsJsvq6Ct3fg2CJPOs0X1DHuxZKoIGIqcbeK4XEO5a0h5TAuJObKdfO0dKwfNSSbpu5sFrpRFwV2FTTYoqF4zI46N9-_hMIznlEpftRXhScEJuZ9HG8C8CHB1WRZ_J48PleqdhF4o7fB5J1wFqUXBtbtuGJ_A2Xe6AEhrlzCOw" iv = intarr2long([227, 197, 117, 252, 2, 219, 233, 68, 180, 225, 77, 219]) aadp = b64_header + b'.' + b64_ejek gcm = AES_GCM(cek) ctxt, tag = gcm.encrypt(iv, msg, aadp) _va = [ord(c) for c in ctxt] assert _va == [229, 236, 166, 241, 53, 191, 115, 196, 174, 43, 73, 109, 39, 122, 233, 96, 140, 206, 120, 52, 51, 237, 48, 11, 190, 219, 186, 80, 111, 104, 50, 142, 47, 167, 59, 61, 181, 127, 196, 21, 40, 82, 242, 32, 123, 143, 168, 226, 73, 216, 176, 144, 138, 247, 106, 60, 16, 205, 160, 109, 64, 63, 192] assert byte_arr(tag) == [130, 17, 32, 198, 120, 167, 144, 113, 0, 50, 158, 49, 102, 208, 118, 152] tag = long2hexseq(tag) iv = long2hexseq(iv) res = b".".join([b64_header, b64_ejek, b64e(iv), b64e(ctxt), b64e(tag)]) assert res == "".join([ "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.", "ApfOLCaDbqs_JXPYy2I937v_xmrzj-Iss1mG6NAHmeJViM6j2l0MHvfseIdHVyU2", "BIoGVu9ohvkkWiRq5DL2jYZTPA9TAdwq3FUIVyoH-Pedf6elHIVFi2KGDEspYMtQ", "ARMMSBcS7pslx6flh1Cfh3GBKysztVMEhZ_maFkm4PYVCsJsvq6Ct3fg2CJPOs0X", "1DHuxZKoIGIqcbeK4XEO5a0h5TAuJObKdfO0dKwfNSSbpu5sFrpRFwV2FTTYoqF4", "zI46N9-_hMIznlEpftRXhScEJuZ9HG8C8CHB1WRZ_J48PleqdhF4o7fB5J1wFqUX", "BtbtuGJ_A2Xe6AEhrlzCOw.", "48V1_ALb6US04U3b.", "5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji", "SdiwkIr3ajwQzaBtQD_A.", "ghEgxninkHEAMp4xZtB2mA"])
def decryptKey(alg, enc, jwk, encryptedKey, header={}): """ Decrypt a JWE encrypted key. @type alg: string @param alg: The JWE "alg" value specifying the key management algorithm @type enc: string @param enc: The JWE "enc" value specifying the encryption algorithm @type jwk: dict @param jwk: The key to be used, as an unserialized JWK object @type encryptedKey: bytes @param encryptedKey: The key to decrypt @type header: dict @param header: A header object containing additional parameters @rtype: bytes @return: The decrypted key """ key = importKey(jwk, private=True) if alg == "RSA1_5": sentinel = "fnord" cipher = PKCS1_v1_5.new(key) CEK = cipher.decrypt(encryptedKey, sentinel) if CEK == sentinel: raise Exception("Unable to unwrap key") return CEK elif alg == "RSA-OAEP": cipher = PKCS1_OAEP.new(key) CEK = cipher.decrypt(encryptedKey) return CEK elif alg in ["A128KW", "A192KW", "A256KW"]: return polyfills.aes_key_unwrap(key, encryptedKey) elif alg == "dir": if encryptedKey and len(encryptedKey) > 0: raise Exception("Direct encryption with non-empty encrypted key") return key elif alg in ["ECDH-ES", "ECDH-ES+A128KW", "ECDH-ES+A192KW", "ECDH-ES+A256KW"]: # Pull the input parameters epk = importKey(getOrRaise(header, "epk")) apu = b64dec(getOrRaise(header, "apu")) apv = b64dec(getOrRaise(header, "apv")) # Select the curve curve = NISTEllipticCurve.byName(getOrRaise(header["epk"], "crv")) # Derive the KEK and decrypt if alg == "ECDH-ES": dkLen = keyLength(enc) return polyfills.ECDH_deriveKey(curve, key, epk, apu, apv, enc, dkLen) elif alg == "ECDH-ES+A128KW": kek = polyfills.ECDH_deriveKey(curve, key, epk, apu, apv, "A128KW", 128) return polyfills.aes_key_unwrap(kek, encryptedKey) elif alg == "ECDH-ES+A192KW": kek = polyfills.ECDH_deriveKey(curve, key, epk, apu, apv, "A192KW", 192) return polyfills.aes_key_unwrap(kek, encryptedKey) elif alg == "ECDH-ES+A256KW": kek = polyfills.ECDH_deriveKey(curve, key, epk, apu, apv, "A256KW", 256) return polyfills.aes_key_unwrap(kek, encryptedKey) elif alg in ["A128GCMKW", "A192GCMKW", "A256GCMKW"]: iv = b64dec(getOrRaise(header, "iv")) tag = b64dec(getOrRaise(header, "tag")) gcm = AES_GCM(bytes_to_long(key)) return gcm.decrypt(bytes_to_long(iv), encryptedKey, bytes_to_long(tag), '') elif alg == "PBES2-HS256+A128KW": return polyfills.PBKDF_key_unwrap(key, encryptedKey, SHA256, 16, header) elif alg == "PBES2-HS384+A192KW": return polyfills.PBKDF_key_unwrap(key, encryptedKey, SHA384, 24, header) elif alg == "PBES2-HS512+A256KW": return polyfills.PBKDF_key_unwrap(key, encryptedKey, SHA512, 32, header) else: raise Exception("Unsupported key management algorithm " + alg)
def generateSenderParams(alg, enc, jwk, header={}, inCEK=None): """ Generate parameters for the sender of a JWE. This is essentially an encryptKey method, except (1) in some cases, the key is specified directly or derived ("dir", "ECDH"), and (2) other parameters besides the encrypted key are generated (e.g., the IV). This method returns several things: - A random CEK (can be overridden with the inCEK parameter) - The encrypted CEK - A random IV - A dictionary of parameters generated within this function The idea is that the parameters generated within this function (e.g., "epk", "p2s") should be added back to the JWE header @type alg: string @param alg: The JWE "alg" value specifying the key management algorithm @type enc: string @param enc: The JWE "enc" value specifying the encryption algorithm @type jwk: dict @param jwk: The key to be used, as an unserialized JWK object @type header: dict @param header: A header object with additional parameters @type inCEK: bytes @param inCEK: A fixed CEK (overrides random CEK generation) @rtype: tuple @return: (CEK, encryptedKey, IV, params), the first three as bytes, params as dict. """ # Generate a random key/iv for enc (CEK, IV) = generateKeyIV(enc) if inCEK: CEK = inCEK encryptedKey = "" params = {} key = importKey(jwk, private=False) # Encrypt key / generate params as defined by alg if alg == "RSA1_5": cipher = PKCS1_v1_5.new(key) encryptedKey = cipher.encrypt(CEK) elif alg == "RSA-OAEP": (CEK, IV) = generateKeyIV(enc) cipher = PKCS1_OAEP.new(key) encryptedKey = cipher.encrypt(CEK) elif alg in ["A128KW", "A192KW", "A256KW"]: encryptedKey = polyfills.aes_key_wrap(key, CEK) elif alg == "dir": CEK = key elif alg in ["ECDH-ES", "ECDH-ES+A128KW", "ECDH-ES+A192KW", "ECDH-ES+A256KW"]: # Generate the input parameters apu = b64dec(header["apu"]) if "apu" in header else Random.get_random_bytes(16) apv = b64dec(header["apv"]) if "apv" in header else Random.get_random_bytes(16) # Generate an ephemeral key pair curve = NISTEllipticCurve.byName(getOrRaise(jwk, "crv")) if "epk" in header: epk = importKey(header["epk"], private=False) eprivk = importKey(header["epk"], private=True) else: (eprivk, epk) = curve.keyPair() # Derive the KEK and encrypt params = { "apu": b64enc(apu), "apv": b64enc(apv), "epk": exportKey(epk, "EC", curve) } if alg == "ECDH-ES": dkLen = keyLength(enc) CEK = polyfills.ECDH_deriveKey(curve, eprivk, key, apu, apv, enc, dkLen) elif alg == "ECDH-ES+A128KW": kek = polyfills.ECDH_deriveKey(curve, eprivk, key, apu, apv, "A128KW", 128) encryptedKey = polyfills.aes_key_wrap(kek, CEK) elif alg == "ECDH-ES+A192KW": kek = polyfills.ECDH_deriveKey(curve, eprivk, key, apu, apv, "A192KW", 192) encryptedKey = polyfills.aes_key_wrap(kek, CEK) elif alg == "ECDH-ES+A256KW": kek = polyfills.ECDH_deriveKey(curve, eprivk, key, apu, apv, "A256KW", 256) encryptedKey = polyfills.aes_key_wrap(kek, CEK) elif alg in ["A128GCMKW", "A192GCMKW", "A256GCMKW"]: iv = Random.get_random_bytes(12) gcm = AES_GCM(bytes_to_long(key)) encryptedKey, tag = gcm.encrypt(bytes_to_long(iv), CEK, '') params = { "iv": b64enc(iv), "tag": b64enc(long_to_bytes(tag,16)) } elif alg == "PBES2-HS256+A128KW": (CEK, IV) = generateKeyIV(enc) (encryptedKey, params) = \ polyfills.PBKDF_key_wrap( key, CEK, SHA256, 16, header ) elif alg == "PBES2-HS384+A192KW": (CEK, IV) = generateKeyIV(enc) (encryptedKey, params) = \ polyfills.PBKDF_key_wrap( key, CEK, SHA384, 24, header ) elif alg == "PBES2-HS512+A256KW": (CEK, IV) = generateKeyIV(enc) (encryptedKey, params) = \ polyfills.PBKDF_key_wrap( key, CEK, SHA512, 32, header ) else: raise Exception("Unsupported key management algorithm " + alg) return (CEK, encryptedKey, IV, params)