This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

AES CCM encryption by S132 timeslot API

Hi,

I have tested AES-CCM of nRF52832 with reference to the following answered questions.

devzone.nordicsemi.com/.../

devzone.nordicsemi.com/.../

devzone.nordicsemi.com/.../

I have confirmed that the NIST example would produce correct results.(Example1 and Example2 in NIST documents)

Reference : NIST Special Publication 800-38C;
Test environment : Java + BouncyCastle(bcprov-jdk15on-158.jar);
Test Input
  ・Key      :01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01;
  ・Nonce    :00 00 00 00 00 02 02 02 02 02 02 02 02;
  ・MACLength:4byte Fix;
  ・AssociatedDataString:empty;
  ・PlainText:00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ; 
OutputData  :0d af 4b 8e 52 50 3a 6e 91 f4 d8 34 26 9b 4e 74;
MIC   :d5 6e b7 75

However, when I encrypted AES-CCM of nRF52832,the MIC did not match the expected results.The result is as follows.(Data structure for unencrypted packet is specified by LENGTH / PAYLOAD.)

Reference : nRF52832 Product Specification v1.3, 29 CCM - AES CCM mode encryption;  
Test envirionment : Nordic nRF52832 DK board and nRF5 SDK v13.1.0;  
Test Input
  ・Key      :01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 (KEY);
  ・Nonce    :00 00 00 00 00 02 02 02 02 02 02 02 02 (PKTCTR:5byte, IV:8byte);
  ・MACLength:4byte Fix;
  ・AssociatedDataString:"There are no registers to set associated data structure.";
  ・PlainText:00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f;
 Output  Data  :0d af 4b 8e 52 50 3a 6e 91 f4 d8 34 26 9b 4e 74;
 MIC   :6d da 11 ad;

The encrypted data matched, but the MIC is wrong.

How to get a correct MIC result by AES CCM mode encryption of nRF52832 correct? Is it unnecessary to set AssociatedDataString ?

Thank you in advance.

