How to decrypt LoRaWAN packet

Hello,

I have found this code able to decrypt a LoRaWAN packet. Tha problem is that I could’nt use it :frowning:

it is basically unsing this function to decrypt LoRaWAN packet:

byte fport= (byte) 0x01;//Byte.valueOf("1");
byte dirValue = (byte) 0x00;//Byte.valueOf("0");
byte[] devAddr = {0x08,0x00,0x00,0x00};//hexStringToByteArray("08000000");
short fCnt = 200;

 public byte[] getClearPayLoad(byte[] payload, byte[] _nwkSKey, byte[] _appSKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, MalformedPacketException {
        byte[] key;
        
        if (fport == 0) {
            if (_nwkSKey == null) {
                throw new IllegalArgumentException("Missing nwkSKey");
            }
            if (_nwkSKey.length != 16) {
                throw new IllegalArgumentException("Invalid nwkSKey");
            }
            key = _nwkSKey;
        } else {
            if (_appSKey == null) {
                throw new IllegalArgumentException("Missing appSKey");
            }
            if (_appSKey.length != 16) {
                throw new IllegalArgumentException("Invalid appSKey");
            }
            key = _appSKey;
        }
        int k = (int) Math.ceil(payload.length / 16.0);
        System.out.println("payload length: "+payload.length);
        System.out.println("k is: "+ k);
        ByteBuffer a = ByteBuffer.allocate(16 * k);
        a.order(ByteOrder.LITTLE_ENDIAN);
        for (int i = 1; i <= k; i++) {
            a.put((byte) 0x01);
            a.put(new byte[]{0x00, 0x00, 0x00, 0x00});
            a.put(dirValue);
            a.put(devAddr);
            a.putInt(fCnt);
            a.put((byte) 0x00);
            a.put((byte) i);
        }
        Key aesKey = new SecretKeySpec(key, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);
        byte[] s = cipher.doFinal(a.array());
        byte[] paddedPayload = new byte[16 * k];
        System.arraycopy(payload, 0, paddedPayload, 0, payload.length);
        byte[] plainPayload = new byte[payload.length];
        for (int i = 0; i < payload.length; i++) {
            plainPayload[i] = (byte) (s[i] ^ paddedPayload[i]);
        }	        
        return plainPayload;
    }

So what I did is the following:

		byte[] appSKey = new byte[] { 0x2B, 0x7E, 0x15, 0x16, 0x28, (byte) 0xAE, (byte) 0xD2, (byte) 0xA6,(byte) 0xAB, (byte) 0xF7, 0x15, (byte) 0x88, 0x09,(byte)  0xCF, 0x4F, 0x3C};
	byte[] nwkSKey  = new byte[] { 0x2B, 0x7E, 0x15, 0x16, 0x28, (byte) 0xAE, (byte) 0xD2, (byte) 0xA6, (byte) 0xAB,(byte)  0xF7, 0x15, (byte) 0x88, 0x09,(byte) 0xCF, 0x4F, 0x3C};			
	byte[] payload = new byte[] {0x40,0x08,0x00,0x00,0x00,0x00,0x02,0x00,0x01,0x6B,(byte) 0xD9,0x4F,0x33,(byte) 0xB4,(byte) 0xC9,(byte) 0xB7,0x4D,(byte) 0xA8,0x26,0x3E,(byte) 0xD0,(byte) 0xAC,0x09, (byte)0xE5, (byte)0xA9,(byte) 0xB2, 0x6D, 0x01, (byte)0x89, 0x15, 0x47,(byte) 0xEA, 0x2F, (byte)0x89, 0x2F, 0x06}; 

	MyLoRaPayloadDecrypter loRaPayloadDecrypter =new  MyLoRaPayloadDecrypter();
	try {
		byte[] decrypted = loRaPayloadDecrypter.getClearPayLoad(payload, nwkSKey, appSKey);						
		System.out.println(new String(decrypted));
	} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException
			| BadPaddingException | MalformedPacketException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} 

But I am getting dummy values…

Any idea how to decrypt LoRaWAN packet in Java?
thanks!

What I think I see :

First you’re Fcnt is wrong because you init it with :

short fCnt = 200;

It should say short fCnt = 0x200; because in the payload the FCnt = 0x02, 0x00

Second, you might have to use :

a.putShort(fCnt);
a.put((byte) 0x00);
a.put((byte) 0x00);.

Instead of just a.putInt(fCnt);

Thirt, only decode the payload part of you data.

0x6C,0xD9,0x4F,0x33,0xB4,0xC9,0xB7,0x4D,
0xA8,0x26,0x3E,0xD0,0xAC,0x09,0xE5,0xA9,
0xB2,0x6D,0x01,0x89,0x15,0x47,0xEA,0x2F,
0x89,0x2F,0x06

