def encode(self): """Create a binary representation of JoinAcceptMessage object. Returns: Packed JoinAccept message. """ # Encoding Join-accept: # MAC Header # 3 bytes appnonce # 3 bytes netid # 4 bytes devaddr # 1 byte dlsettings # 1 byte rxdelay # Optional cflist # Create the message header = self.mhdr.encode() msg = intPackBytes(self.appnonce, 3, endian='little') + \ intPackBytes(self.netid, 3, endian='little') + \ struct.pack('<L', self.devaddr) + \ struct.pack('B', self.dlsettings) + \ struct.pack('B', self.rxdelay) # CFList is not used in a Join Accept message for US/AU bands if self.cflist: pass # Create the MIC over the entire message self.mic = aesEncrypt(intPackBytes(self.appkey, 16), header + msg, mode='CMAC')[0:4] msg += self.mic # Add the header and encrypt the message using AES-128 decrypt data = header + aesDecrypt(intPackBytes(self.appkey, 16), msg) return data
def encrypt(self, key, dir): """Encrypt FRMPayload The algorithm defines a sequence of Blocks Ai for i = 1..k with k = ceil(len(pld) / 16): Ai: [0x01 | 4 x 0x00 | dir | devaddr | Fcntup or FcntDown | 0x00 | i] dir is 0 for uplink and 1 for downlink The blocks Ai are encrypted to get a sequence S of blocks Si: Si = aes128_encrypt(K, Ai) for i = 1..k Encryption and decryption of the payload is done by truncating (pld | pad16) xor S to the first len(pld) octets. i.e. pad pld to a 16 byte boundary, then xor with S, and truncate to the original length. Args: key (int): AES encryption key - device NwkSKey or AppSkey dir (int): Direction - 0 for uplink and 1 for downlink """ if self.payload.frmpayload == None: return plen = len(self.payload.frmpayload) if plen == 0: return k = int(math.ceil(plen/16.0)) # Create the concatenated block S S = '' for i in range(k): # Ai: [0x01 | 4 x 0x00 | dir | devaddr | Fcntup or FcntDown | 0x00 | i] Ai = struct.pack('<BLBLLBB', 1, 0, dir, self.payload.fhdr.devaddr, self.payload.fhdr.fcnt, 0, i+1) # Si = aes128_encrypt(K, Ai) S += aesEncrypt(intPackBytes(key, 16), Ai) # Pad frmpayload to a byte multiple of 16 padlen = k * 16 - plen padded = self.payload.frmpayload + intPackBytes(0, padlen) # Unpack S and padded payload into arrays of long long ints ufmt = '{}Q'.format(k*2) s = struct.unpack(ufmt, S) p = struct.unpack(ufmt, padded) # Perform the XOR function over the data, and pack pld = '' for i in range (len(s)): pld += struct.pack('Q', s[i] ^ p[i]) # Truncate the result to the original length self.payload.frmpayload = pld[:plen]
def test_intPackBytes(self, ): k = int('0x017E151638AEC2A6ABF7258809CF4F3C', 16) length = 16 expected = '\x01~\x15\x168\xae\xc2\xa6\xab\xf7%\x88\t\xcfO<' result = util.intPackBytes(k, length) self.assertEqual(expected, result)
def _createSessionKey(self, pre, app, msg): """Create a NwkSKey or AppSKey Creates the session keys NwkSKey and AppSKey specific for an end-device to encrypt and verify network communication and application data. Args: pre (int): 0x01 ofr NwkSKey, 0x02 for AppSKey app (Application): The applicaiton object. msg (JoinRequestMessage): The MAC Join Request message. Returns: int: 128 bit session key """ # Session key data: 0x0n | appnonce | netid | devnonce | pad (16) data = struct.pack('B', pre) + \ intPackBytes(app.appnonce, 3, endian='little') + \ intPackBytes(self.config.netid, 3, endian='little') + \ struct.pack('<H', msg.devnonce) + intPackBytes(0, 7) aesdata = aesEncrypt(intPackBytes(app.appkey, 16), data) key = intUnpackBytes(aesdata) return key
def checkMIC(self, appkey): """Verify the message integrity code (MIC). The MIC is calculated over the binary join request message excluding the MIC. Use the first four bytes of AES CMAC encrypted data, convert from little endian data to int. Args: appkey (int): The application key. Returns: True on success, False otherwise. """ data = self.mhdr.encode() + struct.pack('<QQH', self.appeui, self.deveui, self.devnonce) aesdata = aesEncrypt(intPackBytes(appkey, 16), data, mode='CMAC') mic = struct.unpack('<L', aesdata[:4])[0] return mic == self.mic
def checkMIC(self, key): """Check the message integrity code Args: key (int): NwkSkey Returns: True on success, False otherwise """ # Calculate the MIC for this message using key msg = self.mhdr.encode() + self.payload.encode() B0 = struct.pack('<BLBLLBB', int('0x49', 16), 0, 0, self.payload.fhdr.devaddr, self.payload.fhdr.fcnt, 0, len(msg)) data = B0 + msg aesdata = aesEncrypt(intPackBytes(key, 16), data, mode='CMAC') mic = struct.unpack('<L', aesdata[:4])[0] # Compare to message MIC return mic == self.mic
def encode(self): """Create a binary representation of MACMessage object. Returns: String of packed data. """ # Calculate the MIC. # The MIC is calculated as cmac = aes128_cmac(NwkSKey, B0 | msg) # MIC = cmac[0:3] # msg is defined as: MHDR | FHDR | FPort | FRMPayload # B0 is defined as: # 1 byte 0x49 | 4 bytes 0x00 | 1 byte dir=0 for uplink, 1 for downlink # 4 bytes devaddr | 4 bytes fcntup or fcntdown # 1 byte 0x00 | 1 bytes len msg = self.mhdr.encode() + self.payload.encode() B0 = struct.pack('<BLBLLBB', int('0x49', 16), 0, 1, self.devaddr, self.payload.fhdr.fcnt, 0, len(msg)) data = B0 + msg # Create the MIC over the entire message self.mic = aesEncrypt(intPackBytes(self.key, 16), data, mode='CMAC')[0:4] msg += self.mic return msg