5

乍一看,我和很多人都有同样的问题。但我的情况有点复杂。

前提条件:
项目语言: Java 11
网络服务器: Orbiwise NS ( https://eu.saas.orbiwise.com/ )
设备:(STM32 + Wifi 模块)通过 Lorawan 网关连接到 Orbiwise 并通过 wifi 使用 TCP 套接字。

输入数据:
TCP 套接字接收到的字节数组:

40 24 fa fa 01 c2 c5 25  03 06 01 43 a4 99 5a c1
85 71 0c 87 38 84 53 9a  80 6c 5a 14 da f8 ff 7c
21 83 8f 78 8e ec f2 7d  4e 4e 07  

(43 字节)

Orbiwise上有对应的上行有效载荷:

31 19 10 07 01 13 51 25  09 01 00 00 00 00 33 04
00 00 5A 00 00 00 EB 0D  00 00 64 EB 

(28 字节)

任务:
从 TCP 套接字解密数据,以与 Orbiwise 上相同的格式解密有效负载

使用的方法没有积极的结果:

  1. https://github.com/jsubercaze/javalora
  2. https://github.com/huahang/crypto-utils/blob/master/crypto-utils/src/main/java/im/chic/utils/crypto/AesUtils.java
  3. https://github.com/matthiaszimmermann/ttn_decoder_java - 我的核心代码。

上面的所有项目都是很久以前写的,对我没有帮助。这个有帮助,但写在 Node JS 上:https ://github.com/anthonykirby/lora-packet

从 LoraWan 规范中,我得到了使用的"AES/ECB/NoPadding"方法,我当前的代码如下所示:

package org.thethingsnetwork.main.java.org.thethingsnetwork.util.security;

import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class Crypto {

    /**
     * Decrypts TTN data_raw payload to data according to the TTN REST API.
     * @param pld encrypted message payload from ttn mqtt message
     * @param K the TTN application key 
     * @param IV
     * @return decrypted payload
     * @throws Exception
     */
    public static byte[] decrypt(byte [] pld, byte [] K, byte [] IV) throws Exception {
        byte [] devAddr = getDevAddr(pld);
        byte [] frameCounter = getFrameCounter(pld);
        byte [] result = initializeResult(pld);
        byte [] Ai = new byte[16];
        byte [] Si = null;

        for(int i = 0; i < result.length; i += 16) {
            int blockSeqCnt = (i >> 4) + 1;

            computeAi(Ai, devAddr, frameCounter, blockSeqCnt);

            Si = encryptAES(Ai, K, IV);

            for(int j=0; j < 16 && i+j < result.length; j++) {
                result[i+j] ^= Si[j];
            }
        }
        return  result;
    }

    /**
     * Converts TTN payload data to data_plain according to the TTN REST API.
     * Decode a text using base 64 decoding. 
     * @param decryptedText
     * @return
     */
    public static String toPlainText(String decryptedText) {
        byte [] data = Base64.getDecoder().decode(decryptedText);
        StringBuffer plain = new StringBuffer();

        for(int i = 0; i < data.length; i++) {
            plain.append((char)data[i]);
        }

        return plain.toString();
    }


    public static byte [] getDevAddr(byte [] payload) {
        byte [] devAddr = new byte[4];
        System.arraycopy(payload, 1, devAddr, 0, 4);
        return devAddr;
    }

    public static byte [] getFrameCounter(byte [] payload) {
        byte [] frameCounter = new byte[2];
        System.arraycopy(payload, 6, frameCounter, 0, 2);
        return frameCounter;
    }

    public static byte [] initializeResult(byte [] payload) {
        byte [] result = new byte[payload.length - 13];

        for(int i = 0; i < result.length; i++) {
            result[i] = payload[i+9];
        }

        return result;
    }

    public static void computeAi(byte [] a, byte [] devAddr, byte [] frameCounter, int blockSeqCnt) {
        a[0]  = 0x01;
        a[1]  = 0x00;
        a[2]  = 0x00;
        a[3]  = 0x00;
        a[4]  = 0x00;
        a[5]  = 0;               // 0 for uplink frames 1 for downlink frames;
        a[6]  = devAddr[0];      // LSB devAddr 4 bytes
        a[7]  = devAddr[1];      // ..
        a[8]  = devAddr[2];      // ..
        a[9]  = devAddr[3];      // MSB
        a[10] = frameCounter[0]; // LSB framecounter
        a[11] = frameCounter[1]; // MSB framecounter
        a[12] = 0x00;            // Frame counter upper Bytes
        a[13] = 0x00;
        a[14] = 0x00;
        a[15] = (byte)blockSeqCnt;  // block sequence counter 1,2,3...
    }

    /**
     * AES encrpytion.
     */
    public static byte[] encryptAES(byte [] data, byte [] key, byte [] iv) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "SunJCE");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        return cipher.doFinal(data);
    }

}

