1

We are developing an application that has to work with data that is enycrpted by LoraWan (https://www.lora-alliance.org)

We have already found the documentation of how they encrypt their data, and have been reading through it for the past few days (https://www.lora-alliance.org/sites/default/files/2018-04/lorawantm_specification_-v1.1.pdf) but currently still can't solve our problem.

We have to use AES 128-bit ECB decryption with zero-padding to decrypt the messages, but the problem is it's not working because the encrypted messages we are receiving are not long enough for AES 128 so the algorithm returns a "Data is not a complete block" exception on the last line.

An example key we receive is like this: D6740C0B8417FF1295D878B130784BC5 (not a real key). It is 32 characters long, so 32 bytes, but if treat it as hexadecimal, then it becomes 16 bytes long, which is what is needed for AES 128-bit. This is the code we use to convert the Hex from String:

public static string HextoString(string InputText)
{byte[] hex= Enumerable.Range(0, InputText.Length)
                 .Where(x => x % 2 == 0)
                 .Select(x => Convert.ToByte(InputText.Substring(x, 2), 16))
                 .ToArray();
return System.Text.Encoding.ASCII.GetString(hex);}

(A small thing to note for the above code is that we are not sure what Encoding to use, as we could not find it in the Lora documentation and they have not told us, but depending on this small setting we could be messing up our decryption (though we have tried all possible combinations, ascii, utf8, utf7, etc))

An example message we receive is: d3 73 4c which we are assuming is also in hexadecimal. This is only 6 bytes, and 3 bytes if we convert it from hexa to normal, compared to the 16 bytes we'd need minimum to match the key length.

This is the code for Aes 128 decrypt we are using:

private static string Aes128Decrypt(string cipherText, string key){

string decrypted = null;

var cipherPlainTextBytes = HexStringToByteArray(cipherText);
//var cipherPlainTextBytes = ForcedZeroPadding(HexStringToByteArray(cipherText));
var keyBytes = HexStringToByteArray(key);

using (var aes = new AesCryptoServiceProvider())
{
    aes.KeySize = 128;
    aes.Key = keyBytes;
    aes.Mode = CipherMode.ECB; 
    aes.Padding = PaddingMode.Zeros; 

    ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
    using (MemoryStream ms = new MemoryStream(cipherPlainTextBytes, 0, cipherPlainTextBytes.Length))
    {
        using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
        {
            using (StreamReader sr = new StreamReader(cs))
            {
                decrypted = sr.ReadToEnd();
            }
        }
    }
}
return decrypted;}

So obviously this is going to return "Data is an incomplete block" at sr.ReadToEnd().

As you can see from the example, in that one commented out line, we have also tried to "Pad" the text to the correct size with a full zero byte array of correct length (16 - cipherText), in which case the algorithm runs fine, but it returns complete gibberish and not the original text.

We already have tried all of the modes of operation and messed around with padding modes as well. They are not providing us with anything but a cipherText, and a key for that text. No Initialization vector either, so we are assuming we are supposed to be generating that every time (but for ECB it isn't even needed iirc)

What's more is, they are able to encrypt-decrypt their messages just fine. What is most puzzling about this is that I have been googling this for days now and I cannot find a SINGLE example on google where the CIPHERTEXT is shorter than the key during decryption.

Obviously I have found examples where the message they are Encrypting is shorter than what is needed, but that is what padding is for on the ENCRYPTION side (right?). So that when you then receive the padded message, you can tell the algorithm what padding mode was used to make it correct length, so then it can seperate the padding from the message. But in all of those cases the recieved message during decryption is of correct length.

So the question is - what are we doing wrong? is there some way to decrypt with ciphertexts that are shorter than the key? Or are they messing up somewhere by producing ciphers that are too short?

Thanks for any help.

user1966576
  • 83
  • 1
  • 10
  • Provide the location in the document that applies to the encryption you are working with. – zaph Jul 21 '18 at 12:44
  • Page 25, MAC Frame Payload Encryption (FRMPayload). That is the payload that they encrypt that we have to decrypt. – user1966576 Jul 21 '18 at 13:22

1 Answers1

3

In AES-ECB, the only valid ciphertext shorter than 16-byte is empty. That 16-byte limit is the block (not key) size of AES, which happens to match the key size for AES-128.

Therefore, the question's

An example message we receive is: d3 73 4c

does not show an ECB encrypted message (since a comment tells that's from a JSON, that can't be bytes that happen to show as hex). And that's way too short to be a FRMPayload (per this comment) for a Join-Accept, since the spec says of the later:

1625     The message is either 16 or 32 bytes long.

Could it be that whatever that JSON message contains is not a full FRMPayload, but a fragment of a packet, encoded as hexadecimal pair with space separator? As long as it is not figured out how to build a FRMPayload, there's not point in deciphering it.

Update: If that mystery message is always 3 bytes, and if it is always the same for a given key (or available a single time per key), then per Maarten Bodewes's comment it might be a Key Check Value. The KCV is often the first 3 bytes of the encryption of the all-zero value with the key per the raw block cipher (equivalently: per ECB). Herbert Hanewinkel's javascript AES can work fully offline (which is necessary to not expose the key), and be used to manually validate an hypothesis. It tells that for the 16-byte key given in the question, a KCV would be cd15e1 (or c076fc per the variant in the next section).


Also: it is used CreateDecryptor to craft a gizmo in charge of the ECB decryption. That's likely incorrect in the context of decryption of a LoraWan payload, which requires ECB encryption for decryption of some fields:

1626     Note: AES decrypt operation in ECB mode is used to encrypt the join-accept message so that the end-device can use an AES encrypt operation to decrypt the message. This way an end device only has to implement AES encrypt but not AES decrypt.


In the context of decryption of a LoraWan packets, you want to communicate with the AES engine using byte arrays, not strings. Strings have an encoding, when LoraWan ciphertext and corresponding plaintext does not. Others seems to have managed to coerce the nice .NET do-it-all crypto API to get a low-level job done.


In the HextoString code, I vaguely get that the intention and perhaps outcome is that hex becomes the originally hex input as a byte array (fully rid of hexadecimal and other encoding sin; in which case the variable hex should be renamed to something on the tune of pure_bytes). But then I'm at loss about System.Text.Encoding.ASCII.GetString(hex). I'd be surprised if it just created a byte string from a byte array, or turned the key back to hexadecimal for later feeding to HexStringToByteArray in Aes128Decrypt. Plus this makes me fear that any byte in [0x80..0xFF] might turn to 0x3F, which is not nice for key, ciphertext, and corresponding LoraWan payload. These have no character encoding when de-hexified.

My conclusion is that if HexStringToByteArray does what its name suggests, and given the current interface of Aes128Decrypt, HextoString should simply remove whitespace (or is unneeded if HexStringToByteArray removes whitespace on the fly). But my recommendation is to change the interface to use byte arrays, not strings (see previous section).


As an aside: creating an ICryptoTransform object from its key is supposed to be performed once for multiple uses of the object.

fgrieu
  • 2,724
  • 1
  • 23
  • 53
  • Thanks for your reply. I assure you that the messages we thus far received are neither 16 or 32. I gave an example which is 6, there are a bunch of these, but we have also received 28 long ones, which when deconverted from hex are 14 bytes long, which again, is just not long enough. As for the rest, I'm actually not 100% sure we are supposed to use ECB, but it's either ECB or CBC. They did not specify, we simply gathered ECB because CBC is not even mentioned in the specification, plus they provide no Initialization Vector – user1966576 Jul 21 '18 at 15:21
  • 1
    @user1966576: It could well be that what you think are the packets received is not. How exactly are you getting these into the .net universe, and conclude they are less than 16 bytes? Packets come to life as byte arrays, not strings. Whatever makes them strings is highly suspect. – fgrieu Jul 21 '18 at 15:40
  • @user1966576: Another pitfall is to conclude that a packet has been received when only the beginning has. Some protocols have a special signal, or a non-byte combination of bits (end-of-frame) to allow detecting the end. Others indicate the length at the beginning (corruption of that cause interesting recovery issues). Yet others have nothing, and the receiver typically ends up assuming no more byte will come if no byte came for some time (with that time contributing to latency, and efficiently detecting the end of that time hard). I do not immediately see that specified in the linked spec. – fgrieu Jul 21 '18 at 16:24
  • Well um... In .NET there is a library for websockets. We use this library, we connect to our source via websocket, they send JSON messages through this, we deserialize these JSONs, one such property of the JSON is the encrypted data, of which I gave one example of. We do not have control over the part that makes them into a string. – user1966576 Jul 21 '18 at 19:02
  • @user1966576: Whatever that source is, what you show that it sent you is not a full AES-ECB ciphertext block. Perhaps that source has a spec telling what it decides to send? – fgrieu Jul 21 '18 at 19:55
  • @user1966576 You need to go deeper, get an network sniffer such as [Charles proxy](https://www.charlesproxy.com) and look at what is actually coming back from the server. – zaph Jul 21 '18 at 20:53
  • 2
    @zaph & all, maybe it's a KCV. That's a block of zero's encrypted using ECB mode (or rather just a block encrypt) and generally only the leftmost 3 bytes are used. – Maarten Bodewes Jul 22 '18 at 00:56