2

I'm trying to implement AES 128 OFB encryption / decryption to match the Z-Wave Application Security Layer (S0) implementation. I can test the correct behavior by using a PC Controller application, with the following test data:

External Nonce: C7 16 D1 58 7E D2 9F 18
Internal Nonce: 68 DE E6 4A 88 A4 A3 E8
Security Key: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Decrypted Message: 00 98 06 2D C3 51 38 5D D8 4D 25 F5 ED 3B C5 B5 AA E2 36
Encrypted Message: 50 AE 49 A7 88 6C A9 BF E6 DA 36 A0 EF B8 CF D2 AA E2 C1

Facit from Silicon Labs Z-Wave PC Controller

Information I have:

  • AES-128 OFB. Output Feedback, or OFB, is the mode of operation used to encrypt and decrypt the payload.
  • IV = (sender's nonce || receiver's nonce) (the payload is encrypted with the external and internal nonce concatenated together.)

This is my attempt to implement the Z-Wave AES 128 OFB encryption / decryption in C#:

using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using System;
using System.IO;
using System.Text;

namespace AesEncryption
{
    class Program
    {
        static void Main(string[] args)
        {
            byte[] externalNonce = new byte[] { 0xC7, 0x16, 0xD1, 0x58, 0x7E, 0xD2, 0x9F, 0x18 };
            byte[] internalNonce = new byte[] { 0x68, 0xDE, 0xE6, 0x4A, 0x88, 0xA4, 0xA3, 0xE8 };
            byte[] securityKey = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
            byte[] unencryptedMessage = new byte[] { 0x00, 0x98, 0x06, 0x2D, 0xC3, 0x51, 0x38, 0x5D, 0xD8, 0x4D, 0x25, 0xF5, 0xED, 0x3B, 0xC5, 0xB5, 0xAA, 0xE2, 0x36 };
            byte[] initializationVector = InitializationVector(externalNonce, internalNonce);

            var cipher = CipherUtilities.GetCipher("AES/OFB/NoPadding");
            var keyParameter = new KeyParameter(securityKey);
            ICipherParameters cipherParameters = new ParametersWithIV(keyParameter, initializationVector);
            cipher.Init(true, cipherParameters);
            var encryptedMessage = cipher.DoFinal(unencryptedMessage);
            var encryptedMessageAsHexString = ToHexString(encryptedMessage); 
        }

        static byte[] InitializationVector(byte[] externalNonce, byte[] internalNonce)
        {
            byte[] iv = new byte[externalNonce.Length + internalNonce.Length];

            for (int i = 0; i < externalNonce.Length; i++)
                iv[i] = externalNonce[i];

            for (int i=0; i< internalNonce.Length; i++)
                iv[i + externalNonce.Length] = internalNonce[i];

            return iv;
        }

        public static string ToHexString(byte[] buffer)
        {
            StringBuilder stringBuilder = new StringBuilder(64);
            for (int i = 0; i < buffer.Length; i++)
            {
                stringBuilder.Append($"{buffer[i]:x2}");
                if (i < (buffer.Length - 1))
                    stringBuilder.Append(' ');
            }

            return stringBuilder.ToString();
        }

    }
}

The problem is that my code produces this encrypted result:

"6c 60 2b 6f 53 bb 36 74 3f 1a 50 dc fe db dd c7 d4 84 aa"

while the correct result is:

"50 AE 49 A7 88 6C A9 BF E6 DA 36 A0 EF B8 CF D2 AA E2 C1"

What could I be missing?