Parents
  • Hello, I too am trying to use the nRF51 AES CCM hardware "manually" (that is, not using the BLE soft-device, not even in a BLE context at all, but for securing communications via a different communications medium). I have run into the same issue of matching-ciphertext but differing MIC. I have finally figured this out, specifically how to decrypt and authenticate data that is encrypted by the nRF AES CCM hardware, using software crypto libraries on my computer.

    I've determined that the nRF AES CCM hardware is not a generic AES CCM device, but instead is targeting the BLE way of using AES CCM. Specifically, the "formatting function" that is used to generate blocks b_0, b_1, ... for the MIC calculation is not part of the CCM specification, but instead specified by the BLE spec (Core v5.0: Vol 6: Part E: Section 2: "CCM"). To correctly compute the MIC using software crypto libraries, it is necessary to use one byte of "adata" or "additional data". The BLE spec says that byte is used as the third byte of the block b_1 (Table 2.3 in the section mentioned before), and must be equal to the "data channel PDU header's first octet with NESN, SN and MD bits masked to 0". Since I'm not using bluetooth, I was puzzled about how this byte is determined by the nRF AES CCM hardware and used in the MIC calculation. I've now determined that it is simply the first byte of the unencrypted packet you point to with NRF_CCM->INPTR, but masked with 0xE3 (to zero the NESN, SN, and MD bits).


    Here's my sample code for the above test values (nRF51 side):

    typedef struct aesCcmData_struct { 
      uint8_t key[16];
      uint8_t packetCounter[5]; // top bit ignored
      uint8_t reserved[3];
      uint8_t directionBit; // bit 0 = direction bit
      uuint8_t iv[8];
    } aesCcmData_s;
    
    aesCcmData_s ccmData = {
      key = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, 
      packetCounter = {0,0,0,0,0}, 
      reserved = {0}, 
      directionBit = 0, 
      iv = {2,2,2,2,2,2,2,2}, 
    };
    
    uint8_t pktInput[] = {
      0, // header - THIS DETERMINES THE BYTE OF ADATA YOU MUST ADD TO YOUR SOFTWARE DECRYPTION/VERIFICTION CODE
      16, // length
      0, // RFU
      // here are the 16 payload bytes:
      0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
    };
    uint8_t pktOutput[4 + sizeof(pktInput)] = {0};
    uint8_t scratch[16 + sizeof(pktOutput)] = {0}; // apparently this should be 16 + maxpacketlength?
    
    NRF_CCM->ENABLE = 2; // Enable CCM 
    NRF_CCM->SHORTS = 1; // Enable shortcut between ENDKSGEN and CRYPT 
    NRF_CCM->MODE = 0; // ENcrypt 
    NRF_CCM->CNFPTR = (uintptr_t) ccmData; 
    NRF_CCM->INPTR = (uintptr_t) pktInput; 
    NRF_CCM->OUTPTR = (uintptr_t) pktOutput; 
    NRF_CCM->SCRATCHPTR = (uintptr_t) scratch;
    
    NRF_CCM->EVENTS_ENDKSGEN = 0;
    NRF_CCM->EVENTS_ENDCRYPT = 0;
    NRF_CCM->EVENTS_ERROR = 0;
    NRF_CCM->TASKS_KSGEN = 1;
    
    while (!(NRF_CCM->EVENTS_ENDCRYPT || NRF_CCM->EVENTS_ERROR)) {
    }


    After the CCM hardware runs, our buffers have these contents:

    ccmData:   01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 00 00 00 00 00 00 00 00 02 02 02 02 02 02 02 02
    pktInput:  00 10 00 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 00 00 00 00 00 00 00
    pktOutput: 00 14 00 0D AF 4B 8E 52 50 3A 6E 91 F4 D8 34 26 9B 4E 74 6D DA 11 AD 00 00 00 00 00 00 00 00

    We can see that the output packet has chipertext bytes [0D AF ... 4E 74] and MAC bytes [6D DA 11 AD] (which match the nRF test data above).


    Now we go to do the same operation on the computer in software. I'm using Python and the Cryptodome library. Here's the code to correctly compute the MAC:

    #!/usr/bin/env python
    
    from Cryptodome.Cipher import AES
    import binascii
    
    hdr_byte = 0x00 # THIS BYTE MUST MATCH THE FIRST BYTE OF THE nRF INPUT PACKET (& with 0xE3 of course)
    hdr = binascii.a2b_hex('%02X' % (hdr_byte & 0xE3))
    plaintext = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
    key = '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
    nonce = '\x00\x00\x00\x00\x00\x02\x02\x02\x02\x02\x02\x02\x02'
    print "key:   ", repr(binascii.b2a_hex(key))
    print "nonce: ", repr(binascii.b2a_hex(nonce))
    print "plain: ", repr(binascii.b2a_hex(plaintext))
    
    cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=4, assoc_len=len(hdr))
    cipher.update(hdr)
    ciphertext = cipher.encrypt(plaintext)
    mac = cipher.digest()
    print "cipher:", repr(binascii.b2a_hex(ciphertext))
    print ""
    print "mac:   ", repr(binascii.b2a_hex(mac)), "(computed in python)"
    #print "mac:    '6dda11ad' (computed by nRF HW)"
    print ""
    
    msg = nonce, hdr, ciphertext, mac
    
    # We assume that the tuple ``msg`` is transmitted to the receiver:
    
    nonce, hdr, ciphertext, mac = msg
    key = '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
    cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=4, assoc_len=len(hdr))
    cipher.update(hdr)
    plaintext = cipher.decrypt(ciphertext)
    print "plain: ", repr(binascii.b2a_hex(plaintext))
    try:
        cipher.verify(mac)
        print "The message is authentic: hdr=%s, pt=%s" % (repr(binascii.b2a_hex(hdr)), repr(binascii.b2a_hex(plaintext)))
    except ValueError:
        print "Key incorrect or message corrupted"
    

    If you don't do the `cipher.update(hdr)` to add the header byte, then the decryption is correct but the MAC doesn't match what the nRF CCM hardware calculated.

    Here is the output of the python script. We can see that the plaintext is correctly computed, and the MAC is computed correctly (at least it matches how the nRF hw computed the MAC).

    $ python ccm_test.py 
    key:    '01010101010101010101010101010101'
    nonce:  '00000000000202020202020202'
    plain:  '000102030405060708090a0b0c0d0e0f'
    cipher: '0daf4b8e52503a6e91f4d834269b4e74'
    
    mac:    '6dda11ad' (computed in python)
    
    plain:  '000102030405060708090a0b0c0d0e0f'
    The message is authentic: hdr='00', pt='000102030405060708090a0b0c0d0e0f'

    I hope this helps other folks to figure out how to use the nRF AES CCM hardware outside of a BLE context!