System.arraycopy(payload, 0, paddedPayload, 0, payload.length);

Think you should use : System.arraycopy(payload, 9, paddedPayload, 0, payload.length - 9);

Forth. for the Cipher Init I mis the IV. Default it will be inited with a random seed.

The init should be done with a initial vector(IV) of

byte iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00};

See : Cipher (Java Card API and Subsets)(javacard.security.Key, byte, byte, short, short)

Hope the above helps you.

Disclamer : Java is not my native language so any syntax errors are for free :wink:

Edit : I have written a small payload (payload less then 16 byte) decoder program in C for testing. When needed I can share the source as a reference.

Thanks!
well I don’ know if I can use the Cipher java class to decrypt the LoRaWAN payload… the default mode is ECB, which is not what LoRaWAN use for encryption since it uses a mode similar to CTR if I well understood. that is not implemeneted in the Cipher class :frowning:

The implemented modes are:
AES/CBC/NoPadding (128)
AES/CBC/PKCS5Padding (128)
AES/ECB/NoPadding (128)
AES/ECB/PKCS5Padding (128)
DES/CBC/NoPadding (56)
DES/CBC/PKCS5Padding (56)
DES/ECB/NoPadding (56)
DES/ECB/PKCS5Padding (56)
DESede/CBC/NoPadding (168)
DESede/CBC/PKCS5Padding (168)
DESede/ECB/NoPadding (168)
DESede/ECB/PKCS5Padding (168)
RSA/ECB/PKCS1Padding (1024, 2048)
RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)

I think you should use : AES/CBC/NoPadding (128)

The padding part I’m not sure.

For you’re info, this is my encrypt function (in C)

int encrypt( void* buffer, int buffer_len, char* IV, char* key, int key_len )
{
  MCRYPT td = mcrypt_module_open("rijndael-128", IV, "cbc", NULL);
  int blocksize = mcrypt_enc_get_block_size(td);
  if( buffer_len % blocksize != 0 ){return 1;}

  mcrypt_generic_init(td, key, key_len, NULL);
  mcrypt_generic(td, buffer, buffer_len);
  mcrypt_generic_deinit (td);
  mcrypt_module_close(td);

  return 0;
}