OlavT
  • 2,496
  • 4
  • 31
  • 56
  • Here is a [similar thread](https://community.silabs.com/s/question/0D51M00007xeQ8XSAU/s0-encryption-type?language=en_US) (but without a solution). With your data, I wonder about the Zero key. – Topaco Nov 02 '22 at 11:13
  • Zero key is just as good as any other key. In practice one would use a different key. – OlavT Nov 02 '22 at 21:13
  • 1
    The implementation is correct ([here](https://dotnetfiddle.net/fc3G92)) and the ciphertext can be verified independently ([here](https://gchq.github.io/CyberChef/#recipe=AES_Encrypt(%7B'option':'Hex','string':'00000000000000000000000000000000'%7D,%7B'option':'Hex','string':'C716D1587ED29F1868DEE64A88A4A3E8'%7D,'OFB','Hex','Hex',%7B'option':'Hex','string':''%7D)&input=MDA5ODA2MkRDMzUxMzg1REQ4NEQyNUY1RUQzQkM1QjVBQUUyMzY)). I.e. either the data is wrong (hence the question about the key) or your information about the processing of the data (IV derivation, algorithm, etc.) is incomplete or wrong. – Topaco Nov 02 '22 at 21:40
  • @Topaco Thanks a lot for test testing! Looks like the Silicon Labs Z-Wave PC Controller must be doing some additional tricks. – OlavT Nov 02 '22 at 22:55
  • @Topaco I got some more information related to the Security Key. It actually goes through an additional step to generate the actual encryption key. The step is documented as KE = AES(KN*V2). V2 is defined as 0xAA repeated 16 times. But I tried to run this through en AES mode ECB and then feed the resulting encryption key to the next step (AES OFB), but the result did still not match. – OlavT Nov 03 '22 at 15:53
  • To get the expected ciphertext, furthermore, the two nonces must be concatenated in reverse order, see my answer. – Topaco Nov 03 '22 at 17:21

1 Answers1

1

With the additional information that the actual encryption key is derived from an encryption of the key material

0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

with the AES primitive and the posted key

0x00000000000000000000000000000000

the expected ciphertext can be reproduced by using as IV the concatenation of internal and external nonce instead of the concatenation of external and internal nonce.

Full code:

using Org.BouncyCastle.Utilities.Encoders;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using System;

namespace AesEncryption
{
    class Program
    {
        static void Main(string[] args)
        {
            byte[] externalNonce = Hex.Decode("C716D1587ED29F18");
            byte[] internalNonce = Hex.Decode("68DEE64A88A4A3E8");
            byte[] securityKey = Hex.Decode("00000000000000000000000000000000");
            byte[] unencryptedMessage = Hex.Decode("0098062DC351385DD84D25F5ED3BC5B5AAE236");
            byte[] initializationVector = InitializationVector(internalNonce, externalNonce); // Fix 1: IV = internalNonce||externalNonce

            // Derive key
            byte[] keyMaterial = Hex.Decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); 
            byte[] encryptionKey = encrypt("AES/ECB/NoPadding", securityKey, null, keyMaterial); // Fix 2: derive encryption key

            //  Actual encryption
            byte[] encryptedMessage = encrypt("AES/OFB/NoPadding", encryptionKey, initializationVector, unencryptedMessage);
            var encryptedMessageAsHexString = Hex.ToHexString(encryptedMessage);
            Console.WriteLine(encryptedMessageAsHexString); // 50ae49a7886ca9bfe6da36a0efb8cfd2aae2c1
        }

        static byte[] encrypt(string algorithm, byte[] key, byte[] iv, byte[] plaintext)
        {
            var cipher = CipherUtilities.GetCipher(algorithm);
            var keyParameter = new KeyParameter(key);
            ICipherParameters cipherParameters = iv != null ? new ParametersWithIV(keyParameter, iv) : keyParameter;
            cipher.Init(true, cipherParameters);
            return cipher.DoFinal(plaintext);
        }

        static byte[] InitializationVector(byte[] nonce1, byte[] nonce2)
        {
            byte[] iv = new byte[nonce1.Length + nonce2.Length];
            Buffer.BlockCopy(nonce1, 0, iv, 0, nonce1.Length);
            Buffer.BlockCopy(nonce2, 0, iv, nonce1.Length, nonce2.Length);
            return iv;
        }
    }
}

This gives the required ciphertext:

0x50ae49a7886ca9bfe6da36a0efb8cfd2aae2c1
Topaco
  • 40,594
  • 4
  • 35
  • 62