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
Information I have:
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?
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