def validateKeys(jr, ja, keysPath, dont_generate_keys): keys = list() with open(keysPath) as f: for k in f: # Fetch keys in byte format. Needed by ctypes keys.append(bytes(k.rstrip().upper(), encoding='utf-8')) jr_decoded = base64.b64decode(jr) jr_m_type = jr_decoded[0] & 0xE0 ja_decoded = base64.b64decode(ja) ja_m_type = ja_decoded[0] & 0xE0 if jr_m_type != 0x00 or ja_m_type != 0x20: print("\nMake sure you provided a valid JoinRequest and JoinAccept \n") return jr_result = LorawanWrapper.testAppKeysWithJoinRequest( keys, jr, dont_generate_keys) if len(jr_result) > 0: # Convert valid keys into bytes valid_keys = list() for valid_key in jr_result.split(): valid_keys.append(bytes(valid_key.upper(), encoding='utf-8')) ja_result = LorawanWrapper.testAppKeysWithJoinAccept( valid_keys, ja, dontGenerateKeys=True) if len(ja_result) > 0: print("\n**** Key found: %s **** \n" % (ja_result.split()[0]))
def deriveSessionKeys(device_auth_obj, appKey): json_result = LorawanWrapper.generateSessionKeysFromJoins( device_auth_obj.join_request, device_auth_obj.join_accept, appKey) keys = json.loads(json_result) device_auth_obj.apps_key = keys["appSKey"] device_auth_obj.nwks_key = keys["nwkSKey"] return device_auth_obj
def parseJSONtoPHY(json, key, nwkskey): if json is None: print ("JSON not specified. Exiting.") return b64 = LorawanWrapper.marshalJsonToPHYPayload(json, key, nwkskey) print ("PHYPayload is %s \n"%(b64))
def generate_mic(message, key): if key is not None: search = re.search(b'(.*)"data"\s?:\s?"(.*?)"(.*)', message) message = search.group( 1) + b'"data":"' + LorawanWrapper.generateValidMIC( search.group(2), key) + b'"' + search.group(3) return message
def bruteforce_accept_request(ja, jr, dont_generate_keys): global keys jr_result = LorawanWrapper.testAppKeysWithJoinRequest( keys, jr, dont_generate_keys) if len(jr_result) > 0: # Convert valid keys into bytes valid_keys = list() for valid_key in jr_result.split(): valid_keys.append(bytes(valid_key.upper(), encoding='utf-8')) ja_result = LorawanWrapper.testAppKeysWithJoinAccept( valid_keys, ja, dontGenerateKeys=True) if len(ja_result) > 0: print("\n**** Key found: %s **** \n" % (ja_result.split()[0]))
def formatData(data): result = "" if data: search = re.search('(.*)"data":"(.*?)"(.*)', data.decode('utf-8', 'backslashreplace')) if search: global key phyPayload = LorawanWrapper.printPHYPayload(search.group(2), key) result = "\nParsed data: " + phyPayload return result
def extractMIC(b64_packet): stringPHY = LorawanWrapper.printPHYPayload(b64_packet) jsonPHY = json.loads(stringPHY) return jsonPHY['mic'] # JoinReq: AppEUI - DevEUI - DevNonce # JoinAccept: AppNonce - NetID - DevAddr # NwkSKey = aes128_encrypt(AppKey, 0x01 | AppNonce | NetID | DevNonce | pad16) # AppSKey = aes128_encrypt(AppKey, 0x02 | AppNonce | NetID | DevNonce | pad16)
def formatData(data): result = "" if data is None: return result else: search = re.search('(.*)"data":"(.*?)"(.*)', data) if search is not None: #means that a PHYPayload was received result = "Parsed data: %s\n" % (LorawanWrapper.printPHYPayload( search.group(2), None)) return result
def formatData(data): result = "" if data is None: return result else: search = re.search('(.*)"data":"(.*?)"(.*)', data.decode('utf-8', 'backslashreplace')) if search is not None: #means that a PHYPayload was received phyPayload = LorawanWrapper.printPHYPayload(search.group(2), None) result += "\nParsed data: " + phyPayload return result
def formatData(data): result = "" global fuzzOutMode global key global timeout global new_counter global dev_address if data is None: return result else: search = re.search('(.*)"data":"(.*?)"(.*)', data.decode('utf-8', 'backslashreplace')) if search is not None: #means that a PHYPayload was received phyPayload = LorawanWrapper.printPHYPayload(search.group(2), None) result += "\nParsed data: " + phyPayload return result
print("LoRaWAN Security Framework - %s" % (sys.argv[0])) print("Copyright (c) 2019 IOActive Inc. All rights reserved.") print("*****************************************************\n") parser = argparse.ArgumentParser( description= 'This script receives a JoinAccept and a JoinRequest in Base64, and an AppKey to generate the session keys. An example of the usage: \npython sessionKeysGenerator.py -a IB1scNmwJRA32RfMbvwe3oI= -r AE0jb3GsOdJVAwD1HInrJ7i3yXAFxKU= -k f5a3b185dfe452c8edca3499abcd0341' ) requiredGroup = parser.add_argument_group('Required arguments') requiredGroup.add_argument("-a", "--jaccept", help='JoinAccept payload in base64', required=True) requiredGroup.add_argument("-r", "--jrequest", help='JoinRequest payload in base64', required=True) requiredGroup.add_argument( "-k", "--key", help= 'Enter a device AppKey (in hex format, a total of 32 characters / 16 bytes). eg. 00112233445566778899AABBCCDDEEFF', required=True) options = parser.parse_args() json_result = LorawanWrapper.generateSessionKeysFromJoins( options.jrequest, options.jaccept, options.key) keys = json.loads(json_result) print("NwkSKey: %s\nAppSKey: %s" % (keys["nwkSKey"], keys["appSKey"]))
def bruteForce(packet): result = "" global device_auth_obj global dontGenerateKeys global keys global hours_betweeen_bruteforce_trials device_auth_obj = None if packet.m_type == "JoinRequest": # Check if Device exists. Otherwise, create it device_obj = Device.find_one_by_dev_eui_and_join_eui_and_datacollector_id( packet.dev_eui, packet.join_eui, packet.data_collector_id) if device_obj is None: try: device_obj = Device( dev_eui=packet.dev_eui, join_eui=packet.join_eui, organization_id=packet.organization_id, ) device_obj.save() except Exception as exc: logging.error("Error trying to save Device: {0}".format(exc)) # Associate Device with the DataCollector try: device_data_collector_obj = DataCollectorToDevice( data_collector_id=packet.data_collector_id, device_id=device_obj.id) device_data_collector_obj.save() except Exception as exc: logging.error( "Error trying to save DataCollectorToDevice: {0}".format( exc)) # Before cracking with many different keys, try with a PotentialAppKey previously found. In case this key is valid, we are pretty sure that is the correct AppKey device_auth_obj = DeviceAuthData.find_one_by_device_id(device_obj.id) if device_auth_obj and extractMIC( device_auth_obj.join_request) != packet.mic: pot_app_keys = PotentialAppKey.find_all_by_device_auth_id( device_auth_obj.id) if len(pot_app_keys) > 0: keys_to_test = list() for pot_app_key in pot_app_keys: keys_to_test.append( bytes(pot_app_key.app_key_hex.rstrip().upper(), encoding='utf-8')) keys_to_test = list(dict.fromkeys(keys_to_test)) correct_app_keys = LorawanWrapper.testAppKeysWithJoinRequest( keys_to_test, packet.data, True).split() if len(correct_app_keys) > 1: logging.warning( "Found more than one possible keys for the device {0}. One of them should be the correct. Check it manually. Keys: {1}" .format(packet.dev_eui, correct_app_keys)) elif len(correct_app_keys) == 1: # AppKey found!! device_auth_obj.second_join_request_packet_id = packet.id device_auth_obj.second_join_request = packet.data device_auth_obj.app_key_hex = correct_app_keys[0] parameters = {} parameters["dev_addr"] = "Unkwown" parameters["dev_eui"] = LorawanWrapper.getDevEUI( device_auth_obj.join_request) parameters["app_key"] = correct_app_keys[0] parameters[ "packet_id_1"] = device_auth_obj.join_request_packet_id parameters["packet_type_1"] = "JoinRequest" parameters["packet_type_2"] = "JoinRequest" parameters['packet_date'] = packet.date.strftime( '%Y-%m-%d %H:%M:%S') # Try to get the gateway from the packet, or retrieve it from DB if possible if packet.gateway: parameters["gateway"] = packet.gateway else: # If we don't receive the gateway in the packet, # get the hex ID of the gateway associated to the device if any. # If we have more than 1 gateway associated to the device, this method returns None gw_obj = Gateway.find_only_one_gateway_by_device_id( device_obj.id) if gw_obj: parameters["gateway"] = gw_obj.gw_hex_id else: parameters["gateway"] = 'Unkwown' try: alert = Alert( type="LAF-009", created_at=datetime.datetime.now(), packet_id=packet.id, device_auth_id=device_auth_obj.id, device_id=device_obj.id, parameters=json.dumps(parameters), data_collector_id=packet.data_collector_id) alert.save() ReportAlert.print_alert(alert) except Exception as exc: logging.error( "Error trying to save Alert LAF-009: {0}".format( exc)) return # Check if the DeviceAuthData wasn't already generated never_bruteforced = False if device_auth_obj is None: never_bruteforced = True try: device_auth_obj = DeviceAuthData( device_id=device_obj.id, data_collector_id=packet.data_collector_id, organization_id=packet.organization_id, join_request=packet.data, created_at=datetime.datetime.now(), join_request_packet_id=packet.id) device_auth_obj.save() except Exception as exc: logging.error( "Error trying to save DeviceAuthData at JoinRequest: {0}". format(exc)) # Check when was the last time it was bruteforced and # Try checking with the keys dictionary and the keys generated on the fly today = datetime.datetime.now() device_auth_obj.created_at = device_auth_obj.created_at.replace( tzinfo=None) elapsed = today - device_auth_obj.created_at # Time in seconds if elapsed.seconds > 3600 * hours_betweeen_bruteforce_trials or never_bruteforced: result = LorawanWrapper.testAppKeysWithJoinRequest( keys, packet.data, dontGenerateKeys) # Update the last time it was broteforced device_auth_obj.created_at = datetime.datetime.now() # If potential keys found... if result != "": device_auth_obj.join_request_packet_id = packet.id device_auth_obj.join_request = packet.data # Split string possibly containing keys separated by spaces candidate_keys_array = result.split() for hex_key in candidate_keys_array: try: potential_key_obj = PotentialAppKey( app_key_hex=hex_key.upper(), organization_id=packet.organization_id, last_seen=packet.date, packet_id=packet.id, device_auth_data_id=device_auth_obj.id) potential_key_obj.save() except Exception as exc: logging.error( "Error trying to save PotentialAppKey at JoinRequest: {0}" .format(exc)) elif packet.m_type == "JoinAccept" and packet.data is not None: last_seconds_date = packet.date - datetime.timedelta(seconds=5) try: organization_keys = PotentialAppKey.find_all_by_organization_id_after_datetime( packet.organization_id, last_seconds_date) # Return if no JR keys were found if len(organization_keys) == 0: return keys_array = list() for pk in organization_keys: # Fetch keys in byte format. Needed by ctypes keys_array.append( bytes(pk.app_key_hex.rstrip().upper(), encoding='utf-8')) # Remove possible duplicates in array keys_array = list(dict.fromkeys(keys_array)) result = LorawanWrapper.testAppKeysWithJoinAccept( keys_array, packet.data, True) except Exception as es: logging.error("Error trying to bforce JA: {0}".format(es)) if result != "": # Clean the key string result = result.rstrip().upper() for potential_key_obj in organization_keys: if potential_key_obj.app_key_hex.upper() == result: device_auth_obj = DeviceAuthData.find_one_by_id( potential_key_obj.device_auth_data_id) break if device_auth_obj is None: logging.error( "Cracked a JoinAccept but no device_auth object found") return # Get DevAddr from JA packet dev_addr = LorawanWrapper.getDevAddr(result, packet.data) # Check if DeviceSession exists. Otherwise, create it dev_ses_obj = DeviceSession.find_one_by_dev_addr_and_datacollector_id( dev_addr, packet.data_collector_id) if dev_ses_obj is None: try: dev_ses_obj = DeviceSession( dev_addr=dev_addr, organization_id=packet.organization_id, ) dev_ses_obj.save() except Exception as exc: logging.error( "Error trying to save DeviceSession: {0}".format(exc)) try: device_session_data_collector_obj = DataCollectorToDeviceSession( data_collector_id=packet.data_collector_id, device_session_id=dev_ses_obj.id) device_session_data_collector_obj.save() except Exception as exc: logging.error( "Error trying to save DataCollectorToDeviceSession: {0}" .format(exc)) #Add missing data device_auth_obj.device_session_id = dev_ses_obj.id device_auth_obj.join_accept = packet.data device_auth_obj.join_accept_packet_id = packet.id device_auth_obj.app_key_hex = result # Add session keys device_auth_obj = deriveSessionKeys(device_auth_obj, result) # Get the device to get dev_eui device_obj = Device.find_one(device_auth_obj.device_id) parameters = {} parameters["dev_addr"] = dev_ses_obj.dev_addr parameters["dev_eui"] = LorawanWrapper.getDevEUI( device_auth_obj.join_request) parameters["app_key"] = result parameters["packet_id_1"] = device_auth_obj.join_request_packet_id parameters["packet_type_1"] = "JoinRequest" parameters["packet_type_2"] = "JoinAccept" parameters['packet_date'] = packet.date.strftime( '%Y-%m-%d %H:%M:%S') if packet.gateway: parameters["gateway"] = packet.gateway else: # If we don't receive the gateway in the packet, # get the hex ID of the gateway associated to the device if any. # If we have more than 1 gateway associated to the device, this method returns None gw_obj = Gateway.find_only_one_gateway_by_device_id( device_obj.id) if gw_obj: parameters["gateway"] = gw_obj.gw_hex_id else: parameters["gateway"] = 'Unkwown' try: alert = Alert(type="LAF-009", created_at=datetime.datetime.now(), packet_id=packet.id, device_session_id=dev_ses_obj.id, device_id=device_obj.id, device_auth_id=device_auth_obj.id, parameters=json.dumps(parameters), data_collector_id=packet.data_collector_id) alert.save() ReportAlert.print_alert(alert) except Exception as exc: logging.error( "Error trying to save Alert LAF-009: {0}".format(exc))
print("LoRaWAN Security Framework - %s" % (sys.argv[0])) print("Copyright (c) 2019 IOActive Inc. All rights reserved.") print("*****************************************************\n") parser = argparse.ArgumentParser( description= 'This script parses and prints a single LoRaWAN PHYPayload data in Base64. It does the inverse as packetCrafter.py, so the output of that script can be used here and vice-versa.' ) requiredGroup = parser.add_argument_group('Required arguments') requiredGroup.add_argument( "-d", "--data", help= 'Base64 data to be parsed. eg. -d AE0jb3GsOdJVAwD1HInrJ7i3yXAFxKU=', default=None, required=True) parser.add_argument( "-k", "--key", help= 'Enter a device AppKey or AppSKey depending on the packet to be decrypted (join accept or data packet). Must be in hex format, a total of 32 characters / 16 bytes. eg. 00112233445566778899AABBCCDDEEFF', default=None) options = parser.parse_args() print("Parsed data: %s \n" % (LorawanWrapper.printPHYPayload(options.data, options.key))) except KeyboardInterrupt: exit(0)
def bruteForce(packet): result = "" global device_auth_obj global dontGenerateKeys global keys device_auth_obj = None if packet.m_type == "JoinRequest": result = LorawanWrapper.testAppKeysWithJoinRequest( keys, packet.data, dontGenerateKeys) if result != "": # Check if Device exists. Otherwise, create it device_obj = Device.find_one_by_dev_eui_and_join_eui_and_datacollector_id( packet.dev_eui, packet.join_eui, packet.data_collector_id) if device_obj is None: try: device_obj = Device( dev_eui=packet.dev_eui, join_eui=packet.join_eui, organization_id=packet.organization_id, ) device_obj.save() except Exception as exc: logging.error( "Error trying to save Device: {0}".format(exc)) # Associate Device with the DataCollector try: device_data_collector_obj = DataCollectorToDevice( data_collector_id=packet.data_collector_id, device_id=device_obj.id) device_data_collector_obj.save() except Exception as exc: logging.error( "Error trying to save DataCollectorToDevice: {0}". format(exc)) # Check if the DeviceAuthData wasn't already generated device_auth_obj = DeviceAuthData.find_one_by_device_id( device_obj.id) if device_auth_obj is None: try: device_auth_obj = DeviceAuthData( device_id=device_obj.id, data_collector_id=packet.data_collector_id, organization_id=packet.organization_id, join_request=packet.data, created_at=datetime.datetime.now(), join_request_packet_id=packet.id) device_auth_obj.save() except Exception as exc: logging.error( "Error trying to save DeviceAuthData at JoinRequest: {0}" .format(exc)) else: device_auth_obj.join_request_packet_id = packet.id device_auth_obj.join_request = packet.data # Split string possibly containing keys separated by spaces candidate_keys_array = result.split() for hex_key in candidate_keys_array: try: potential_key_obj = PotentialAppKey( app_key_hex=hex_key, organization_id=packet.organization_id, last_seen=packet.date, packet_id=packet.id, device_auth_data_id=device_auth_obj.id) potential_key_obj.save() except Exception as exc: logging.error( "Error trying to save PotentialAppKey at JoinRequest: {0}" .format(exc)) elif packet.m_type == "JoinAccept" and packet.data is not None: last_seconds_date = packet.date - datetime.timedelta(seconds=5) try: organization_keys = PotentialAppKey.find_all_by_organization_id_after_datetime( packet.organization_id, last_seconds_date) keys_array = list() for pk in organization_keys: # Fetch keys in byte format. Needed by ctypes keys_array.append( bytes(pk.app_key_hex.rstrip().upper(), encoding='utf-8')) # Remove possible duplicates in array keys_array = list(dict.fromkeys(keys_array)) result = LorawanWrapper.testAppKeysWithJoinAccept( keys_array, packet.data, True) except Exception as es: logging.error("Error trying to bforce JA: {0}".format(es)) if result != "": # Clean the key string result = result.rstrip().upper() for potential_key_obj in organization_keys: if potential_key_obj.app_key_hex == result: device_auth_obj = DeviceAuthData.find_one_by_id( potential_key_obj.device_auth_data_id) break # Get DevAddr from JA packet dev_addr = LorawanWrapper.getDevAddr(result, packet.data) # Check if DeviceSession exists. Otherwise, create it dev_ses_obj = DeviceSession.find_one_by_dev_addr_and_datacollector_id( dev_addr, packet.data_collector_id) if dev_ses_obj is None: try: dev_ses_obj = DeviceSession( dev_addr=dev_addr, organization_id=packet.organization_id, ) dev_ses_obj.save() except Exception as exc: logging.error( "Error trying to save DeviceSession: {0}".format(exc)) try: device_session_data_collector_obj = DataCollectorToDeviceSession( data_collector_id=packet.data_collector_id, device_session_id=dev_ses_obj.id) device_session_data_collector_obj.save() except Exception as exc: logging.error( "Error trying to save DataCollectorToDeviceSession: {0}" .format(exc)) #Add missing data device_auth_obj.device_session_id = dev_ses_obj.id device_auth_obj.join_accept = packet.data device_auth_obj.join_accept_packet_id = packet.id device_auth_obj.app_key_hex = result # Add session keys device_auth_obj = deriveSessionKeys(device_auth_obj, result) # Get the device to get dev_eui device_obj = Device.find_one(device_auth_obj.device_id) parameters = {} parameters["dev_addr"] = dev_ses_obj.dev_addr parameters["dev_eui"] = LorawanWrapper.getDevEUI( device_auth_obj.join_request) parameters["app_key"] = result parameters[ "join_request_packet_id"] = device_auth_obj.join_request_packet_id try: alert = Alert(type="LAF-009", created_at=datetime.datetime.now(), packet_id=packet.id, device_session_id=dev_ses_obj.id, device_auth_id=device_auth_obj.id, parameters=json.dumps(parameters), data_collector_id=packet.data_collector_id) alert.save() except Exception as exc: logging.error( "Error trying to save Alert LAF-009: {0}".format(exc)) ReportAlert.print_alert(alert)
def setPHYPayload(data): packet = {} stringPHY = LorawanWrapper.printPHYPayload(data) # If the PHYPayload couldn't be parsed, just put the error and return if "Error" in stringPHY: packet['error'] = stringPHY return packet try: jsonPHY = json.loads(stringPHY) except Exception as e: logging.error('Error parsing PHYPayload: {0}'.format(e)) packet['error'] = stringPHY return packet # The following fields are shared by every packet packet['m_type'] = jsonPHY['mhdr']['mType'] packet['major'] = jsonPHY['mhdr']['major'] packet['mic'] = jsonPHY['mic'] if packet['m_type'] == "JoinRequest": # It's a JoinRequest packet['join_eui'] = jsonPHY['macPayload']['joinEUI'] packet['dev_eui'] = jsonPHY['macPayload']['devEUI'] packet['dev_nonce'] = jsonPHY['macPayload']['devNonce'] return packet elif packet['m_type'] == "JoinAccept": # It's a JoinAccept Nothing to see return packet elif packet['m_type'] == "UnconfirmedDataDown" or packet[ 'm_type'] == "ConfirmedDataDown": # These are fCtrl fields for downlink packets packet['adr'] = jsonPHY['macPayload']['fhdr']['fCtrl']['adr'] packet['class_b'] = jsonPHY['macPayload']['fhdr']['fCtrl']['classB'] packet['adr_ack_req'] = jsonPHY['macPayload']['fhdr']['fCtrl'][ 'adrAckReq'] packet['ack'] = jsonPHY['macPayload']['fhdr']['fCtrl']['ack'] # These fields are common for every data packet packet['dev_addr'] = jsonPHY['macPayload']['fhdr']['devAddr'] packet['f_count'] = jsonPHY['macPayload']['fhdr']['fCnt'] if 'fOpts' in jsonPHY['macPayload']['fhdr'] and jsonPHY['macPayload'][ 'fhdr']['fOpts'] is not None: if isinstance(jsonPHY['macPayload']['fhdr']['fOpts'], dict): packet['f_opts'] = json.dumps( jsonPHY['macPayload']['fhdr']['fOpts']) elif isinstance(jsonPHY['macPayload']['fhdr']['fOpts'], list): packet['f_opts'] = json.dumps( jsonPHY['macPayload']['fhdr']['fOpts']) else: packet['f_opts'] = str(jsonPHY['macPayload']['fhdr']['fOpts']) if 'f_port' in jsonPHY['macPayload']['fhdr'] and jsonPHY['macPayload'][ 'fhdr']['fPort'] is not None: packet['f_port'] = jsonPHY['macPayload']['fhdr']['fPort'] return packet elif packet['m_type'] == "UnconfirmedDataUp" or packet[ 'm_type'] == "ConfirmedDataUp": # These are fCtrl fields for uplink packets packet['adr'] = jsonPHY['macPayload']['fhdr']['fCtrl']['adr'] packet['f_pending'] = jsonPHY['macPayload']['fhdr']['fCtrl'][ 'fPending'] packet['ack'] = jsonPHY['macPayload']['fhdr']['fCtrl']['ack'] # These fields are common for every data packet packet['dev_addr'] = jsonPHY['macPayload']['fhdr']['devAddr'] packet['f_count'] = jsonPHY['macPayload']['fhdr']['fCnt'] if 'fOpts' in jsonPHY['macPayload']['fhdr'] and jsonPHY['macPayload'][ 'fhdr']['fOpts'] is not None: if isinstance(jsonPHY['macPayload']['fhdr']['fOpts'], dict): packet['f_opts'] = json.dumps( jsonPHY['macPayload']['fhdr']['fOpts']) elif isinstance(jsonPHY['macPayload']['fhdr']['fOpts'], list): packet['f_opts'] = json.dumps( jsonPHY['macPayload']['fhdr']['fOpts']) else: packet['f_opts'] = str(jsonPHY['macPayload']['fhdr']['fOpts']) if 'f_port' in jsonPHY['macPayload']['fhdr'] and jsonPHY['macPayload'][ 'fhdr']['fPort'] is not None: packet['f_port'] = jsonPHY['macPayload']['fhdr']['fPort'] return packet return packet
parser = argparse.ArgumentParser( description= 'This scripts receives a PHYPayload packet in Base64 and a key which can be the NwkSKey of the AppKey depending on the packet type and generates the new MIC.' ) requiredGroup = parser.add_argument_group('Required arguments') requiredGroup.add_argument( "-d", "--data", help= 'Base64 data to be signed. eg. -d AE0jb3GsOdJVAwD1HInrJ7i3yXAFxKU=', default=None, required=True) requiredGroup.add_argument( "-k", "--key", help= 'Enter the new key (in hex format, a total of 32 characters / 16 bytes) to sign packets (calculate and add a new MIC). Note that for JoinRequest/JoinAccept it must be the AppKey, and the NwkSKey for Data packets. This cannot be validated beforehand by this program. eg. 00112233445566778899AABBCCDDEEFF', required=True, default=None) parser.add_argument( "--jakey", help= '[JoinAccept ONLY]. Enter the key used to encrypt the JoinAccept previously (in hex format, a total of 32 characters / 16 bytes). This cannot be validated beforehand by this program. eg. 00112233445566778899AABBCCDDEEFF. A valid key sample for the JoinAccept "IB1scNmwJRA32RfMbvwe3oI=" is "f5a3b185dfe452c8edca3499abcd0341"', default=None) options = parser.parse_args() print("\nYour PHYPayload with the new MIC is %s\n" % (LorawanWrapper.generateValidMIC(options.data, options.key, options.jakey)))