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