第二类 - 测试:

package org.thethingsnetwork.util.security;

import org.apache.commons.codec.DecoderException;
import org.junit.Assert;
import org.junit.Test;
import org.thethingsnetwork.main.java.org.thethingsnetwork.util.security.Crypto;
import org.apache.commons.codec.binary.Hex;

public class CryptoTest {

private byte [] SEMTECH_DEFAULT_KEY = Hex.decodeHex("2E12E8BD30FE2FB2D8DE609747D2569F".toCharArray());

    public static final byte [] IV = new byte [] {
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
    };  //  initialization vector – IV

    // example data from a ttn message
    public static final byte[] TEXT_RAW   = new byte[] {0x40, 0x24, (byte) 0xfa, (byte) 0xfa, 0x01, (byte) 0xc2,
            (byte) 0xc5, 0x25, 0x03, 0x06, 0x01, 0x43, (byte) 0xa4, (byte) 0x99, 0x5a, (byte) 0xc1, (byte) 0x85, 0x71,
            0x0c, (byte) 0x87, 0x38, (byte) 0x84, 0x53, (byte) 0x9a, (byte) 0x80, 0x6c, 0x5a, 0x14, (byte) 0xda,
            (byte) 0xf8, (byte) 0xff, 0x7c, 0x21, (byte) 0x83, (byte) 0x8f, 0x78, (byte) 0x8e, (byte) 0xec, (byte) 0xf2,
            0x7d, 0x4e, 0x4e, 0x07};

    public CryptoTest() throws DecoderException {
    }

    @Test
    public void testDecoder() throws Exception {
        byte[] decryptedText = Crypto.decrypt(TEXT_RAW, SEMTECH_DEFAULT_KEY, IV);

        printTheByteToString(decryptedText);
    }

    /**
     * Method for prin in command line byte array. For debug necessary
     * @param b - input byte array
     */
    private void printTheByteToString (byte[] b) {
        for (byte val : b) {
            System.out.print(String.format("%02x ", val));
        }
    }
}

我检查了代码一百次,但没有得到肯定的结果——解密的消息与 Orbiwise 上的不同。

