Search code examples
c#aesz-wave

Implement Z-Wave AES 128 OFB in C#?


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?


Solution

  • 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