//IV needs to be empty
uint8_t iv[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
1 Like

I’m on holiday so far from my computer but here is a quick sample:

    byte[] lorawan = new byte[]{};
    byte[] nwkSKey = new byte[]{};
    byte[] appSKey = new byte[]{};
    
    PhyPayload pp = PhyPayload.parse(ByteBuffer.wrap(lorawan));
    
    System.out.println(pp.getMessage().getClass());

    /**
     * case where your payload is a data payload
     */
    
    MACPayload pl = (MACPayload) pp.getMessage();
    
    FRMPayload data = pl.getFRMPayload();
    
    byte[] clearData = data.getClearPayLoad(nwkSKey, appSKey);
1 Like

Hello cambierrr,

This code looks like the code I probably need as weel, but I am not a programmer.

I would like to decrypt the payload from a wireshark captured lora packet.
Can I use this code on my Linux computer?

I have already decoded a packet with the Lora Packet decoder (https://runkit.io/avbentem/lorawan-packet-decoder/branches/master?)
This gives the next output:

Decoding-Command	: sudo lora-packet-decode --base64 QCYjASYAMgACPJCTL5qlKADKXShapK8=

decoding from Base64:  QCYjASYAMgACPJCTL5qlKADKXShapK8=
Decoded packet
--------------
Message Type = Data
  PHYPayload = 4026230126003200023C90932F9AA52800CA5D285AA4AF

( PHYPayload = MHDR[1] | MACPayload[..] | MIC[4] )
        MHDR = 40
  MACPayload = 26230126003200023C90932F9AA52800CA5D
         MIC = 285AA4AF

( MACPayload = FHDR | FPort | FRMPayload )
        FHDR = 26230126003200
       FPort = 02
  FRMPayload = 3C90932F9AA52800CA5D

      ( FHDR = DevAddr[4] | FCtrl[1] | FCnt[2] | FOpts[0..15] )
     DevAddr = 26012326 (Big Endian)
       FCtrl = 00
        FCnt = 0032 (Big Endian)
       FOpts = 

Message Type = Unconfirmed Data Up
   Direction = up
        FCnt = 50
   FCtrl.ACK = false
   FCtrl.ADR = false

Now I want to decrypt the actual payload. I have the AppKey and the Network Session Key

Is this possible?

See

(The basic online frontend you used does did not decrypt anything, but only shows showed the public data, even without any validation of the signature. It uses the above Node.js library, which can validate and decrypt for you.)

Hello,

I tried that and it works. Thanks a lot
Gerke

Please kindly give us code a ardunio example…

Could you please send me the source code? Thanks!

I have created a detailed way to decrypt LoRaWAN packet , hope it would be useful

Decoding of LoRaWAN packets

The above screenshot is the received LoRaWAN packet at the gateway which needs to be decoded to understand if the correct data are being sent to TTN.

From the LoRaWAN packet data (QNIfASaABAABA/uB+N3FPLnND5ufMW6z) which is in base 64 encoded format is decoded using step (1) to obtain FRMpayload.

  1. https://LoRaWAN-packet-decoder-0ta6puiniaut.runkit.sh

This web link helps decoding the base 64 data received at the gateway

Assuming base64-encoded packet
QNIfASaABAABA/uB+N3FPLnND5ufMW6z

Message Type = Data
  PHYPayload = 40D21F01268004000103FB81F8DDC53CB9CD0F9B9F316EB3

( PHYPayload = MHDR[1] | MACPayload[..] | MIC[4] )
        MHDR = 40
  MACPayload = D21F01268004000103FB81F8DDC53CB9CD0F9B
         MIC = 9F316EB3

( MACPayload = FHDR | FPort | FRMPayload )
        FHDR = D21F0126800400
       FPort = 01
  FRMPayload = 03FB81F8DDC53CB9CD0F9B

      ( FHDR = DevAddr[4] | FCtrl[1] | FCnt[2] | FOpts[0..15] )
     DevAddr = 26011FD2 (Big Endian)
       FCtrl = 80
        FCnt = 0004 (Big Endian)
       FOpts = 

Message Type = Unconfirmed Data Up
   Direction = up
        FCnt = 4
   FCtrl.ACK = false
   FCtrl.ADR = true

Download LoRaWAN packet decoder

This script has dependency on cryptography.cryptography requires cffi which in turn requires libffi-dev, python-dev and openssl.

$sudo apt-get install libffi-dev
$sudo apt-get install python-dev
$sudo apt-get install python3-dev
$sudo apt-get install openssl
$pip install python-lora

python-lora.png

The FRMpayload is then used in payload_decrypt.py along with sequence counter (fcnt), dev_addr of the node and Appskey to obtain the decimal equivalent of the payload.

$ mkdir lora_decrypt
$ cd lora_decrypt
$ sudo nano payload_decrypt.py

Copy the FRMpayload from (1) to payload, fcnt to sequence_counter, and enter the dev_addr and Appskey used to encrypt the payload.

Press ctrl+x -> y -> ENTER to save the python script.

To run the script type “python payload_decrypt.py” and hit enter.

Upon execution of the payload_decrypt.py a string of decimal values is obtained; this string is then decoded using decimal to hex converter using online tool.

The cryptology library has dependency on setuptools and python version in Linux and Raspbian, which takes a quite a bit effort. However, I migrated to Anaconda IDE in windows to execute the python decrypt script.

Assuming base64-encoded packet
QIsbASaABQABlKmBif/36J2UWcXZ/hdK

Message Type = Data
  PHYPayload = 408B1B01268005000194A98189FFF7E89D9459C5D9FE174A

( PHYPayload = MHDR[1] | MACPayload[..] | MIC[4] )
        MHDR = 40
  MACPayload = 8B1B01268005000194A98189FFF7E89D9459C5
         MIC = D9FE174A

( MACPayload = FHDR | FPort | FRMPayload )
        FHDR = 8B1B0126800500
       FPort = 01
  FRMPayload = 94A98189FFF7E89D9459C5

      ( FHDR = DevAddr[4] | FCtrl[1] | FCnt[2] | FOpts[0..15] )
     DevAddr = 26011B8B (Big Endian)
       FCtrl = 80
        FCnt = 0005 (Big Endian)
       FOpts = 

Message Type = Unconfirmed Data Up
   Direction = up
        FCnt = 5
   FCtrl.ACK = false
   FCtrl.ADR = true

from lora.crypto import loramac_decrypt
payload = ’94A98189FFF7E89D9459C5′
sequence_counter = 5
key = ‘DF2FE61E548EF67A186ECFF15E2A09AB’
dev_addr = ‘26011B8B’
print (loramac_decrypt(payload, sequence_counter, key, dev_addr))

Result obtained by executing the payload_decrypt.py

[104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]

To convert this base 64 values to ASCII Value, let us uses an online converter

dec to ascii.png

1 Like

Beware that one should always validate the MIC (using the NwkSKey), as otherwise there is no way to tell if the decrypted payload is correct, or just some random value.

As an aside: when specifying the secrets, the online decoder decrypts as well:

  FRMPayload = 94A98189FFF7E89D9459C5 (from packet, encrypted)
             = 68656C6C6F20776F726C64 (decrypted)

Even when changing just a single bit in the AppSKey while decrypting, one would get a totally different result:

             = 65D25B53FBF89844647E85 (decrypted)
1 Like