我已经使用 NodeJs 项目(https://github.com/anthonykirby/lora-packet)检查了数据,一切都是正确的。但无法使用我的 Java 代码解决任务。
在此处输入图像描述

在这种情况下,有人可以帮助我吗?提前非常感谢!

4

2 回答 2

2

您的密文偏移量减少了 2(毫无疑问,因为有 3 个选项字节而不是 1)。打印出中间结果或对标头字节执行完整解析应该会告诉您这一点,这就是我在评论中提到它的原因。请注意,将 9 的偏移量增加 2 也可能会影响密文的大小,因为密文的结尾是固定的。

此外,您正在使用Cipher.DECRYPT_MODE而协议仅在转发模式下使用密码。计数器模式(由 CCM 和 CCM* 密码使用)仅在转发模式下使用密码,因为它们生成一个密钥流,然后与明文流或密文流进行异或运算以分别加密/解密。协议中的“解密”部分仅是关于对生成的密钥流执行最终的 XOR到密文而不是明文。

当然,ECB 模式——在这种情况下用于简单地实现单个块加密——不需要 IV,因此部分代码是虚假的。

于 2019-12-28T12:45:09.707 回答
1

非常感谢Maarten解决了这个问题。
结果 - 正确的代码如下所示:

import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class Crypto {

    /**
     * Decrypts TTN data_raw payload to data according to the TTN REST API.
     * @param pld encrypted message payload from ttn mqtt message
     * @param K the TTN application key
     * @return decrypted payload
     * @throws Exception
     */
    public static byte[] decrypt(byte [] pld, byte [] K) throws Exception {
        byte [] devAddr = getDevAddr(pld);
        byte [] frameCounter = getFrameCounter(pld);
        byte [] result = initializeResult(pld);
        byte [] Ai = new byte[16];
        byte [] Si = null;

        for(int i = 0; i < result.length; i += 16) {
            int blockSeqCnt = (i >> 4) + 1;

            computeAi(Ai, devAddr, frameCounter, blockSeqCnt);
            Si = encryptAES(Ai, K);

            for(int j=0; j < 16 && i+j < result.length; j++) {
                result[i+j] ^= Si[j];
            }
        }
        return  result;
    }

    public static byte [] getDevAddr(byte [] payload) {
        byte [] devAddr = new byte[4];
        System.arraycopy(payload, 1, devAddr, 0, 4);
        return devAddr;
    }

    public static byte [] getFrameCounter(byte [] payload) {
        byte [] frameCounter = new byte[2];
        System.arraycopy(payload, 6, frameCounter, 0, 2);
        return frameCounter;
    }

    public static byte [] initializeResult(byte [] payload) {
        byte [] result = new byte[payload.length - 15];

        for(int i = 0; i < result.length; i++) {
            result[i] = payload[i+11];
        }

        return result;
    }

    public static void computeAi(byte [] a, byte [] devAddr, byte [] frameCounter, int blockSeqCnt) {
        a[0]  = 0x01;
        a[1]  = 0x00;
        a[2]  = 0x00;
        a[3]  = 0x00;
        a[4]  = 0x00;
        a[5]  = 0;               // 0 for uplink frames 1 for downlink frames;
        a[6]  = devAddr[0];      // LSB devAddr 4 bytes
        a[7]  = devAddr[1];      // ..
        a[8]  = devAddr[2];      // ..
        a[9]  = devAddr[3];      // MSB
        a[10] = frameCounter[0]; // LSB framecounter
        a[11] = frameCounter[1]; // MSB framecounter
        a[12] = 0x00;            // Frame counter upper Bytes
        a[13] = 0x00;
        a[14] = 0x00;
        a[15] = (byte)blockSeqCnt;  // block sequence counter 1,2,3...
    }

    /**
     * AES encrpytion.
     * @param data
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] encryptAES(byte [] data, byte [] key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "SunJCE");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        return cipher.doFinal(data);
    }

}

和测试类:

import org.apache.commons.codec.DecoderException;
import org.junit.Assert;
import org.junit.Test;
import org.security.Crypto;
import org.apache.commons.codec.binary.Hex;

public class CryptoTest {

private byte [] SEMTECH_DEFAULT_KEY = Hex.decodeHex("2E12E8BD30FE2FB2D8DE609747D2569F".toCharArray());

    // example data from a ttn message
    public static final byte[] TEXT_RAW   = new byte[] {0x40, 0x24, (byte) 0xfa, (byte) 0xfa, 0x01, (byte) 0xc2,
            (byte) 0xc5, 0x25, 0x03, 0x06, 0x01, 0x43, (byte) 0xa4, (byte) 0x99, 0x5a, (byte) 0xc1, (byte) 0x85, 0x71,
            0x0c, (byte) 0x87, 0x38, (byte) 0x84, 0x53, (byte) 0x9a, (byte) 0x80, 0x6c, 0x5a, 0x14, (byte) 0xda,
            (byte) 0xf8, (byte) 0xff, 0x7c, 0x21, (byte) 0x83, (byte) 0x8f, 0x78, (byte) 0x8e, (byte) 0xec, (byte) 0xf2,
            0x7d, 0x4e, 0x4e, 0x07};
    public static final String TEXT       = "31 19 10 07 01 13 51 25 09 01 00 00 00 00 33 04 00 00 5a 00 00 00 eb 0d 00 00 64 eb";

    public CryptoTest() throws DecoderException {
    }

    @Test
    public void testDecoder() throws Exception {
        byte[] decryptedText = Crypto.decrypt(TEXT_RAW, SEMTECH_DEFAULT_KEY);

        printTheByteToString(decryptedText);
        Assert.assertEquals("decrypted text does not match", TEXT, decryptedText);
    }

    /**
     * Method for prin in command line byte array. For debug necessary
     * @param b - input byte array
     */
    private void printTheByteToString (byte[] b) {
        for (byte val : b) {
            System.out.print(String.format("%02x ", val));
        }
    }
}
于 2019-12-28T13:53:53.127 回答