{"id":43074230,"url":"https://github.com/PentHertz/LoRa_Craft","last_synced_at":"2026-02-11T10:00:55.302Z","repository":{"id":54962428,"uuid":"234043243","full_name":"PentHertz/LoRa_Craft","owner":"PentHertz","description":"Some Scapy layers and tools to study LoRa PHY and LoRaWAN","archived":false,"fork":false,"pushed_at":"2025-11-13T18:12:11.000Z","size":833,"stargazers_count":120,"open_issues_count":1,"forks_count":19,"subscribers_count":12,"default_branch":"master","last_synced_at":"2026-02-09T13:36:22.296Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PentHertz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-01-15T09:16:59.000Z","updated_at":"2026-02-06T15:16:40.000Z","dependencies_parsed_at":"2025-02-27T19:24:13.334Z","dependency_job_id":"54753caf-6f5a-4f00-bcae-b8e3a2b025e5","html_url":"https://github.com/PentHertz/LoRa_Craft","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/PentHertz/LoRa_Craft","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PentHertz%2FLoRa_Craft","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PentHertz%2FLoRa_Craft/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PentHertz%2FLoRa_Craft/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PentHertz%2FLoRa_Craft/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PentHertz","download_url":"https://codeload.github.com/PentHertz/LoRa_Craft/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PentHertz%2FLoRa_Craft/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29331591,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-11T06:13:03.264Z","status":"ssl_error","status_checked_at":"2026-02-11T06:12:55.843Z","response_time":97,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-31T14:00:24.326Z","updated_at":"2026-02-11T10:00:55.297Z","avatar_url":"https://github.com/PentHertz.png","language":"Python","funding_links":[],"categories":["Wireless Protocols","Satellite Communication"],"sub_categories":["LoRa / LoRaWAN","SMPP / SMS Gateways"],"readme":"# LoRa Craft\n\nLoRa Craft is a small set of tools that aims to provide tools to assess LoRAPHY and LoRaWAN communications.\n\nAvailable features:\n\n* Capture packet as PCAP and read them\n* Parses LoRaPHY and LoRaWAN 1.0 + 1.1 packets\n* Supports UpLink as well as DownLink\n* Can bruteforce Join-Request and Join-Accept MIC as well as Data Payload MIC\n* Can decipher payloads =\u003e not tested with real LoRaWAN 1.1 devices yet\n* Possible to generate packet at low LoRaPHY layer\n\n## Dependencies\n\n* Python 2 or 3\n* Scapy\n* GNU Radio 3.8\n* gr-lora from [rpp0](https://github.com/rpp0): [link here](https://github.com/rpp0/gr-lora)\n* or gr-lorasdr (for TX \u0026 RX): [link here](https://github.com/tapparelj/gr-lora_sdr)\n* Software-Defined Radio equipment (USRP, bladeRF, RTL-SDR dongle, etc.)\n\n### Receive and decode packets packets\n\nFirst you need to generate GNU Radio hierachical blocks `lora_txrxdecode.grc`, and `lora_rechan.grc` before running `LoRa_MultiSF_decode_to_UDP.grc` flowgraph located in the `grc` directory.\n\nAfter that we can run the `LoRa_MultiSF_decode_to_UDP.grc` by connecting one the supported SDR device by the `osmocom Source` block:\n\n![alt text](https://github.com/PentHertz/LoRa_Craft/blob/master/img/LoRaMultiSF.png \"Multi channel and SF flowgraph\")\n\nThen we can run the decoder script that will automatically parse packet from the socket used by `gr-lora` and display it on the console as follows:\n\n```bash\n# python3 LoRa_PHYDecode-NG.py   \n\n------------------------------\u003e\n\u003cLoRa  Preamble=0x1 PHDR=0xe312 PHDR_CRC=0x0 MType=Unconfirmed Data Up RFU=0 Major=0 DevAddr=[\u003cDevAddrElem  NwkID=0x6e NwkAddr=0x260117 |\u003e] FCtrl=[\u003cFCtrl_Link  ADR=1 ADRACKReq=0 ACK=0 UpClassB_DownFPending=0 FOptsLen=0 |\u003e] FCnt=0 FPort=1 ULDataPayload=\"M\\x93'\\tT\\xd6\\xa4\\x02\\x8e\\x0e9f\\xdc\\xfd\\xec\\x898\" MIC=0x8ce72a63 CRC=0x978e |\u003e\n\n------------------------------\u003e\n\u003cLoRa  Preamble=0x1 PHDR=0xe312 PHDR_CRC=0x0 MType=Unconfirmed Data Up RFU=0 Major=0 DevAddr=[\u003cDevAddrElem  NwkID=0x6e NwkAddr=0x260117 |\u003e] FCtrl=[\u003cFCtrl_Link  ADR=1 ADRACKReq=0 ACK=0 UpClassB_DownFPending=0 FOptsLen=0 |\u003e] FCnt=1 FPort=1 ULDataPayload='w\\xf96\\x98\\x9f\\x1a\\x1e\\x14\\xa3\\xac\\xb4\\xbe_X\u0026\\xa1\\x81' MIC=0x43f31d41 CRC=0x6b0 |\u003e\n\n\u003c------------------------------\n\u003cLoRa  Preamble=0x1 PHDR=0x3219 PHDR_CRC=0x0 MType=Unconfirmed Data Down RFU=0 Major=0 DevAddr=[\u003cDevAddrElem  NwkID=0x6e NwkAddr=0x260117 |\u003e] FCtrl=[\u003cFCtrl_Link  ADR=0 ADRACKReq=0 ACK=0 UpClassB_DownFPending=0 FOptsLen=0 |\u003e] FCnt=0 FPort=1 DLDataPayload=\"\\xb9d\\x8c\\xf90'\" MIC=0xd395a01e |\u003e\n```\n\n**Note** we can see 2 uplink packets and 1 downling packet that got parsed by the tool\n\n## Generate packets\n\nTo generate packets, you can instantiate a Scapy packet as follows:\n\n```python\n\u003e\u003e\u003e from layers.loraphy2wan import *\n\u003e\u003e\u003e pkt = LoRa()\n\u003e\u003e\u003e pkt\n\u003cLoRa  Join_Request_Field=[''] |\u003e\n```\n\nAnd start to fill it.\n\nAfter crafting your packet, you can use [python-loranode](https://github.com/rpp0/python-loranode) as follows:\n\n```python\n\u003e\u003e\u003e import binascii\n\u003e\u003e\u003e from loranode import RN2483Controller\n\u003e\u003e\u003e to_send = binascii.hexlify(str(pkt))[3:]\n\u003e\u003e\u003e c = RN2483Controller(\"/dev/ttyACM0\")  # Choose the correct /dev device here\n\u003e\u003e\u003e c.set_sf(7)  # choose your spreading factor here\n\u003e\u003e\u003e c.set_bw(150) # choose the bandwidth here\n\u003e\u003e\u003e c.set_cr(\"4/8\")  # Set 4/8 coding for example\n\u003e\u003e\u003e c.send_p2p(to_send)\n```\n\nNote that you should skip the first three bytes (Preamble, PHDR, PHDR_CRC), before sending it with `send_p2p` method.\n\n## LoRa crypto helpers\n\nFew helpers have been implemented to calculate MIC field, encrypt and decrypt packets:\n\n* `JoinAcceptPayload_decrypt`: decrypt Join-accept payloads;\n* `JoinAcceptPayload_encrypt`: encrypt Join-accept payloads;\n* `getPHY_CMAC`: compute MIC field of a packet using a provided key;\n* `checkMIC`: check MIC of a packet against a provided key.\n* `checkDATAMIC_1x`: check MIC for FRMPayloads\n* `bruteforceDATAMIC_10`: bruteforce MIC for UL/DL FRMPayloads\n\n\n### Checking MIC for 'Join-request' packets\n\nAs an example, to check if the key `000102030405060708090A0B0C0D0E0F` is used to compute MIC on the following Join-request, we can write a little script as follows:\n\n```python\n\u003e\u003e\u003e from layers.loraphy2wan import *\n\u003e\u003e\u003e from lutil.crypto import *\n\u003e\u003e\u003e key = \"000102030405060708090A0B0C0D0E0F\"\n\u003e\u003e\u003e p = '000000006c6f7665636166656d656565746f6f00696953024c49'\n\u003e\u003e\u003e pkt = LoRa(binascii.unhexlify(p))\n\u003e\u003e\u003e pkt\n\u003cLoRa  Preamble=0x0 PHDR=0x0 PHDR_CRC=0x0 MType=Join-request RFU=0 Major=0 Join_Request_Field=[\u003cJoin_Request  AppEUI='lovecafe' DevEUI='meeetoo' DevNonce=26985 |\u003e] MIC=0x53024c49 |\u003e\n\u003e\u003e\u003e checkMIC(binascii.unhexlify(key), bytes(pkt))\nTrue\n```\n\n### Deciphering a 'Join-accept' message\n\nTo check if `000102030405060708090A0B0C0D0E0F` key is used to encrypt a Join-accept message, we can combine `JoinAcceptPayload_decrypt` and `checkMIC` as follows:\n\n```python\n\u003e\u003e\u003e pkt = \"000000200836e287a9805cb7ee9e5fff7c9ee97a\"\n\u003e\u003e\u003e ja = JoinAcceptPayload_decrypt(binascii.unhexlify(key), binascii.unhexlify(pkt))\n\u003e\u003e\u003e ja\n'ghi#\\x01\\x00\\xb2\\\\C\\x03\\x00\\x00{\\x06O\\x8a'\n\u003e\u003e\u003e Join_Accept(ja)\n\u003cJoin_Accept  JoinAppNonce=0x6fe14a NetID=0x10203 DevAddr=0x68e8cb1 OptNeg=0 RX1DRoffset=0x0 RX2_Data_rate=0x0 RxDelay=0x0 |\u003cPadding  load='\\xbejsu' |\u003e\u003e\n\u003e\u003e\u003e p = b\"\\x00\\x00\\x00\\x20\"+ja # adding headers\n\u003e\u003e\u003e checkMIC(binascii.unhexlify(key), p)\n\u003e\u003e\u003e True\n```\n\n### Check and bruteforce the MIC of a FRMPayload\n\nWe want to check the MIC of the following captured packet containing an UL/DL data:\n\n```python\n~\u003e\u003e\u003e pkt\n\u003cLoRa  Preamble=0x1 PHDR=0xe312 PHDR_CRC=0x0 MType=Unconfirmed Data Up RFU=0 Major=0 DevAddr=[\u003cDevAddrElem  NwkID=0x6e NwkAddr=0x260117 |\u003e] FCtrl=[\u003cFCtrl_Link  ADR=1 ADRACKReq=0 ACK=0 UpClassB_DownFPending=0 FOptsLen=0 |\u003e] FCnt=0 FPort=1 ULDataPayload=\"M\\x93'\\tT\\xd6\\xa4\\x02\\x8e\\x0e9f\\xdc\\xfd\\xec\\x898\" MIC=0x8ce72a63 CRC=0x978e |\u003e\n```\n\nIf we already have the `NwkSkey` that is `2B7E151628AED2A6ABF7158809CF4F3C` for example, we can use one of the `checkDATAMIC_1x` function (depending of LoRaWAN version) to check it:\n\n```python\n~\u003e\u003e\u003e checkDATAMIC_10(binascii.unhexlify(\"2B7E151628AED2A6ABF7158809CF4F3C\"), bytes(pkt))\nTrue\n```\n\nAnd see that decoded MIC (`MIC=0x8ce72a63`) with the Scapy layer matches the one processed by `checkDATAMIC_1x` function.\n\nBut in case we want to bruteforce this key, we can actually do it using the `bruteforceDATAMIC_1x` function by providing a list of key dictionnary path:\n\n```python\n~\u003e\u003e\u003e bruteforceDATAMIC_10(bytes(pkt), \"/home/fluxius/Projects/LoRa/tools/LoRa_Craft/resources/keydict.lst\")\nTesting:  00000000000000000000000000000000\n\nTesting:  00010101010101010101010101010101\n\nTesting:  01234567890123456789012345678901\n\nTesting:  000102030405060708090a0b0c0d0e0f\n\nTesting:  00020202020202020202020202020202\n\nTesting:  00030303030303030303030303030303\n\nTesting:  00040404040404040404040404040404\n\nTesting:  00050505050505050505050505050505\n\nTesting:  00060606060606060606060606060606\n\nTesting:  2B7E151628AED2A6ABF7158809CF4F3C\n\n('Found NwkSKey: ', b'2b7e151628aed2a6abf7158809cf4f3c')\n```\n\n**Warning:** the check function also takes a 3rd argument that is the direction of the packet (UL or DL)\n\nAnd we found the correct key! :) =\u003e so we can mess with packet's integrity now.\n\n\n### Decipher a FRMPayload\n\nIf we have been able to retrieve the key used to decipher the FRMPayload, we can try it using the `decryptFRMPayload` function as follows:\n\n```python\n~\u003e\u003e\u003e pkt\n\u003cLoRa  Preamble=0x1 PHDR=0xe312 PHDR_CRC=0x0 MType=Unconfirmed Data Up RFU=0 Major=0 DevAddr=[\u003cDevAddrElem  NwkID=0x6e NwkAddr=0x260117 |\u003e] FCtrl=[\u003cFCtrl_Link  ADR=1 ADRACKReq=0 ACK=0 UpClassB_DownFPending=0 FOptsLen=0 |\u003e] FCnt=0 FPort=1 ULDataPayload=\"M\\x93'\\tT\\xd6\\xa4\\x02\\x8e\\x0e9f\\xdc\\xfd\\xec\\x898\" MIC=0x8ce72a63 CRC=0x978e |\u003e\n~\u003e\u003e\u003e decryptFRMPayload(binascii.unhexlify(\"2b7e151628aed2a6abf7158809cf4f3c\"), bytes(pkt))\nb'\u003c3Trend with Love\\xed@W`f/;\\xafL\\xff\\x04\\xd0\\xb5\\xb83'\n```\n\n**Warning:** the check function also takes a 3rd argument that is the direction of the packet (UL or DL)\n\n## Further work\n\n* Test MIC bruteforcing and deciphering on LoRaWAN 1.1\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FPentHertz%2FLoRa_Craft","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FPentHertz%2FLoRa_Craft","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FPentHertz%2FLoRa_Craft/lists"}