2

I'm trying to implement my own LoraWAN server for personal research. I'm utilizing Semtech's LNS protocol and Basic Station as Gateway server. Now when a device tries to perform an OTAA join I receive the message and try to send back a Join-Accept response. But I'm having trouble with the MIC calculation and key derivation for the AppSKey and NwkSKey.

From the docs I must calculate the MIC of a Join-Accept message like

cmac = aes128_cmac(NwkKey, MHDR | JoinNonce | NetID | DevAddr | DLSettings | RxDelay | CFList)
MIC = cmac[0..3]

The code below should exactly do what the documentation says, join the message parts and calculate the cmac hash over it. But when I try to calculate the MIC using some example data taken from this website

I'm using a combination of Java and Clojure (mostly Clojure but I'm using the bouncycastle library for the cryptography part).

This is the example code:

  (let [;; Example data
        NwkKey      [0xB6 0xB5 0x3F 0x4A 0x16 0x8A 0x7A 0x88 0xBD 0xF7 0xEA 0x13 0x5C 0xE9 0xCF 0xCA]
        MHDR        (unchecked-byte 0x20)
        JoinNonce   [0xe5 0x06 0x3a]
        NetID       [0x00 0x00 0x13]
        DevAddr     [0x26 0x01 0x2e 0x43]
        DLSettings  (unchecked-byte 0x03)
        RxDelay     (unchecked-byte 0x01)
        CFList      [0x18 0x4f 0x84  0xe8 0x56 0x84  0xb8 0x5e 0x84  0x88 0x66 0x84  0x58 0x6e 0x84  0x00]]
        ;; prepare data for hashing
        NwkKey'     (to-byte-array NwkKey)    ; 16 bytes
        buffer      (ByteBuffer/allocate 32)
        CFList'     (concat (reverse (subvec CFList 0 3))   ; 16 bytes, correct endianess to LE
                            (reverse (subvec CFList 3 6))
                            (reverse (subvec CFList 6 9))
                            (reverse (subvec CFList 9 12))
                            (reverse (subvec CFList 12 15))
                            [(get CFList 15)])]
    ;; fill buffer with data
    (.put buffer MHDR)                                    ; 1 bytes
    (.put buffer (to-byte-array  (reverse JoinNonce)))    ; 3 bytes, little endian
    (.put buffer (to-byte-array  (reverse NetID)))        ; 3 bytes, little endian
    (.put buffer (to-byte-array  (reverse DevAddr)))      ; 4 bytes, little endian
    (.put buffer DLSettings)                              ; 1 bytes
    (.put buffer RxDelay)                                 ; 1 bytes
    (.put buffer (to-byte-array CFList'))                 ; 16 bytes, already LE (see abvove)

    ;; padding: 1 byte with value 1 then n bytes with valu 0 until a 128 bit block is full (RFC 4493)
    (.put buffer (unchecked-byte 2r1000000))
    (.put buffer (to-byte-array (map (constantly 0x00) (range 2))))

    ;; content of the byte buffer before calculating the cmac hash
    (println (string/join " " (map byte->xstr (.array buffer))))

    ;; get the first four bytes of the hash, this should be the MIC
    (println (string/join " " (map byte->xstr (take 4 (cmac NwkKey' (.array buffer))))))))

The example should yield a MIC with the value 55 12 1d e0 but instead it gives me 7d fd eb 7b. Looking into the buffer it has the value 20 3a 06 e5 13 00 00 43 2e 01 26 03 01 84 4f 18 84 56 e8 84 5e b8 84 66 88 84 6e 58 00 40 00 00 which seems correct.

The byte->xstr function is a helper that takes a single byte and converts it to a string with its value in hexadecimal.

I already tried to fiddle with the padding in case bouncycastle does some padding in the background which I'm unaware off but still I don't get the correct result.

Now I'm kinda lost here because I can't find my error. I hope you can spot my mistake and point me in the correct direction to fix it.

Edit:

For completeness here the function that prints the value of a byte as hexadecimal:

(defn byte->xstr
  "Convert a byte to a hexadecimal string representation (2 digits fixed)."
  [b]
  (format "%02x" b))
7tupel
  • 73
  • 6
  • What is the data before/after the `byte->xstr` function? Where is that defined? What libraries & namespaces are you using? – Alan Thompson Jun 25 '21 at 15:15
  • @AlanThompson You can see the value of the buffer in my description (already converted to little endian format) and the values of the single parts (in big endian format). As I wrote I'm using the bouncycastle library to calculate the cmac (from the module `org.bouncycastle.crypto.macs` to be precise). I added the full code of the `byte->xstr` function but as I wrote it is just a simple conversion for debugging/printing. – 7tupel Jun 26 '21 at 06:09
  • have you looked at buddy-core? i’m usually loathe to recommend libraries but i replaced 50 lines of handcranked ported-Java crypto code with a couple of lines of buddy yesterday to solve some JWT issues, so… they also use bouncycastle as the underlyer iirc, but they’ve done the job of making an idiomatic clojure interface. – pete23 Jun 26 '21 at 07:48
  • i'd also suggest outputting each different stage of your reference implementation, then you can double check what you got wrong. from what you're saying with the buffer "seems correct", the core of the problem is that you're expecting cmac to return something different? – pete23 Jun 26 '21 at 10:21
  • so where is the cmac code? – pete23 Jun 26 '21 at 10:23

0 Answers0