diff --git a/MatterDotNet/Security/AesCtr.cs b/MatterDotNet/Security/AesCtr.cs
new file mode 100644
index 0000000..d134a2b
--- /dev/null
+++ b/MatterDotNet/Security/AesCtr.cs
@@ -0,0 +1,83 @@
+// MatterDotNet Copyright (C) 2024
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or any later version.
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY, without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU Affero General Public License for more details.
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+using System.Security.Cryptography;
+
+namespace MatterDotNet.Security
+{
+ internal class AesCtr : IDisposable
+ {
+ private const int BLOCK_SIZE = 16;
+ private readonly byte[] counter = new byte[BLOCK_SIZE];
+
+ private readonly ICryptoTransform counterEncryptor;
+ private bool isDisposed;
+
+ public AesCtr(byte[] key, byte[] initialCounter)
+ {
+ this.isDisposed = false;
+
+ SymmetricAlgorithm aes = Aes.Create();
+ aes.Mode = CipherMode.ECB;
+ aes.Padding = PaddingMode.None;
+
+ Buffer.BlockCopy(initialCounter, 0, this.counter, 0, BLOCK_SIZE);
+
+ var zeroIv = new byte[BLOCK_SIZE];
+ this.counterEncryptor = aes.CreateEncryptor(key, zeroIv);
+ }
+
+ public Span EncryptDecrypt(Span input)
+ {
+ if (isDisposed)
+ throw new ObjectDisposedException("AES CTR disposed");
+
+ Span output = new byte[input.Length];
+ int offset = 0, numBytes = input.Length;
+ byte[] block = new byte[BLOCK_SIZE];
+
+ while (numBytes > 0)
+ {
+ this.counterEncryptor.TransformBlock(counter, 0, BLOCK_SIZE, block, 0);
+
+ for (int i = 0; i < BLOCK_SIZE; i++)
+ {
+ if (++counter[i] != 0)
+ break;
+ }
+
+ if (numBytes <= BLOCK_SIZE)
+ {
+ for (int i = 0; i < numBytes; i++)
+ output[i + offset] = (byte)(input[i + offset] ^ block[i]);
+ return output;
+ }
+
+ for (int i = 0; i < BLOCK_SIZE; i++)
+ output[i + offset] = (byte)(input[i + offset] ^ block[i]);
+
+ numBytes -= BLOCK_SIZE;
+ offset += BLOCK_SIZE;
+ }
+ return output;
+ }
+
+ public void Dispose()
+ {
+ if (!isDisposed)
+ this.counterEncryptor?.Dispose();
+
+ isDisposed = true;
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/MatterDotNet/Security/Crypto.cs b/MatterDotNet/Security/Crypto.cs
index 3475c5f..04757c3 100644
--- a/MatterDotNet/Security/Crypto.cs
+++ b/MatterDotNet/Security/Crypto.cs
@@ -11,6 +11,7 @@
// along with this program. If not, see .
+using System.Buffers.Binary;
using System.Security.Cryptography;
namespace MatterDotNet.Security
@@ -56,10 +57,93 @@ public static bool Crypto_AEAD_DecryptVerify(byte[] key, Span payload, Rea
}
}
- public static ReadOnlySpan Crypto_Privacy_Encrypt(byte[] key, Span message, byte[] nonce)
+ ///
+ /// Performs the decryption of message using the key and a nonce; the output is the decryption of message
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Span Crypto_Privacy_Decrypt(byte[] key, Span message, byte[] nonce)
{
- // TODO
- throw new NotImplementedException();
+ AesCtr ctr = new AesCtr(key, nonce);
+ return ctr.EncryptDecrypt(message);
+ }
+
+ ///
+ /// Performs the Encryption of message using the key and a nonce; the output is the decryption of message
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Span Crypto_Privacy_Encrypt(byte[] key, Span message, byte[] nonce)
+ {
+ AesCtr ctr = new AesCtr(key, nonce);
+ return ctr.EncryptDecrypt(message);
+ }
+
+ ///
+ /// Returns the key of len bits derived from inputKey using the salt and the info; len SHALL be a multiple of 8.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Span Crypto_KDF(byte[] inputKey, byte[] salt, byte[] info, int len)
+ {
+ if (len > 256)
+ throw new ArgumentException("This wasn't designed for multiple blocks");
+ int L = len / 8;
+ const int HashLen = 32;
+ byte N = (byte)Math.Ceiling((double)(L / HashLen));
+ byte[] PRK = HKDF_Extract(salt, inputKey);
+ return HMAC_Hash(N, PRK, info);
+ }
+
+ private static byte[] HMAC_Hash(byte T, byte[] PRK, byte[] info)
+ {
+ if (T == 0)
+ return [];
+ byte[] bytes = SpanUtil.Combine(HMAC_Hash((byte)(T - 1), PRK, info), info, [T]);
+ return HMACSHA256.HashData(PRK, bytes);
+ }
+
+ public static byte[] HKDF_Extract(byte[] salt, byte[] IKM)
+ {
+ return HMACSHA256.HashData(salt, IKM);
+ }
+
+ ///
+ /// Password-based key derivation function to compute a derived key from a cryptographically weak password
+ ///
+ ///
+ ///
+ ///
+ /// Length in bits
+ ///
+ ///
+ public static byte[] Crypto_PBKDF(byte[] input, byte[] salt, int iterations, int len)
+ {
+ if (iterations < 1000 || iterations > 100000)
+ throw new ArgumentOutOfRangeException(nameof(iterations));
+ int numBlocks = (int)Math.Ceiling((double)(len / 256));
+ byte[] ret = new byte[len / 8];
+ for (int i = 0; i < numBlocks; i++)
+ {
+ byte[] IntI = new byte[4];
+ BinaryPrimitives.WriteUInt32BigEndian(IntI, (uint)i);
+ Span T = new byte[32];
+ byte[] U = SpanUtil.Combine(salt, IntI);
+ for (int j = 1; j < iterations; j++)
+ {
+ U = HMACSHA256.HashData(input, U);
+ T = SpanUtil.XOR(T, U);
+ }
+ T.CopyTo(ret.AsSpan(i * 32, 32));
+ }
+ return ret;
}
}
}
diff --git a/MatterDotNet/Security/MemoryUtil.cs b/MatterDotNet/Security/SpanUtil.cs
similarity index 70%
rename from MatterDotNet/Security/MemoryUtil.cs
rename to MatterDotNet/Security/SpanUtil.cs
index 058bfc8..4274b4d 100644
--- a/MatterDotNet/Security/MemoryUtil.cs
+++ b/MatterDotNet/Security/SpanUtil.cs
@@ -15,8 +15,20 @@
namespace MatterDotNet.Security
{
- internal static class MemoryUtil
+ internal static class SpanUtil
{
+ public static byte[] Combine(params byte[][] arrays)
+ {
+ byte[] rv = new byte[arrays.Sum(a => a.Length)];
+ int offset = 0;
+ foreach (byte[] array in arrays)
+ {
+ System.Buffer.BlockCopy(array, 0, rv, offset, array.Length);
+ offset += array.Length;
+ }
+ return rv;
+ }
+
public static Memory Fill(byte val, int count)
{
Memory ret = new byte[count];
@@ -25,38 +37,38 @@ public static Memory Fill(byte val, int count)
return ret;
}
- public static Memory PadZeros(Memory val, int count)
+ public static Span PadZeros(Span val, int count)
{
if (count <= 0)
return val;
Memory ret = new byte[val.Length + count];
if (val.Length > 0)
- val.CopyTo(ret);
- return ret;
+ val.CopyTo(ret.Span);
+ return ret.Span;
}
- public static Memory LeftShift1(Memory array)
+ public static Span LeftShift1(Span array)
{
Memory ret = new byte[array.Length];
for (int i = 0; i < ret.Length - 1; i++)
- ret.Span[i] = (byte)(array.Span[i] << 1 | (array.Span[i + 1] >> 7));
- ret.Span[ret.Length - 1] = (byte)(array.Span[array.Length - 1] << 1);
- return ret;
+ ret.Span[i] = (byte)(array[i] << 1 | (array[i + 1] >> 7));
+ ret.Span[ret.Length - 1] = (byte)(array[array.Length - 1] << 1);
+ return ret.Span;
}
- public static Memory XOR(Memory a, Memory b)
+ public static Span XOR(Span a, Span b)
{
if (a.Length != b.Length)
throw new ArgumentException("Invalid Byte Array Sizes");
Memory ret = new byte[a.Length];
for (int i = 0; i < a.Length; i++)
- ret.Span[i] = (byte)(a.Span[i] ^ b.Span[i]);
- return ret;
+ ret.Span[i] = (byte)(a[i] ^ b[i]);
+ return ret.Span;
}
- public static byte XOR(Memory a, byte start)
+ public static byte XOR(Span a, byte start)
{
- foreach (byte b in a.Span)
+ foreach (byte b in a)
start ^= b;
return start;
}
@@ -71,11 +83,6 @@ public static void Increment(Span mem)
}
}
- public static string Print(Memory mem)
- {
- return Print(mem.Span);
- }
-
public static string Print(ReadOnlySpan span)
{
StringBuilder ret = new StringBuilder(span.Length * 3);
@@ -88,7 +95,7 @@ public static string Print(ReadOnlySpan span)
return ret.ToString();
}
- public static Memory From(string hexString)
+ public static Span From(string hexString)
{
if (hexString.Length % 2 != 0)
throw new ArgumentException("Not a hex string");
@@ -97,7 +104,7 @@ public static Memory From(string hexString)
for (int index = 0; index < data.Length; index++)
data.Span[index] = byte.Parse(hexString.AsSpan(index * 2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
- return data;
+ return data.Span;
}
}
}
diff --git a/README.md b/README.md
index df960a5..fe2ccf5 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,9 @@
+[![Build](https://github.com/SmartHomeOS/MatterDotNet/actions/workflows/dotnet.yml/badge.svg)](https://github.com/SmartHomeOS/MatterDotNet/actions/workflows/dotnet.yml)
# MatterDotNet
**Coming Soon** - New implementation in progress
#### What's Working
-* Nothing Yet
+* Some Crypto functions, message framing and QR/PIN parsing
#### Coming Soon
* Matter 1.3 Standard Implementation