-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
It seems sample input data provided in the spec is big-endian (just like other AES-CMAC-based functions), but all keys are in little-endian( HCI standard), so they need to be reverse before and after applying AES-CMAC.
- Loading branch information
Showing
4 changed files
with
210 additions
and
181 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ | |
"deregistration", | ||
"dhkey", | ||
"diversifier", | ||
"endianness", | ||
"Fitbit", | ||
"GATTLINK", | ||
"HANDSFREE", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,8 @@ | |
# ----------------------------------------------------------------------------- | ||
# Imports | ||
# ----------------------------------------------------------------------------- | ||
from __future__ import annotations | ||
|
||
import logging | ||
import operator | ||
|
||
|
@@ -29,11 +31,13 @@ | |
from cryptography.hazmat.primitives.asymmetric.ec import ( | ||
generate_private_key, | ||
ECDH, | ||
EllipticCurvePrivateKey, | ||
EllipticCurvePublicNumbers, | ||
EllipticCurvePrivateNumbers, | ||
SECP256R1, | ||
) | ||
from cryptography.hazmat.primitives import cmac | ||
from typing import Tuple | ||
|
||
|
||
# ----------------------------------------------------------------------------- | ||
|
@@ -46,16 +50,18 @@ | |
# Classes | ||
# ----------------------------------------------------------------------------- | ||
class EccKey: | ||
def __init__(self, private_key): | ||
def __init__(self, private_key: EllipticCurvePrivateKey) -> None: | ||
self.private_key = private_key | ||
|
||
@classmethod | ||
def generate(cls): | ||
def generate(cls) -> EccKey: | ||
private_key = generate_private_key(SECP256R1()) | ||
return cls(private_key) | ||
|
||
@classmethod | ||
def from_private_key_bytes(cls, d_bytes, x_bytes, y_bytes): | ||
def from_private_key_bytes( | ||
cls, d_bytes: bytes, x_bytes: bytes, y_bytes: bytes | ||
) -> EccKey: | ||
d = int.from_bytes(d_bytes, byteorder='big', signed=False) | ||
x = int.from_bytes(x_bytes, byteorder='big', signed=False) | ||
y = int.from_bytes(y_bytes, byteorder='big', signed=False) | ||
|
@@ -65,22 +71,22 @@ def from_private_key_bytes(cls, d_bytes, x_bytes, y_bytes): | |
return cls(private_key) | ||
|
||
@property | ||
def x(self): | ||
def x(self) -> bytes: | ||
return ( | ||
self.private_key.public_key() | ||
.public_numbers() | ||
.x.to_bytes(32, byteorder='big') | ||
) | ||
|
||
@property | ||
def y(self): | ||
def y(self) -> bytes: | ||
return ( | ||
self.private_key.public_key() | ||
.public_numbers() | ||
.y.to_bytes(32, byteorder='big') | ||
) | ||
|
||
def dh(self, public_key_x, public_key_y): | ||
def dh(self, public_key_x: bytes, public_key_y: bytes) -> bytes: | ||
x = int.from_bytes(public_key_x, byteorder='big', signed=False) | ||
y = int.from_bytes(public_key_y, byteorder='big', signed=False) | ||
public_key = EllipticCurvePublicNumbers(x, y, SECP256R1()).public_key() | ||
|
@@ -93,35 +99,44 @@ def dh(self, public_key_x, public_key_y): | |
# Functions | ||
# ----------------------------------------------------------------------------- | ||
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def xor(x, y): | ||
def xor(x: bytes, y: bytes) -> bytes: | ||
assert len(x) == len(y) | ||
return bytes(map(operator.xor, x, y)) | ||
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def r(): | ||
def reverse(input: bytes) -> bytes: | ||
''' | ||
Returns bytes of input in reversed endianness. | ||
''' | ||
return input[::-1] | ||
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def r() -> bytes: | ||
''' | ||
Generate 16 bytes of random data | ||
''' | ||
return secrets.token_bytes(16) | ||
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def e(key, data): | ||
def e(key: bytes, data: bytes) -> bytes: | ||
''' | ||
AES-128 ECB, expecting byte-swapped inputs and producing a byte-swapped output. | ||
See Bluetooth spec Vol 3, Part H - 2.2.1 Security function e | ||
''' | ||
|
||
cipher = Cipher(algorithms.AES(bytes(reversed(key))), modes.ECB()) | ||
cipher = Cipher(algorithms.AES(reverse(key)), modes.ECB()) | ||
encryptor = cipher.encryptor() | ||
return bytes(reversed(encryptor.update(bytes(reversed(data))))) | ||
return reverse(encryptor.update(reverse(data))) | ||
Check failure Code scanning / CodeQL Use of a broken or weak cryptographic algorithm High
The block mode ECB is broken or weak, and should not be used.
|
||
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def ah(k, r): # pylint: disable=redefined-outer-name | ||
def ah(k: bytes, r: bytes) -> bytes: # pylint: disable=redefined-outer-name | ||
''' | ||
See Bluetooth spec Vol 3, Part H - 2.2.2 Random Address Hash function ah | ||
''' | ||
|
@@ -132,7 +147,16 @@ def ah(k, r): # pylint: disable=redefined-outer-name | |
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def c1(k, r, preq, pres, iat, rat, ia, ra): # pylint: disable=redefined-outer-name | ||
def c1( | ||
k: bytes, | ||
r: bytes, | ||
preq: bytes, | ||
pres: bytes, | ||
iat: int, | ||
rat: int, | ||
ia: bytes, | ||
ra: bytes, | ||
) -> bytes: # pylint: disable=redefined-outer-name | ||
''' | ||
See Bluetooth spec, Vol 3, Part H - 2.2.3 Confirm value generation function c1 for | ||
LE Legacy Pairing | ||
|
@@ -144,7 +168,7 @@ def c1(k, r, preq, pres, iat, rat, ia, ra): # pylint: disable=redefined-outer-n | |
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def s1(k, r1, r2): | ||
def s1(k: bytes, r1: bytes, r2: bytes) -> bytes: | ||
''' | ||
See Bluetooth spec, Vol 3, Part H - 2.2.4 Key generation function s1 for LE Legacy | ||
Pairing | ||
|
@@ -154,7 +178,7 @@ def s1(k, r1, r2): | |
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def aes_cmac(m, k): | ||
def aes_cmac(m: bytes, k: bytes) -> bytes: | ||
''' | ||
See Bluetooth spec, Vol 3, Part H - 2.2.5 FunctionAES-CMAC | ||
|
@@ -166,108 +190,100 @@ def aes_cmac(m, k): | |
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def f4(u, v, x, z): | ||
def f4(u: bytes, v: bytes, x: bytes, z: bytes) -> bytes: | ||
''' | ||
See Bluetooth spec, Vol 3, Part H - 2.2.6 LE Secure Connections Confirm Value | ||
Generation Function f4 | ||
''' | ||
return bytes( | ||
reversed( | ||
aes_cmac(bytes(reversed(u)) + bytes(reversed(v)) + z, bytes(reversed(x))) | ||
) | ||
) | ||
return reverse(aes_cmac(reverse(u) + reverse(v) + z, reverse(x))) | ||
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def f5(w, n1, n2, a1, a2): | ||
def f5(w: bytes, n1: bytes, n2: bytes, a1: bytes, a2: bytes) -> Tuple[bytes, bytes]: | ||
''' | ||
See Bluetooth spec, Vol 3, Part H - 2.2.7 LE Secure Connections Key Generation | ||
Function f5 | ||
NOTE: this returns a tuple: (MacKey, LTK) in little-endian byte order | ||
''' | ||
salt = bytes.fromhex('6C888391AAF5A53860370BDB5A6083BE') | ||
t = aes_cmac(bytes(reversed(w)), salt) | ||
t = aes_cmac(reverse(w), salt) | ||
key_id = bytes([0x62, 0x74, 0x6C, 0x65]) | ||
return ( | ||
bytes( | ||
reversed( | ||
aes_cmac( | ||
bytes([0]) | ||
+ key_id | ||
+ bytes(reversed(n1)) | ||
+ bytes(reversed(n2)) | ||
+ bytes(reversed(a1)) | ||
+ bytes(reversed(a2)) | ||
+ bytes([1, 0]), | ||
t, | ||
) | ||
reverse( | ||
aes_cmac( | ||
bytes([0]) | ||
+ key_id | ||
+ reverse(n1) | ||
+ reverse(n2) | ||
+ reverse(a1) | ||
+ reverse(a2) | ||
+ bytes([1, 0]), | ||
t, | ||
) | ||
), | ||
bytes( | ||
reversed( | ||
aes_cmac( | ||
bytes([1]) | ||
+ key_id | ||
+ bytes(reversed(n1)) | ||
+ bytes(reversed(n2)) | ||
+ bytes(reversed(a1)) | ||
+ bytes(reversed(a2)) | ||
+ bytes([1, 0]), | ||
t, | ||
) | ||
reverse( | ||
aes_cmac( | ||
bytes([1]) | ||
+ key_id | ||
+ reverse(n1) | ||
+ reverse(n2) | ||
+ reverse(a1) | ||
+ reverse(a2) | ||
+ bytes([1, 0]), | ||
t, | ||
) | ||
), | ||
) | ||
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def f6(w, n1, n2, r, io_cap, a1, a2): # pylint: disable=redefined-outer-name | ||
def f6( | ||
w: bytes, n1: bytes, n2: bytes, r: bytes, io_cap: bytes, a1: bytes, a2: bytes | ||
) -> bytes: # pylint: disable=redefined-outer-name | ||
''' | ||
See Bluetooth spec, Vol 3, Part H - 2.2.8 LE Secure Connections Check Value | ||
Generation Function f6 | ||
''' | ||
return bytes( | ||
reversed( | ||
aes_cmac( | ||
bytes(reversed(n1)) | ||
+ bytes(reversed(n2)) | ||
+ bytes(reversed(r)) | ||
+ bytes(reversed(io_cap)) | ||
+ bytes(reversed(a1)) | ||
+ bytes(reversed(a2)), | ||
bytes(reversed(w)), | ||
) | ||
return reverse( | ||
aes_cmac( | ||
reverse(n1) | ||
+ reverse(n2) | ||
+ reverse(r) | ||
+ reverse(io_cap) | ||
+ reverse(a1) | ||
+ reverse(a2), | ||
reverse(w), | ||
) | ||
) | ||
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def g2(u, v, x, y): | ||
def g2(u: bytes, v: bytes, x: bytes, y: bytes) -> int: | ||
''' | ||
See Bluetooth spec, Vol 3, Part H - 2.2.9 LE Secure Connections Numeric Comparison | ||
Value Generation Function g2 | ||
''' | ||
return int.from_bytes( | ||
aes_cmac( | ||
bytes(reversed(u)) + bytes(reversed(v)) + bytes(reversed(y)), | ||
bytes(reversed(x)), | ||
reverse(u) + reverse(v) + reverse(y), | ||
reverse(x), | ||
)[-4:], | ||
byteorder='big', | ||
) | ||
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def h6(w, key_id): | ||
def h6(w: bytes, key_id: bytes) -> bytes: | ||
''' | ||
See Bluetooth spec, Vol 3, Part H - 2.2.10 Link key conversion function h6 | ||
''' | ||
return aes_cmac(key_id, w) | ||
return reverse(aes_cmac(key_id, reverse(w))) | ||
|
||
|
||
# ----------------------------------------------------------------------------- | ||
def h7(salt, w): | ||
def h7(salt: bytes, w: bytes) -> bytes: | ||
''' | ||
See Bluetooth spec, Vol 3, Part H - 2.2.11 Link key conversion function h7 | ||
''' | ||
return aes_cmac(w, salt) | ||
return reverse(aes_cmac(reverse(w), salt)) |
Oops, something went wrong.