1#!/usr/bin/env python3 2# BlueKitchen GmbH (c) 2019 3 4# pip3 install pycryptodomex 5 6# implementation of the Bluetooth SIG Mesh crypto functions using pycryptodomex 7 8from Cryptodome.Cipher import AES 9from Cryptodome.Hash import CMAC 10 11def aes128(key, n): 12 cipher = AES.new(key, AES.MODE_ECB) 13 ciphertext = cipher.encrypt(n) 14 return ciphertext 15 16def aes_cmac(key, n): 17 cobj = CMAC.new(key, ciphermod=AES) 18 cobj.update(n) 19 return cobj.digest() 20 21def aes_ccm_encrypt(key, nonce, message, additional_data, mac_len): 22 cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=mac_len) 23 cipher.update(additional_data) 24 ciphertext, tag = cipher.encrypt_and_digest(message) 25 return ciphertext, tag 26 27def aes_ccm_decrypt(key, nonce, message, additional_data, mac_len, mac_tag): 28 cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=mac_len) 29 cipher.update(additional_data) 30 try: 31 ciphertext = cipher.decrypt_and_verify(message, mac_tag) 32 return ciphertext 33 except ValueError: 34 return None 35 36def s1(m): 37 # s1(M) = AES-CMACZERO (M) 38 zero_key = bytes(16) 39 return aes_cmac(zero_key, m) 40 41def k1(n, salt, p): 42 # T = AES-CMACSALT (N) 43 t = aes_cmac(salt, n) 44 # k1(N, SALT, P) = AES-CMACT (P) 45 return aes_cmac(t, p) 46 47def k2(n, p): 48 # SALT = s1(“smk2”) 49 salt = s1(b'smk2') 50 # T = AES-CMACSALT (N) 51 t = aes_cmac(salt, n) 52 # T0 = empty string (zero length) 53 t0 = b'' 54 # T1 = AES-CMACT (T0 || P || 0x01) 55 t1 = aes_cmac(t, t0 + p + b'\x01') 56 # T2 = AES-CMACT (T1 || P || 0x02) 57 t2 = aes_cmac(t, t1 + p + b'\x02') 58 # T3 = AES-CMACT (T2 || P || 0x03) 59 t3 = aes_cmac(t, t2 + p + b'\x03') 60 nid = t1[15] & 0x7f 61 encryption_key = t2 62 privacy_key = t3 63 return (nid, encryption_key, privacy_key) 64 65def k3(n): 66 # SALT = s1(“smk3”) 67 salt = s1(b'smk3') 68 # T = AES-CMACSALT (N) 69 t = aes_cmac(salt, n) 70 return aes_cmac(t, b'id64' + b'\x01')[8:] 71 72def k4(n): 73 # SALT = s1(“smk4”) 74 salt = s1(b'smk4') 75 # T = AES-CMACSALT (N) 76 t = aes_cmac(salt, n) 77 return aes_cmac(t, b'id6' + b'\x01')[15] & 0x3f 78 79def network_pecb(privacy_random, iv_index, privacy_key): 80 privacy_plaintext = bytes(5) + iv_index + privacy_random 81 return aes128(privacy_key, privacy_plaintext)[0:6] 82 83def network_decrypt(network_pdu, iv_index, encryption_key, privacy_key): 84 privacy_random = network_pdu[7:14] 85 pecb = network_pecb(privacy_random, iv_index, privacy_key) 86 deobfuscated = bytes([(a ^ b) for (a,b) in zip(pecb, network_pdu[1:7])]) 87 if deobfuscated[0] & 0x80: 88 net_mic_len = 8 89 else: 90 net_mic_len = 4 91 nonce = bytes(1) + deobfuscated + bytes(2) + iv_index 92 ciphertext = network_pdu[7:-net_mic_len] 93 net_mic = network_pdu[-net_mic_len:] 94 decrypted = aes_ccm_decrypt(encryption_key, nonce, ciphertext, b'', net_mic_len, net_mic) 95 if decrypted == None: 96 return None 97 return network_pdu[0:1] + deobfuscated + decrypted 98 99def network_encrypt(network_pdu, iv_index, encryption_key, privacy_key): 100 nonce = bytes(1) + network_pdu[1:7] + bytes(2) + iv_index 101 if network_pdu[1] & 0x80: 102 net_mic_len = 8 103 else: 104 net_mic_len = 4 105 plaintext = network_pdu[7:] 106 (ciphertext, net_mic) = aes_ccm_encrypt(encryption_key, nonce, plaintext, b'', net_mic_len) 107 ciphertext_and_mic = ciphertext + net_mic 108 privacy_random = ciphertext_and_mic[0:7] 109 pecb = network_pecb(privacy_random, iv_index, privacy_key) 110 obfuscated = bytes([(a ^ b) for (a,b) in zip(pecb, network_pdu[1:7])]) 111 return network_pdu[0:1] + obfuscated + ciphertext_and_mic 112