LoRaWAN数据解密实例(附C源码)

1 篇文章 0 订阅
1 篇文章 0 订阅

前提

默认读者对LoRaWAN有一定的了解,此文将着重讲解如何加/解密。


帧格式

PHY 帧格式

上行PHY帧格式:

PreamblePHDRPHDR_CRCPHYPayloadCRC

下行PHY帧格式:

PreamblePHDRPHDR_CRCPHYPayload



MAC帧格式

一般我们拿到的原始数据就是PHYPayload,其结构如下:

bytes11…M4
PHYPayloadMHDRMACPayloadMIC

MHDR:MAC头中指定了消息类型(MType)和帧编码所遵循的LoRaWAN规范的主版本号(Major)

MACPayload:MAC载荷,也就是所谓的“数据帧”,包含:帧头(FHDR)、端口(FPort)以及帧载荷
(FRMPayload),其中端口和帧载荷是可选的

MIC:消息校验码



数据帧

FHDR(帧头)FPort(端口,可选)FRMPayload(载荷,可选)

FHDR(帧头)

bytes4120…15
FHDRDevAddrFCtrlFCntFOpts

其中FCtrl的低4字节表示FOpts长度

FPort(端口)

如果帧载荷字段不为空,端口字段必须体现出来

端口字段有体现时,若FPort的值为0表示FRMPayload只包含了MAC命令



加/解密

载荷数据需要进行加密,这里并不是直接对载荷数据进行加密,而是加密事先定义好的数据块,最后利用算出来的数据块与载荷数据异或来实现加/解密

MIC用来校验消息的完整性



FRMPayload加/解密过程

采用AES128_ECB加密,密钥K根据不同的FPort来使用:

FPortK
0NwkSKey
1…255AppSKey

算法定义了一个块序列Ai,i从1到k,k = ceil( len( FRMPayload) / 16 ),ceil为向上取整函数

bytes1414411
Ai0x014 * 0x00DirDevAddrFCntUp or
FCntDown
0x00i

Dir:上行帧时为0,在下行帧时为1

FCnt:FCnt在数据帧中占2字节,需要补两个0,大小端同原始数据PHYPayload


对Ai进行加密可以得到序列S

Si = aes128_encrypt(K, Ai) for i = 1…k

S = S1 | S2 | … | Sk


通过与S异或计算对载荷数据FRMPayload进行加解密



FRMPayload解密实例

{
    "phyPayload": "80 86 96 72 01 80 1F 09 08 DD 84 E1 6A 81 E9 B5 99 5C C5 D5 CF 77 5E 39",
    "phyPayloadJSON": {
        "mhdr": {
            "mType": "ConfirmedDataUp",
            "major": "LoRaWANR1"
        },
        "macPayload": {
            "fhdr": {
                "devAddr": "01729686",
                "fCtrl": {
                    "adr": true,
                    "adrAckReq": false,
                    "ack": false,
                    "fPending": false,
                    "classB": false
                },
                "fCnt": 2335,
                "fOpts": null
            },
            "fPort": 8,
            "DecryptData": [
                "63 71 A5 EB 10 00 00 00 32 00 00"
            ]
        },
        "mic": "cf775e39"
    },
    "AppSKey": "e022c95865de731b94cab0e19e02992b",
    "NwkSKey": "0bfd388aa201cc2b63f78a1d8efb58aa"
}

通过分析帧格式,可以得到如下重要信息:

AppSKey:e022c95865de731b94cab0e19e02992b

FRMPayload:DD 84 E1 6A 81 E9 B5 99 5C C5 D5

DevAddr:86 96 72 01

fCnt:1F 09

Dir:这里是上行帧,所以Dir=0


这里的FRMPayload的数据长度为11,所以只用加密一块数据,即A1,经过简单拼凑可以得到A1如下:

01 00 00 00 00 00 86 96 72 01 1F 09 00 00 00 01

对A1进行AES128_ECB加密得到S=S1如下:

BE F5 44 81 91 E9 B5 99 6E C5 D5 86 2F 1B 4C 42


S与FRMPayload异或可以得到如下结果:

BE F5 44 81 91 E9 B5 99 6E C5 D5 86 2F 1B 4C 42

​ XOR

DD 84 E1 6A 81 E9 B5 99 5C C5 D5

​ =

63 71 A5 EB 10 00 00 00 32 00 00,与DecryptData一致


附上C语言代码,基于OpneSSL

#include <stdio.h>
#include <string.h>
#include <openssl/aes.h>

int main(int arg, char *argv[])
{
    unsigned char A1[16]  = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x96, 0x72, 0x01 ,0x1f, 0x09, 0x00, 0x00, 0x00, 0x01};
    unsigned char AppSKey[16] = {0xe0, 0x22, 0xc9, 0x58, 0x65, 0xde, 0x73, 0x1b, 0x94, 0xca, 0xb0, 0xe1, 0x9e, 0x02, 0x99, 0x2b};
    unsigned char S[16] = {0};
    AES_KEY encrypt_key;

    // 生成加密密钥  必须128bit/16BYTE
    AES_set_encrypt_key(AppSKey, 128, &encrypt_key);

    // 加密 一次执行一个块即16BYTE
    AES_ecb_encrypt(A1, S, &encrypt_key, AES_ENCRYPT);
    printf("S: ");
    for (size_t i = 0; i < 16; i++)
    {
        printf("%02X ", S[i]);
    }
    printf("\r\n");
    return 0;
}



MIC计算过程

采用CMAC ,即分组密码的消息认证码算法。具体公式如下:

cmac = aes128_cmac(NwkSKey, B0 | msg) MIC = cmac[0…3]

msg = MHDR | FHDR | FPort | FRMPayload

块B0的定义如下:

bytes1414411
B00x494 * 0x00DirDevAddrFCntUp or
FCntDown
0x00len(msg)

Dir:上行帧时为0,在下行帧时为1

FCnt:FCnt在数据帧中占2字节,需要补两个0,大小端同原始数据PHYPayload



MIC计算实例

{
    "phyPayload": "80 86 96 72 01 80 1F 09 08 DD 84 E1 6A 81 E9 B5 99 5C C5 D5 CF 77 5E 39",
    "NwkSKey": "0bfd388aa201cc2b63f78a1d8efb58aa"
}

通过分析帧格式,可以得到如下重要信息:

NwkSKey:0bfd388aa201cc2b63f78a1d8efb58aa

msg:80 86 96 72 01 80 1F 09 08 DD 84 E1 6A 81 E9 B5 99 5C C5 D5

mic:CF 77 5E 39

DevAddr:86 96 72 01

fCnt:1F 09

Dir:这里是上行帧,所以Dir=0


可以得出B0:

49 00 00 00 00 00 86 96 72 01 1f 09 00 00 00 14

进一步可以得出实际参与运算的MSG = B0 | msg:

49 00 00 00 00 00 86 96 72 01 1f 09 00 00 00 14 80 86 96 72 01 80 1F 09 08 DD 84 E1 6A 81 E9 B5 99 5C C5 D5


通过上一节的公式可以得到cmac = aes128_cmac(NwkSKey, B0 | msg):

CF 77 5E 39 6E 69 9B 4E 33 15 40 18 55 77 A6 51,前4字节与phyPayload中的mic一致


附上C语言代码,基于OpneSSL

#include <openssl/cmac.h>
#include <openssl/evp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int arg, char *argv[])
{
    unsigned char NwkSKey[16] = {0X0b, 0Xfd, 0X38, 0X8a, 0Xa2, 0X01, 0Xcc, 0X2b, 0X63, 0Xf7, 0X8a, 0X1d, 0X8e, 0Xfb, 0X58, 0Xaa};
    unsigned char b0[16] = {0X49, 0X00, 0X00, 0X00, 0X00, 0X00, 0x86, 0x96, 0x72, 0x01 ,0x1f, 0x09, 0x00, 0x00, 0x00, 0X14};
    unsigned char msg[20]= {0x80, 0x86, 0x96, 0x72, 0x01, 0x80, 0x1F, 0x09, 0x08, 0xDD, 0x84, 0xE1, 0x6A, 0x81, 0xE9, 0xB5, 0x99, 0x5C, 0xC5, 0xD5};
    unsigned char MSG[36];
    memcpy(MSG, b0, 16);
    memcpy(MSG + 16, msg, 20);
    
    CMAC_CTX* cmac_ctx = CMAC_CTX_new();
    if (!cmac_ctx)
    {
        printf("Create CMAC_CTX error!\n");
        return -1;
    }

    const EVP_CIPHER* cipher = EVP_aes_128_cbc();
    if (!CMAC_Init(cmac_ctx, NwkSKey, 16, cipher, 0))
    {
        CMAC_CTX_free(cmac_ctx);
        printf("CMAC Init error!\n");
        return -1;
    }

    if(!CMAC_Update(cmac_ctx, MSG, 36))
    {
        CMAC_CTX_free(cmac_ctx);
        printf("CMAC Update error!\n");
        return -1;
    }

    size_t cmac_len;
    uint8_t cmac[128];
    if (!CMAC_Final(cmac_ctx, cmac, &cmac_len)) 
    {
        printf("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]\n");
        return -1;
    }

    printf("derive key len[%ld]data:\n",cmac_len);
    for(int i = 0; i < cmac_len; i++)
    {
        printf("%02X ",cmac[i]);
    }
    printf("\n");

    CMAC_CTX_free(cmac_ctx);
    return 0;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值