mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-01-03 23:47:08 +00:00
121 lines
4.0 KiB
Python
Executable File
121 lines
4.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# BlueKitchen GmbH (c) 2019
|
|
|
|
# pip3 install pycryptodomex
|
|
|
|
# implementation of the Bluetooth SIG Mesh crypto functions using pycryptodomex
|
|
|
|
try:
|
|
from Cryptodome.Cipher import AES
|
|
from Cryptodome.Hash import CMAC
|
|
except ImportError:
|
|
# fallback: try to import PyCryptodome as (an almost drop-in) replacement for the PyCrypto library
|
|
try:
|
|
from Crypto.Cipher import AES
|
|
from Crypto.Hash import CMAC
|
|
except ImportError:
|
|
print("\n[!] PyCryptodome required but not installed (using random value instead)")
|
|
print("[!] Please install PyCryptodome, e.g. 'pip3 install pycryptodomex' or 'pip3 install pycryptodome'\n")
|
|
|
|
def aes128(key, n):
|
|
cipher = AES.new(key, AES.MODE_ECB)
|
|
ciphertext = cipher.encrypt(n)
|
|
return ciphertext
|
|
|
|
def aes_cmac(key, n):
|
|
cobj = CMAC.new(key, ciphermod=AES)
|
|
cobj.update(n)
|
|
return cobj.digest()
|
|
|
|
def aes_ccm_encrypt(key, nonce, message, additional_data, mac_len):
|
|
cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=mac_len)
|
|
cipher.update(additional_data)
|
|
ciphertext, tag = cipher.encrypt_and_digest(message)
|
|
return ciphertext, tag
|
|
|
|
def aes_ccm_decrypt(key, nonce, message, additional_data, mac_len, mac_tag):
|
|
cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=mac_len)
|
|
cipher.update(additional_data)
|
|
try:
|
|
ciphertext = cipher.decrypt_and_verify(message, mac_tag)
|
|
return ciphertext
|
|
except ValueError:
|
|
return None
|
|
|
|
def s1(m):
|
|
# s1(M) = AES-CMACZERO (M)
|
|
zero_key = bytes(16)
|
|
return aes_cmac(zero_key, m)
|
|
|
|
def k1(n, salt, p):
|
|
# T = AES-CMACSALT (N)
|
|
t = aes_cmac(salt, n)
|
|
# k1(N, SALT, P) = AES-CMACT (P)
|
|
return aes_cmac(t, p)
|
|
|
|
def k2(n, p):
|
|
# SALT = s1(“smk2”)
|
|
salt = s1(b'smk2')
|
|
# T = AES-CMACSALT (N)
|
|
t = aes_cmac(salt, n)
|
|
# T0 = empty string (zero length)
|
|
t0 = b''
|
|
# T1 = AES-CMACT (T0 || P || 0x01)
|
|
t1 = aes_cmac(t, t0 + p + b'\x01')
|
|
# T2 = AES-CMACT (T1 || P || 0x02)
|
|
t2 = aes_cmac(t, t1 + p + b'\x02')
|
|
# T3 = AES-CMACT (T2 || P || 0x03)
|
|
t3 = aes_cmac(t, t2 + p + b'\x03')
|
|
nid = t1[15] & 0x7f
|
|
encryption_key = t2
|
|
privacy_key = t3
|
|
return (nid, encryption_key, privacy_key)
|
|
|
|
def k3(n):
|
|
# SALT = s1(“smk3”)
|
|
salt = s1(b'smk3')
|
|
# T = AES-CMACSALT (N)
|
|
t = aes_cmac(salt, n)
|
|
return aes_cmac(t, b'id64' + b'\x01')[8:]
|
|
|
|
def k4(n):
|
|
# SALT = s1(“smk4”)
|
|
salt = s1(b'smk4')
|
|
# T = AES-CMACSALT (N)
|
|
t = aes_cmac(salt, n)
|
|
return aes_cmac(t, b'id6' + b'\x01')[15] & 0x3f
|
|
|
|
def network_pecb(privacy_random, iv_index, privacy_key):
|
|
privacy_plaintext = bytes(5) + iv_index + privacy_random
|
|
return aes128(privacy_key, privacy_plaintext)[0:6]
|
|
|
|
def network_decrypt(network_pdu, iv_index, encryption_key, privacy_key):
|
|
privacy_random = network_pdu[7:14]
|
|
pecb = network_pecb(privacy_random, iv_index, privacy_key)
|
|
deobfuscated = bytes([(a ^ b) for (a,b) in zip(pecb, network_pdu[1:7])])
|
|
if deobfuscated[0] & 0x80:
|
|
net_mic_len = 8
|
|
else:
|
|
net_mic_len = 4
|
|
nonce = bytes(1) + deobfuscated + bytes(2) + iv_index
|
|
ciphertext = network_pdu[7:-net_mic_len]
|
|
net_mic = network_pdu[-net_mic_len:]
|
|
decrypted = aes_ccm_decrypt(encryption_key, nonce, ciphertext, b'', net_mic_len, net_mic)
|
|
if decrypted == None:
|
|
return None
|
|
return network_pdu[0:1] + deobfuscated + decrypted
|
|
|
|
def network_encrypt(network_pdu, iv_index, encryption_key, privacy_key):
|
|
nonce = bytes(1) + network_pdu[1:7] + bytes(2) + iv_index
|
|
if network_pdu[1] & 0x80:
|
|
net_mic_len = 8
|
|
else:
|
|
net_mic_len = 4
|
|
plaintext = network_pdu[7:]
|
|
(ciphertext, net_mic) = aes_ccm_encrypt(encryption_key, nonce, plaintext, b'', net_mic_len)
|
|
ciphertext_and_mic = ciphertext + net_mic
|
|
privacy_random = ciphertext_and_mic[0:7]
|
|
pecb = network_pecb(privacy_random, iv_index, privacy_key)
|
|
obfuscated = bytes([(a ^ b) for (a,b) in zip(pecb, network_pdu[1:7])])
|
|
return network_pdu[0:1] + obfuscated + ciphertext_and_mic
|