ZigBee Green Power AES-CCM

2

I'm currently working on an very simple ZigBee Green Power (GP) end device implementation. Currently, my problem is how to generate a correct MIC (message identification code).
I already have an AES-CCM implementation and tested it with the test vectors for ZigBee Green Power from the ZigBee Pro Specification. The problem is that the result I get isn't correct (it's not the same as in the specification).
I also tried to verify my implementation with other test vectors (non-GP ones) from the specification and the result was correct. So, my algorithm seems to be correct.

Is there a difference in calculating the GP MIC code to the normal ZigBee MIC?? Maybe someone has another implementation which is working for ZigBee GP??

Additional information:

The ZigBee specification I used is available here: https://zigbeealliance.org/wp-content/uploads/2019/11/docs-05-3474-21-0csg-zigbee-specification.pdf#

The test vectors which are working are in Annex C.3 of the above document.

The test vectors for the Green Power version are in Annex H.2. For the sake of completeness, the parameters are as follows (Annex H.2.3):

Key: 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCa, 0xCb, 0xCc, 0xCd, 0xCe, 0xCf
NWK Frame Control: 0x8C
GPD Src ID: 0x87654321
Security Frame Counter: 0x00000002
GPD Command ID: 0x02
No data Payload

The specification says that the nonce is constructed like this:

Nonce: SRC ID || SRC ID || Frame Counter || 0x05
Nonce: 0x21 0x43 0x65 0x87 0x21 0x43 0x65 0x87 0x02 0x00 0x00 0x00 0x05

For the calculation an 'a' value is needed:

a = Header || Payload
Header = NWK FC || NWK Ext FC || SRC ID || Frame Counter
Header = 0x8C 0x10 0x21 0x43 0x65 0x87 0x02 0x00 0x00 0x00
Payload = GPD Command ID = 0x20
a = 0x8C 0x10 0x21 0x43 0x65 0x87 0x02 0x00 0x00 0x00 0x20

Finally, for the calculation of the AES-CBC algorithm the following paramters are needed:

length(a) = 0x0B
L(a) = 0x00 0x0B (big endian encoding of length(a))
AddAuthData = L(a) || a || padding
AddAuthData = 0x00 0x0B 0x8C 0x10 0x21 0x43 0x65 0x87 0x02 0x00 0x00 0x00 0x20 0x00 0x00 0x00
Flags = 0x49
B0 = Flags || Nonce || padding
B0 = 0x49 0x21 0x43 0x65 0x87 0x21 0x43 0x65 0x87 0x02 0x00 0x00 0x00 0x05 0x00 0x00

For generating the MIC code, the algorithm is used with the encryption key and the B0 ... Bi blocks (each 16 bytes). B0 is created using the nonce (see above). Usually AddAuthData and the message block is concatenated. But for the green power version, no message block is available as far as I know. Therefore, B1 ... Bn is created using the AddAuthData only. Am I right here??

B1 = AddAuthData
B1 = 0x00 0x0B 0x8C 0x10 0x21 0x43 0x65 0x87 0x02 0x00 0x00 0x00 0x20 0x00 0x00 0x00

The AES-CBC algorithm E is used with each Bi block. Bi is xor'd with the previous generated Xi result. The initial vector X0 is an all zero bits block with a length of 16 bytes. X_i+1 = E(Key, Xi XOR Bi) for i = 0 ... N

The result of the AES calculation is a 16 byte value. But only the 4 leftmost bytes are used. And the expected result is:

U = 0xCF 0x78 0x7E 0x72

But I get:

X2 = 02 1C 9F 9C 40 3A 27 B4 9A 31 64 EA 17 CF 69 D3
U = 0x02 0x1C 0x9F 0x9C
encryption
aes
zigbee
asked on Stack Overflow Sep 29, 2017 by Andreas Auer • edited Feb 17, 2021 by Mr Robot

2 Answers

2

Here is a detailed AES-128-CCM* encryption with Python source code: ZigBee frame encryption with AES-128-CCM*

answered on Stack Overflow Mar 10, 2021 by Fifi
1

AES-CCM* is a combination of AES-CBC (authentication) and AES-CTR (encryption).

Authentication transformation runs AES-CBC over the 16B Bi buffers. B0 is built from Nonce and B1,B2,..,Bn are the result of parsing AuthData, whose definition is:

AuthData = AddAuthData || PlaintextData

Since you have no payload PlaintextData is empty and then AuthData = AddAuthData in your case.

Note that your B0 definition is not correct. There is no padding after the Nonce but the length of m (i.e. the payload length). As you have no payload (l(m)=0) the result is the same as padding here. But the correct definition of B0 is:

B0 = Flags || Nonce || l(m)

So here are the Bi buffers (hex format without the 0x prefix):

B0 = 49 21 43 65 87 21 43 65 87 02 00 00 00 05 00 00
B1 = 00 0B 8C 10 21 43 65 87 02 00 00 00 20 00 00 00

With the proper 128b key, the AES-CBC steps produces the following:

X0 = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
B0 = 49 21 43 65 87 21 43 65 87 02 00 00 00 05 00 00

X1 = 14 5B B8 1F DE D7 99 45 9D 9B 77 51 B7 31 A3 C1
B1 = 00 0B 8C 10 21 43 65 87 02 00 00 00 20 00 00 00

X2 = 02 1C 9F 9C 40 3A 27 B4 9A 31 64 EA 17 CF 69 D3

Summing it up, your are correct until now, but you are just not done yet! What you get is not U but the T authentication tag (not encrypted) : T = 02 1C 9F 9C. To obtain U you should run the encryption transformation (even though you have no payload to encrypt).

Encryption transformation uses AES-CTR. The counter is called Ai and defined as:

Ai = Flags || Nonce || Counter

Flags is pretty much the same as the one used for B0 calculation, but just keeping the 3 least significant bits. Then Flags = 0x01 and the Ai are:

A0 = 01 21 43 65 87 21 43 65 87 02 00 00 00 05 00 00
A1 = 01 21 43 65 87 21 43 65 87 02 00 00 00 05 00 01
A2 = 01 21 43 65 87 21 43 65 87 02 00 00 00 05 00 02
...

A1,A2,..,An are used the encrypt the payload which you are not interested in. But A0 is used to generate the U encrypted tag. The operation is basically U = E(Key, A0) xor T:

       A0 = 01 21 43 65 87 21 43 65 87 02 00 00 00 05 00 00
E(Key,A0) = CD 64 E1 EE 37 25 CF 25 AD 84 00 F0 5C B4 9B 03
        T = 02 1C 9F 9C
        U = CF 78 7E 72

And here you have the expected result U = 0xCF 0x78 0x7E 0x72.

answered on Stack Overflow Jan 4, 2019 by Lrnt Gr • edited Jan 4, 2019 by Lrnt Gr

User contributions licensed under CC BY-SA 3.0