Skip to content

Instantly share code, notes, and snippets.

@ulidtko
Created March 5, 2020 14:14
Show Gist options
  • Save ulidtko/35cc082a27c348501e5e8305f2aecd77 to your computer and use it in GitHub Desktop.
Save ulidtko/35cc082a27c348501e5e8305f2aecd77 to your computer and use it in GitHub Desktop.
TLS1.2 RSA-AES key exchange decryption example
#/usr/bin/env python3
"""
An exploration of TLS_RSA_WITH_AES_256_CBC_SHA key exchange in TLS v1.2
-- using https://cryptography.io/
[1]: https://lowleveldesign.org/2016/03/09/manually-decrypting-https-request/
[2]: https://www.acunetix.com/blog/articles/establishing-tls-ssl-connection-part-5/
[RFC5246]: https://tools.ietf.org/html/rfc5246
"""
__author__ = "Max <[email protected]>"
# pylint: disable=line-too-long
from binascii import hexlify, unhexlify
from dataclasses import dataclass
from itertools import chain, islice
from pprint import pprint
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes, hmac
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
@dataclass
class RsaKexParams:
""" RSA key exchange parameters -- extract these from your Wireshark dump """
clientrandom: bytes
serverrandom: bytes
encrypted_premaster: bytes
#-- the first TLS-encrypted record, 64 bytes, "Handshake Protocol: Finished"
handshake_finished: bytes
# -- -- -- extract from wireshark -- -- -- -- -- -- -- -- -- -- -- -- -- #
# TEST1 = RsaKexParams(
# clientrandom=unhexlify('e1:52:d4:9c:3c:f3:25:d7:4f:82:0c:9f:0e:30:10:87:77:c0:4d:18:64:e2:c5:62:85:15:1e:60:bf:af:10:1b'.replace(':', '')),
# serverrandom=unhexlify('7c:6a:74:59:ca:ac:2d:95:82:9c:4e:a9:31:60:00:d8:12:18:82:73:46:9c:ad:51:44:4f:57:4e:47:52:44:01'.replace(':', '')),
# encrypted_premaster=unhexlify('a3a7ba01e79d19010649431e19c0f0933e377656cd0662f4afd1d96470187e5d6536927ed2ff4240e3176d53a38b92cfb7d1cbcc1ba37e255241e2a5edb5f99b97bed355a5af858d413fa391b87b0e0e2457c7af5c612f654c40cd34452e29373126259700cb7429cbcf329c760c0c90d97eccd1fb7b83999a1a8abe008f58f4f224ee8a1a5eba4b2073b028a034ff2c5caba7145d30d88c8231eaf6a036bcfcad388145eac50d3756f681c72467b78cde8e4eae19097034566a8b16d67685a78753db4926c1f1288b8d97f78651d351787fb9caa0a69a36a90bb9f9255e3ddbe73d52012bcf9c6465e6f1d34a226c3cd38e3cb4d8754de2b7152ade36156f3e')
# )
#-- modem-kex-wtf.1.pcapng
TEST1 = RsaKexParams(
clientrandom=unhexlify('5e5fbf3a57ccbd4d5ce3951556d54773039005541d7a64847718cfa52ce46006'),
serverrandom=unhexlify('f402e9748ccaf418ad4ee9d34a5921a5237e5d442108b83a444f574e47524401'),
encrypted_premaster=unhexlify('54bc7b307a2cdb53474afb85ea1881bb75f5dde51bae37ce8137c8f7730830f6444acce9e3dd1ca3b5c2b968a03ec4583168a37175cb6a3471f26f9b737ceba6840dee2b0fcbe49a1d645d01da3af91caf1e0971e49558794c07fd7411c19bf93576d171ac0e7f7c75dee8e93d738cb3185d595530cb5ab53cad167cfcc2ea82759f73b51d3e824b135c459499c67d5cafe5233f834fcfc2aa67a28036bb5730d2177b35882da1a36943be393afd31d5996b78cda3710847a85f9dd7b455751e0944f2b18dfb04481b59c7908d063bcf7e6aac64c478e41a3cca91abb0439bf28a048b639d066e9edfdcc191bc07292d7c6197ce2bef1425427faa2130c9f0f4'),
handshake_finished=unhexlify('b66d95cb60c0631ecdb24cb913afeff4bb00b0d6ef058df298cf5e7b4bda2ce1ceca3a7af9f1182ac4c567de181dc1ff45cc7898a7ad92b9638b0adc31efaa6b')
)
#-- modem-kex-wtf.2.pcapng
TEST2 = RsaKexParams(
clientrandom=unhexlify('5e5ff3ce5f2cc1111387165a947cd3a0f5e16386f87483c32b5657e963675e6e'),
serverrandom=unhexlify('c5b8197b542240a463ea9deaec2d474142a173a89ad0f0d7444f574e47524401'),
encrypted_premaster=unhexlify('2c44c3fec4ea69894a6d10248597ddc9596f02394d45d4591d0fd53d3ebdd220e11ef6145c601b8faa5725ccb7c878dce82749b134242423bb45c0c4336526ad5321f216b7a6e9267f509497e4a2f088cda5d43ff17d55b849878816b4ee74e2b6d5f4700b58b7b66f8f21da06a000b642b2fd5e285a694f25099e523814a8027d0a25d35020b6a04c36bb969259559598674fd9d245cfa99eee312090e0ebc1b4190f6fab74b9c45692fd817ce9788439c3d4aaa49b2e6464849d17e3575c6540de965196b72fb858e414ac5605f6714f7b969ef755f06c32a4b456ab76cd675c9c57e39be21f1402a0478c9d7cfc03d391d2bcebdcd557a1fa66e89963e3db'),
handshake_finished=unhexlify('af0757a141108c423243693673d735ac12c9499d5e602d79d54163d19150bdd1ae40f2caa4128846e644d44740b717ef6869b6e3073664d860a80da9f33504a9')
)
# TEST = RsaKexParams(
# clientrandom=unhexlify(''),
# serverrandom=unhexlify(''),
# encrypted_premaster=unhexlify(''),
# handshake_finished=unhexlify('')
# )
SERVER_PRIVKEY_PATH = '/tmp/ftpkey.pem'
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #
# a few clarity wrappers for the Y U NO FUNCTIONAL crypto apis.
def hmac_sha256(key, bmsg):
digest = hmac.HMAC(key, hashes.SHA256(), backend=default_backend())
digest.update(bmsg)
return digest.finalize()
def hmac_sha1(key, bmsg):
digest = hmac.HMAC(key, hashes.SHA1(), backend=default_backend())
digest.update(bmsg)
return digest.finalize()
def hmac_md5(key, bmsg):
digest = hmac.HMAC(key, hashes.MD5(), backend=default_backend())
digest.update(bmsg)
return digest.finalize()
def aes256cbc_decrypt(key, iv, msg):
decryptor = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()).decryptor()
return decryptor.update(msg) + decryptor.finalize()
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #
def tls12_prf_sha256(secret: bytes, label: str, seed: bytes, length: int) -> bytes:
""" https://tools.ietf.org/html/rfc5246#section-5 """
def p_sha256(secret, seed):
a = seed
while True:
a = hmac_sha256(secret, a)
yield hmac_sha256(secret, a + seed)
chunk_generator = p_sha256(secret, label.encode('ascii') + seed)
return bytes(islice(chain.from_iterable(chunk_generator), length))
def tls10_prf(secret: bytes, label: str, seed: bytes, length: int) -> bytes:
""" https://tools.ietf.org/html/rfc2246#section-5 """
def p_md5(secret, seed):
a = seed
while True:
a = hmac_md5(secret, a)
yield hmac_md5(secret, a + seed)
def p_sha1(secret, seed):
a = seed
while True:
a = hmac_sha1(secret, a)
yield hmac_sha1(secret, a + seed)
halflen = (len(secret) + 1) // 2
s1, s2 = secret[:halflen], secret[-halflen:]
chunks_md5 = p_md5(s1, label.encode('ascii') + seed)
chunks_sha1 = p_sha1(s2, label.encode('ascii') + seed)
bytes_generator = (x ^ y for x, y in zip(chain.from_iterable(chunks_md5),
chain.from_iterable(chunks_sha1)))
return bytes(islice(bytes_generator, length))
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #
def blog_test():
clientrandom = unhexlify('56 d9 7d 9d be c6 ea 92 23 c1 5f d6 3d fd 22 24 90 13 f2 34 aa 04 ed cc 30 8a f6 c5 09 f8 60 a5'.replace(' ', ''))
serverrandom = unhexlify('16 62 fe 6f 2c 14 06 b2 73 a5 e3 76 a6 25 4a 81 86 06 d4 75 9a b7 ef b7 97 a1 e5 59 f4 dd 04 7f'.replace(' ', ''))
premaster = unhexlify("03 01 82 BB 54 5E 61 A2 7C D2 B0 BD 59 9E E8 6A 41 9C 63 0C 4B B9 E1 25 DB 9F 9E 08 F4 D4 41 F6 1F 3B 1E C1 3E 88 BA DD 38 AE E3 73 E7 88 FC AB".replace(' ', ''))
master_secret_expected = unhexlify("27 90 3D 7D 3F 40 B9 0E 64 84 40 87 A5 8D FF F0 1A 1D D7 6C 96 81 8E F9 62 4F 38 AF 38 60 9E 52 D3 18 0F E8 07 3D 4A 81 EC 28 26 44 22 66 C8 CF".replace(' ', ''))
master_secret_computed = tls10_prf(premaster, "master secret", clientrandom + serverrandom, 48)
# print("[BLOG TEST] computed master_secret:")
# pprint(hexlify(master_secret_computed))
# print("[BLOG TEST] expected master_secret:")
# pprint(hexlify(master_secret_expected))
assert master_secret_computed == master_secret_expected
def tohex(m: bytes) -> str:
return hexlify(m).decode('ascii')
def tohex_abbrev(m: bytes, detail: int = 4) -> str:
nlead, nlast = detail, detail * 3 // 4
return hexlify(m).decode('ascii') if len(m) <= 32 \
else "{}..{}".format(hexlify(m[:nlead]).decode('ascii'), hexlify(m[-nlast:]).decode('ascii'))
def run_single(kex: RsaKexParams):
print()
print("### Parameters dump ###")
print()
print(f" - server PEM key path: {SERVER_PRIVKEY_PATH}")
print(f" - client random: {kex.clientrandom}")
print(f" - server random: {kex.serverrandom}")
print()
rsa_privkey = serialization.load_pem_private_key(open(SERVER_PRIVKEY_PATH, 'rb').read(), password=None, backend=default_backend())
premaster = rsa_privkey.decrypt(kex.encrypted_premaster, padding.PKCS1v15())
print("=== PreMaster Secret ===")
print(f" encrypted: {tohex_abbrev(kex.encrypted_premaster, 8)}")
print(f" decrypted: {tohex_abbrev(premaster, 8)}")
print()
master_secret = tls12_prf_sha256(premaster, "master secret", kex.clientrandom + kex.serverrandom, 48)
print("=== PRF-derived master secret [48] ===")
print(master_secret)
print()
derived_material = tls12_prf_sha256(master_secret, "key expansion", kex.serverrandom + kex.clientrandom, 136)
client_mac_key = derived_material[0:20]
server_mac_key = derived_material[20:40]
client_aes_key = derived_material[40:72]
server_aes_key = derived_material[72:104]
client_aes_iv = derived_material[104:120]
server_aes_iv = derived_material[120:136]
print("=== PRF-derived expansion parts ===")
print(f" [20] client MAC key: {hexlify(client_mac_key)}")
print(f" [32] client AES key: {hexlify(client_aes_key)}")
print(f" [16] client AES IV : {hexlify(client_aes_iv)}")
print(f" [20] server MAC key: {hexlify(server_mac_key)}")
print(f" [32] server AES key: {hexlify(server_aes_key)}")
print(f" [16] server AES IV : {hexlify(server_aes_iv)}")
print()
hello_tls = aes256cbc_decrypt(client_aes_key, client_aes_iv, kex.handshake_finished)
print("--- Handshake Finished block ---")
print(f" encrypted: {tohex_abbrev(kex.handshake_finished, 8)}")
print(f" decrypted: {tohex(hello_tls)}")
print(" (%d bytes)" % len(hello_tls))
if len(hello_tls) == 64:
print(f" ?? {tohex(hello_tls[ 0:16])}")
print(f" {tohex(hello_tls[16:32])}")
print(f" hmac {tohex(hello_tls[32:52])}")
print(f" pad {tohex(hello_tls[52:64])}")
print()
def main():
blog_test()
run_single(TEST1)
#-- output:
# d1ca1243cbadb256f3a3b95f144ad790 1400000c4ff05a3d6aed00845104a34 bab033f4518b1de38d91a65b828f3132ed6378aef 0b0b0b0b0b0b0b0b0b0b0b0b
run_single(TEST2)
#-- output:
# 68363641725ccdc27639f618a92460bb 1400000cc242cd2d2d2dd91445e2b98 f418033cf277c97110ffb984b17f3e6e74da5318e 0b0b0b0b0b0b0b0b0b0b0b0b
if __name__ == "__main__":
import sys
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment