赞
踩
默认读者对LoRaWAN有一定的了解,此文将着重讲解如何加/解密。
上行PHY帧格式:
| Preamble | PHDR | PHDR_CRC | PHYPayload | CRC |
|---|
下行PHY帧格式:
| Preamble | PHDR | PHDR_CRC | PHYPayload |
|---|
一般我们拿到的原始数据就是PHYPayload,其结构如下:
| bytes | 1 | 1…M | 4 |
|---|---|---|---|
| PHYPayload | MHDR | MACPayload | MIC |
MHDR:MAC头中指定了消息类型(MType)和帧编码所遵循的LoRaWAN规范的主版本号(Major)
MACPayload:MAC载荷,也就是所谓的“数据帧”,包含:帧头(FHDR)、端口(FPort)以及帧载荷
(FRMPayload),其中端口和帧载荷是可选的
MIC:消息校验码
| FHDR(帧头) | FPort(端口,可选) | FRMPayload(载荷,可选) |
|---|
| bytes | 4 | 1 | 2 | 0…15 |
|---|---|---|---|---|
| FHDR | DevAddr | FCtrl | FCnt | FOpts |
其中FCtrl的低4字节表示FOpts长度
如果帧载荷字段不为空,端口字段必须体现出来
端口字段有体现时,若FPort的值为0表示FRMPayload只包含了MAC命令
载荷数据需要进行加密,这里并不是直接对载荷数据进行加密,而是加密事先定义好的数据块,最后利用算出来的数据块与载荷数据异或来实现加/解密
MIC用来校验消息的完整性
采用AES128_ECB加密,密钥K根据不同的FPort来使用:
| FPort | K |
|---|---|
| 0 | NwkSKey |
| 1…255 | AppSKey |
算法定义了一个块序列Ai,i从1到k,k = ceil( len( FRMPayload) / 16 ),ceil为向上取整函数
| bytes | 1 | 4 | 1 | 4 | 4 | 1 | 1 |
|---|---|---|---|---|---|---|---|
| Ai | 0x01 | 4 * 0x00 | Dir | DevAddr | FCntUp or FCntDown | 0x00 | i |
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进行加解密
{ "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; }
采用CMAC ,即分组密码的消息认证码算法。具体公式如下:
cmac = aes128_cmac(NwkSKey, B0 | msg) MIC = cmac[0…3]
msg = MHDR | FHDR | FPort | FRMPayload
块B0的定义如下:
| bytes | 1 | 4 | 1 | 4 | 4 | 1 | 1 |
|---|---|---|---|---|---|---|---|
| B0 | 0x49 | 4 * 0x00 | Dir | DevAddr | FCntUp or FCntDown | 0x00 | len(msg) |
Dir:上行帧时为0,在下行帧时为1
FCnt:FCnt在数据帧中占2字节,需要补两个0,大小端同原始数据PHYPayload
{
"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
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; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。