Skip to content

Commit 31ecc73

Browse files
authored
Optimize hash comparer
Signed-off-by: Xen <lordofxen@deskasoft.com>
1 parent 75c3916 commit 31ecc73

File tree

1 file changed

+20
-8
lines changed

1 file changed

+20
-8
lines changed

HashifyNet/Core/Utilities/HashComparer.cs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// *
1+
// *
22
// *****************************************************************************
33
// *
44
// * Copyright (c) 2025 Deskasoft International
@@ -27,8 +27,8 @@
2727
// ******************************************************************************
2828
// *
2929

30+
using System;
3031
using System.Collections.Immutable;
31-
using System.Linq;
3232

3333
namespace HashifyNet
3434
{
@@ -53,6 +53,22 @@ public static class HashComparer
5353
/// <returns><see langword="true"/> if the two byte arrays are equal; otherwise, <see langword="false"/>.</returns>
5454
/// <exception cref="System.ArgumentNullException">Thrown if either <paramref name="left"/> or <paramref name="right"/> is <see langword="null"/>.</exception>
5555
public static bool FixedTimeEquals(byte[] left, byte[] right)
56+
{
57+
return FixedTimeEquals(left.AsSpan(), right.AsSpan());
58+
}
59+
60+
/// <summary>
61+
/// Compares two <see cref="ReadOnlySpan{Byte}"/> instances for equality in a way that is resistant to timing attacks.
62+
/// </summary>
63+
/// <remarks>This method performs a constant-time comparison to prevent timing attacks, which can occur when
64+
/// the time taken to compare two values leaks information about their contents. The method ensures that the
65+
/// comparison time depends only on the length of the <see cref="ReadOnlySpan{Byte}"/>, not their contents. <para> If the <see cref="ReadOnlySpan{Byte}"/> instances have different
66+
/// lengths, the method returns <see langword="false"/> immediately.</para></remarks>
67+
/// <param name="left">The first <see cref="ReadOnlySpan{Byte}"/> to compare. Cannot be <see langword="null"/>.</param>
68+
/// <param name="right">The second <see cref="ReadOnlySpan{Byte}"/> to compare. Cannot be <see langword="null"/>.</param>
69+
/// <returns><see langword="true"/> if the two <see cref="ReadOnlySpan{Byte}"/> instances are equal; otherwise, <see langword="false"/>.</returns>
70+
/// <exception cref="System.ArgumentNullException">Thrown if either <paramref name="left"/> or <paramref name="right"/> is <see langword="null"/>.</exception>
71+
public static bool FixedTimeEquals(ReadOnlySpan<byte> left, ReadOnlySpan<byte> right)
5672
{
5773
if (left == null)
5874
{
@@ -65,7 +81,7 @@ public static bool FixedTimeEquals(byte[] left, byte[] right)
6581
}
6682

6783
#if NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 || NETCOREAPP3_1 || NETSTANDARD2_1 || NET5_0_OR_GREATER
68-
return System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(left, right);
84+
return System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(left, right);
6985
#else
7086
if (left.Length != right.Length)
7187
{
@@ -97,11 +113,7 @@ public static bool FixedTimeEquals(byte[] left, byte[] right)
97113
/// <exception cref="System.ArgumentNullException">Thrown if either <paramref name="left"/> or <paramref name="right"/> is <see langword="null"/>.</exception>
98114
public static bool FixedTimeEquals(ImmutableArray<byte> left, ImmutableArray<byte> right)
99115
{
100-
return FixedTimeEquals(left.ToArray(), right.ToArray());
116+
return FixedTimeEquals(left.AsSpan(), right.AsSpan());
101117
}
102118
}
103119
}
104-
105-
106-
107-

0 commit comments

Comments
 (0)