xref: /btstack/test/mesh/mesh_crypto.py (revision cd5f23a3250874824c01a2b3326a9522fea3f99f)
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