Skip to content

Commit

Permalink
More work on crypto primitives
Browse files Browse the repository at this point in the history
  • Loading branch information
jdomnitz committed Nov 21, 2024
1 parent 1f7f472 commit 8758703
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 24 deletions.
83 changes: 83 additions & 0 deletions MatterDotNet/Security/AesCtr.cs
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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<byte> EncryptDecrypt(Span<byte> input)
{
if (isDisposed)
throw new ObjectDisposedException("AES CTR disposed");

Span<byte> 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);
}
}
}
90 changes: 87 additions & 3 deletions MatterDotNet/Security/Crypto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.


using System.Buffers.Binary;
using System.Security.Cryptography;

namespace MatterDotNet.Security
Expand Down Expand Up @@ -56,10 +57,93 @@ public static bool Crypto_AEAD_DecryptVerify(byte[] key, Span<byte> payload, Rea
}
}

public static ReadOnlySpan<byte> Crypto_Privacy_Encrypt(byte[] key, Span<byte> message, byte[] nonce)
/// <summary>
/// Performs the decryption of message using the key and a nonce; the output is the decryption of message
/// </summary>
/// <param name="key"></param>
/// <param name="message"></param>
/// <param name="nonce"></param>
/// <returns></returns>
public static Span<byte> Crypto_Privacy_Decrypt(byte[] key, Span<byte> message, byte[] nonce)
{
// TODO
throw new NotImplementedException();
AesCtr ctr = new AesCtr(key, nonce);
return ctr.EncryptDecrypt(message);
}

/// <summary>
/// Performs the Encryption of message using the key and a nonce; the output is the decryption of message
/// </summary>
/// <param name="key"></param>
/// <param name="message"></param>
/// <param name="nonce"></param>
/// <returns></returns>
public static Span<byte> Crypto_Privacy_Encrypt(byte[] key, Span<byte> message, byte[] nonce)
{
AesCtr ctr = new AesCtr(key, nonce);
return ctr.EncryptDecrypt(message);
}

/// <summary>
/// Returns the key of len bits derived from inputKey using the salt and the info; len SHALL be a multiple of 8.
/// </summary>
/// <param name="inputKey"></param>
/// <param name="salt"></param>
/// <param name="info"></param>
/// <param name="len"></param>
/// <returns></returns>
public static Span<byte> 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);
}

/// <summary>
/// Password-based key derivation function to compute a derived key from a cryptographically weak password
/// </summary>
/// <param name="input"></param>
/// <param name="salt"></param>
/// <param name="iterations"></param>
/// <param name="len">Length in bits</param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
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<byte> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte> Fill(byte val, int count)
{
Memory<byte> ret = new byte[count];
Expand All @@ -25,38 +37,38 @@ public static Memory<byte> Fill(byte val, int count)
return ret;
}

public static Memory<byte> PadZeros(Memory<byte> val, int count)
public static Span<byte> PadZeros(Span<byte> val, int count)
{
if (count <= 0)
return val;
Memory<byte> 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<byte> LeftShift1(Memory<byte> array)
public static Span<byte> LeftShift1(Span<byte> array)
{
Memory<byte> 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<byte> XOR(Memory<byte> a, Memory<byte> b)
public static Span<byte> XOR(Span<byte> a, Span<byte> b)
{
if (a.Length != b.Length)
throw new ArgumentException("Invalid Byte Array Sizes");
Memory<byte> 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<byte> a, byte start)
public static byte XOR(Span<byte> a, byte start)
{
foreach (byte b in a.Span)
foreach (byte b in a)
start ^= b;
return start;
}
Expand All @@ -71,11 +83,6 @@ public static void Increment(Span<byte> mem)
}
}

public static string Print(Memory<byte> mem)
{
return Print(mem.Span);
}

public static string Print(ReadOnlySpan<byte> span)
{
StringBuilder ret = new StringBuilder(span.Length * 3);
Expand All @@ -88,7 +95,7 @@ public static string Print(ReadOnlySpan<byte> span)
return ret.ToString();
}

public static Memory<byte> From(string hexString)
public static Span<byte> From(string hexString)
{
if (hexString.Length % 2 != 0)
throw new ArgumentException("Not a hex string");
Expand All @@ -97,7 +104,7 @@ public static Memory<byte> 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;
}
}
}
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit 8758703

Please sign in to comment.