Reply
  • Hello, I too am trying to use the nRF51 AES CCM hardware "manually" (that is, not using the BLE soft-device, not even in a BLE context at all, but for securing communications via a different communications medium). I have run into the same issue of matching-ciphertext but differing MIC. I have finally figured this out, specifically how to decrypt and authenticate data that is encrypted by the nRF AES CCM hardware, using software crypto libraries on my computer.

    I've determined that the nRF AES CCM hardware is not a generic AES CCM device, but instead is targeting the BLE way of using AES CCM. Specifically, the "formatting function" that is used to generate blocks b_0, b_1, ... for the MIC calculation is not part of the CCM specification, but instead specified by the BLE spec (Core v5.0: Vol 6: Part E: Section 2: "CCM"). To correctly compute the MIC using software crypto libraries, it is necessary to use one byte of "adata" or "additional data". The BLE spec says that byte is used as the third byte of the block b_1 (Table 2.3 in the section mentioned before), and must be equal to the "data channel PDU header's first octet with NESN, SN and MD bits masked to 0". Since I'm not using bluetooth, I was puzzled about how this byte is determined by the nRF AES CCM hardware and used in the MIC calculation. I've now determined that it is simply the first byte of the unencrypted packet you point to with NRF_CCM->INPTR, but masked with 0xE3 (to zero the NESN, SN, and MD bits).


    Here's my sample code for the above test values (nRF51 side):

    typedef struct aesCcmData_struct { 
      uint8_t key[16];
      uint8_t packetCounter[5]; // top bit ignored
      uint8_t reserved[3];
      uint8_t directionBit; // bit 0 = direction bit
      uuint8_t iv[8];
    } aesCcmData_s;
    
    aesCcmData_s ccmData = {
      key = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, 
      packetCounter = {0,0,0,0,0}, 
      reserved = {0}, 
      directionBit = 0, 
      iv = {2,2,2,2,2,2,2,2}, 
    };
    
    uint8_t pktInput[] = {
      0, // header - THIS DETERMINES THE BYTE OF ADATA YOU MUST ADD TO YOUR SOFTWARE DECRYPTION/VERIFICTION CODE
      16, // length
      0, // RFU
      // here are the 16 payload bytes:
      0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
    };
    uint8_t pktOutput[4 + sizeof(pktInput)] = {0};
    uint8_t scratch[16 + sizeof(pktOutput)] = {0}; // apparently this should be 16 + maxpacketlength?
    
    NRF_CCM->ENABLE = 2; // Enable CCM 
    NRF_CCM->SHORTS = 1; // Enable shortcut between ENDKSGEN and CRYPT 
    NRF_CCM->MODE = 0; // ENcrypt 
    NRF_CCM->CNFPTR = (uintptr_t) ccmData; 
    NRF_CCM->INPTR = (uintptr_t) pktInput; 
    NRF_CCM->OUTPTR = (uintptr_t) pktOutput; 
    NRF_CCM->SCRATCHPTR = (uintptr_t) scratch;
    
    NRF_CCM->EVENTS_ENDKSGEN = 0;
    NRF_CCM->EVENTS_ENDCRYPT = 0;
    NRF_CCM->EVENTS_ERROR = 0;
    NRF_CCM->TASKS_KSGEN = 1;
    
    while (!(NRF_CCM->EVENTS_ENDCRYPT || NRF_CCM->EVENTS_ERROR)) {
    }


    After the CCM hardware runs, our buffers have these contents:

    ccmData:   01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 00 00 00 00 00 00 00 00 02 02 02 02 02 02 02 02
    pktInput:  00 10 00 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 00 00 00 00 00 00 00
    pktOutput: 00 14 00 0D AF 4B 8E 52 50 3A 6E 91 F4 D8 34 26 9B 4E 74 6D DA 11 AD 00 00 00 00 00 00 00 00

    We can see that the output packet has chipertext bytes [0D AF ... 4E 74] and MAC bytes [6D DA 11 AD] (which match the nRF test data above).


    Now we go to do the same operation on the computer in software. I'm using Python and the Cryptodome library. Here's the code to correctly compute the MAC:

    #!/usr/bin/env python
    
    from Cryptodome.Cipher import AES
    import binascii
    
    hdr_byte = 0x00 # THIS BYTE MUST MATCH THE FIRST BYTE OF THE nRF INPUT PACKET (& with 0xE3 of course)
    hdr = binascii.a2b_hex('%02X' % (hdr_byte & 0xE3))
    plaintext = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
    key = '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
    nonce = '\x00\x00\x00\x00\x00\x02\x02\x02\x02\x02\x02\x02\x02'
    print "key:   ", repr(binascii.b2a_hex(key))
    print "nonce: ", repr(binascii.b2a_hex(nonce))
    print "plain: ", repr(binascii.b2a_hex(plaintext))
    
    cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=4, assoc_len=len(hdr))
    cipher.update(hdr)
    ciphertext = cipher.encrypt(plaintext)
    mac = cipher.digest()
    print "cipher:", repr(binascii.b2a_hex(ciphertext))
    print ""
    print "mac:   ", repr(binascii.b2a_hex(mac)), "(computed in python)"
    #print "mac:    '6dda11ad' (computed by nRF HW)"
    print ""
    
    msg = nonce, hdr, ciphertext, mac
    
    # We assume that the tuple ``msg`` is transmitted to the receiver:
    
    nonce, hdr, ciphertext, mac = msg
    key = '\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01'
    cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=4, assoc_len=len(hdr))
    cipher.update(hdr)
    plaintext = cipher.decrypt(ciphertext)
    print "plain: ", repr(binascii.b2a_hex(plaintext))
    try:
        cipher.verify(mac)
        print "The message is authentic: hdr=%s, pt=%s" % (repr(binascii.b2a_hex(hdr)), repr(binascii.b2a_hex(plaintext)))
    except ValueError:
        print "Key incorrect or message corrupted"
    

    If you don't do the `cipher.update(hdr)` to add the header byte, then the decryption is correct but the MAC doesn't match what the nRF CCM hardware calculated.

    Here is the output of the python script. We can see that the plaintext is correctly computed, and the MAC is computed correctly (at least it matches how the nRF hw computed the MAC).

    $ python ccm_test.py 
    key:    '01010101010101010101010101010101'
    nonce:  '00000000000202020202020202'
    plain:  '000102030405060708090a0b0c0d0e0f'
    cipher: '0daf4b8e52503a6e91f4d834269b4e74'
    
    mac:    '6dda11ad' (computed in python)
    
    plain:  '000102030405060708090a0b0c0d0e0f'
    The message is authentic: hdr='00', pt='000102030405060708090a0b0c0d0e0f'

    I hope this helps other folks to figure out how to use the nRF AES CCM hardware outside of a BLE context!

Children
No Data
Related