From 1d0ba3a50937e585ebce27d37145291505fe56a8 Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Fri, 18 Oct 2019 11:51:20 +0200 Subject: [PATCH] test/mesh: mesh security toolbox in python --- test/mesh/mesh_crypto.py | 73 +++++++++++++++++++++++++++++++ test/mesh/mesh_message_test.py | 80 ++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100755 test/mesh/mesh_crypto.py create mode 100755 test/mesh/mesh_message_test.py diff --git a/test/mesh/mesh_crypto.py b/test/mesh/mesh_crypto.py new file mode 100755 index 000000000..ef641426b --- /dev/null +++ b/test/mesh/mesh_crypto.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# BlueKitchen GmbH (c) 2019 + +# pip3 install pycryptodomex + +# implementation of the Bluetooth SIG Mesh crypto functions using pycryptodomex + +from Cryptodome.Cipher import AES +from Cryptodome.Hash import CMAC + +def aes_cmac(k, n): + cobj = CMAC.new(k, 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 + diff --git a/test/mesh/mesh_message_test.py b/test/mesh/mesh_message_test.py new file mode 100755 index 000000000..96e08f85d --- /dev/null +++ b/test/mesh/mesh_message_test.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# BlueKitchen GmbH (c) 2019 + +# pip3 install pycryptodomex + +from mesh_crypto import * + +# S1('test') = b73cefbd641ef2ea598c2b6efb62f79c +s1_input = b'test' +s1_actual = s1(s1_input) + +s1_expected = bytes.fromhex('b73cefbd641ef2ea598c2b6efb62f79c') +if s1_actual != s1_expected: + print("s1: expected " + s1_expected.hex + ", but got " + s1_actual.hex()) + +# K1(3216d1509884b533248541792b877f98, 2ba14ffa0df84a2831938d57d276cab4, 5a09d60797eeb4478aada59db3352a0d) = f6ed15a8934afbe7d83e8dcb57fcf5d7 +k1_n = bytes.fromhex("3216d1509884b533248541792b877f98") +k1_salt = bytes.fromhex("2ba14ffa0df84a2831938d57d276cab4") +k1_p = bytes.fromhex("5a09d60797eeb4478aada59db3352a0d") +k1_actual = k1(k1_n, k1_salt, k1_p) + +k1_expected = bytes.fromhex("f6ed15a8934afbe7d83e8dcb57fcf5d7") +if k1_actual != k1_expected: + print("k1: expected " + k1_expected.hex() + ", but got " + k1_actual.hex()) + +# k2 test +k2_n = bytes.fromhex('7dd7364cd842ad18c17c2b820c84c3d6') +k2_p = bytes.fromhex('00') +(k2_nid, k2_encryption_key, k2_privacy_key) = k2(k2_n, k2_p) + +k2_nid_expected = 0x68 +k2_encryption_key_expected = bytes.fromhex('0953fa93e7caac9638f58820220a398e') +k2_privacy_key_expected = bytes.fromhex('8b84eedec100067d670971dd2aa700cf') +if k2_nid_expected != k2_nid: + print("k2: nid expected " + hex(k2_nid_expected) + ", but got " + hex(k2_nid)) +if k2_encryption_key_expected != k2_encryption_key: + print("k2: encryption key expected " + k2_encryption_key_expected.hex() + ", but got " + k2_encryption_key.hex()) +if k2_privacy_key_expected != k2_privacy_key: + print("k2: privacy key expected " + k2_privacy_key_expected.hex() + ", but got " + k2_privacy_key.hex()) + +# k3 test +k3_n = bytes.fromhex('f7a2a44f8e8a8029064f173ddc1e2b00') +k3_actual = k3(k3_n) + +k3_expected = bytes.fromhex('ff046958233db014') +if k3_actual != k3_expected: + print("k3: expected " + k3_expected.hex() + ", but got " + k3_actual.hex()) + +# k4 test +k4_n = bytes.fromhex('3216d1509884b533248541792b877f98') +k4_actual = k4(k4_n) + +k4_expected = 0x38 +if k4_actual != k4_expected: + print("k4: expected " + hex(k4_expected) + ", but got " + hex(k4_actual)) + +# aes-ccm-encrypt test +message_1_network_nonce = bytes.fromhex('00800000011201000012345678') +message_1_network_plaintext = bytes.fromhex('fffd' + '034b50057e400000010000') +message_1_encryption_key = bytes.fromhex('0953fa93e7caac9638f58820220a398e') +(message_1_ciphertext, message_1_mic) = aes_ccm_encrypt(message_1_encryption_key, message_1_network_nonce, message_1_network_plaintext, b'', 8) + +message_1_ciphertext_expected = bytes.fromhex('b5e5bfdacbaf6cb7fb6bff871f') +message_1_mic_expected = bytes.fromhex('035444ce83a670df') +if message_1_ciphertext_expected != message_1_ciphertext: + print("aes_ccm_encrypt: ciphertext expected " + hex(message_1_ciphertext_expected) + ", but got " + hex(message_1_ciphertext)) +if message_1_mic_expected != message_1_mic: + print("aes_ccm_encrypt: encryption key expected " + message_1_mic_expected.hex() + ", but got " + message_1_mic.hex()) + +# aes-ccm-decrypt test +message_1_network_nonce = bytes.fromhex('00800000011201000012345678') +message_1_ciphertext_expected = bytes.fromhex('b5e5bfdacbaf6cb7fb6bff871f') +message_1_mic_expected = bytes.fromhex('035444ce83a670df') +message_1_plaintext_decrypted = aes_ccm_decrypt(message_1_encryption_key, message_1_network_nonce, message_1_ciphertext_expected, b'', 8, message_1_mic_expected) + +message_1_network_plaintext = bytes.fromhex('fffd' + '034b50057e400000010000') +if message_1_plaintext_decrypted == None: + print("aes_ccm_decrypt: digest validation failed") +elif message_1_network_plaintext != message_1_plaintext_decrypted: + print("aes_ccm: plaintext expected " + hex(message_1_network_plaintext) + ", but got " + hex(message_1_plaintext_decrypted))