Use custom md5 just for account/user IDs, not anything real (#314)
* Use custom md5 just for account/user IDs, not anything real * test fixes * To lower and upper as needed
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
namespace Speckle.Sdk.Common;
|
||||
|
||||
// MD5 implementation in pure C# (public domain / no dependencies)
|
||||
// Not for cryptographic purposes
|
||||
// Using this instead of changing ID generation but avoiding built in MD5 for FIPS compliance
|
||||
|
||||
public static class Md5
|
||||
{
|
||||
// Standard initial values
|
||||
private static readonly uint[] T =
|
||||
[
|
||||
0xd76aa478,
|
||||
0xe8c7b756,
|
||||
0x242070db,
|
||||
0xc1bdceee,
|
||||
0xf57c0faf,
|
||||
0x4787c62a,
|
||||
0xa8304613,
|
||||
0xfd469501,
|
||||
0x698098d8,
|
||||
0x8b44f7af,
|
||||
0xffff5bb1,
|
||||
0x895cd7be,
|
||||
0x6b901122,
|
||||
0xfd987193,
|
||||
0xa679438e,
|
||||
0x49b40821,
|
||||
0xf61e2562,
|
||||
0xc040b340,
|
||||
0x265e5a51,
|
||||
0xe9b6c7aa,
|
||||
0xd62f105d,
|
||||
0x02441453,
|
||||
0xd8a1e681,
|
||||
0xe7d3fbc8,
|
||||
0x21e1cde6,
|
||||
0xc33707d6,
|
||||
0xf4d50d87,
|
||||
0x455a14ed,
|
||||
0xa9e3e905,
|
||||
0xfcefa3f8,
|
||||
0x676f02d9,
|
||||
0x8d2a4c8a,
|
||||
0xfffa3942,
|
||||
0x8771f681,
|
||||
0x6d9d6122,
|
||||
0xfde5380c,
|
||||
0xa4beea44,
|
||||
0x4bdecfa9,
|
||||
0xf6bb4b60,
|
||||
0xbebfbc70,
|
||||
0x289b7ec6,
|
||||
0xeaa127fa,
|
||||
0xd4ef3085,
|
||||
0x04881d05,
|
||||
0xd9d4d039,
|
||||
0xe6db99e5,
|
||||
0x1fa27cf8,
|
||||
0xc4ac5665,
|
||||
0xf4292244,
|
||||
0x432aff97,
|
||||
0xab9423a7,
|
||||
0xfc93a039,
|
||||
0x655b59c3,
|
||||
0x8f0ccc92,
|
||||
0xffeff47d,
|
||||
0x85845dd1,
|
||||
0x6fa87e4f,
|
||||
0xfe2ce6e0,
|
||||
0xa3014314,
|
||||
0x4e0811a1,
|
||||
0xf7537e82,
|
||||
0xbd3af235,
|
||||
0x2ad7d2bb,
|
||||
0xeb86d391,
|
||||
];
|
||||
|
||||
public static byte[] ComputeHash(byte[] input)
|
||||
{
|
||||
// Pad input
|
||||
int origLenBits = input.Length * 8;
|
||||
int padLen = (56 - (input.Length + 1) % 64 + 64) % 64;
|
||||
byte[] padded = new byte[input.Length + 1 + padLen + 8];
|
||||
Array.Copy(input, padded, input.Length);
|
||||
padded[input.Length] = 0x80;
|
||||
BitConverter.GetBytes((long)origLenBits).CopyTo(padded, padded.Length - 8);
|
||||
|
||||
// Initialize MD5 buffer
|
||||
uint a = 0x67452301;
|
||||
uint b = 0xefcdab89;
|
||||
uint c = 0x98badcfe;
|
||||
uint d = 0x10325476;
|
||||
|
||||
for (int i = 0; i < padded.Length / 64; i++)
|
||||
{
|
||||
uint[] M = new uint[16];
|
||||
for (int j = 0; j < 16; j++)
|
||||
{
|
||||
M[j] = BitConverter.ToUInt32(padded, (i * 64) + j * 4);
|
||||
}
|
||||
|
||||
uint AA = a,
|
||||
BB = b,
|
||||
CC = c,
|
||||
DD = d;
|
||||
for (int j = 0; j < 64; j++)
|
||||
{
|
||||
uint f,
|
||||
g;
|
||||
if (j < 16)
|
||||
{
|
||||
f = (b & c) | (~b & d);
|
||||
g = (uint)j;
|
||||
}
|
||||
else if (j < 32)
|
||||
{
|
||||
f = (d & b) | (~d & c);
|
||||
g = (uint)((5 * j + 1) % 16);
|
||||
}
|
||||
else if (j < 48)
|
||||
{
|
||||
f = b ^ c ^ d;
|
||||
g = (uint)((3 * j + 5) % 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
f = c ^ (b | ~d);
|
||||
g = (uint)((7 * j) % 16);
|
||||
}
|
||||
|
||||
uint temp = d;
|
||||
d = c;
|
||||
c = b;
|
||||
b += LeftRotate(a + f + T[j] + M[g], S(j));
|
||||
a = temp;
|
||||
}
|
||||
a += AA;
|
||||
b += BB;
|
||||
c += CC;
|
||||
d += DD;
|
||||
}
|
||||
|
||||
byte[] output = new byte[16];
|
||||
Array.Copy(BitConverter.GetBytes(a), 0, output, 0, 4);
|
||||
Array.Copy(BitConverter.GetBytes(b), 0, output, 4, 4);
|
||||
Array.Copy(BitConverter.GetBytes(c), 0, output, 8, 4);
|
||||
Array.Copy(BitConverter.GetBytes(d), 0, output, 12, 4);
|
||||
return output;
|
||||
}
|
||||
|
||||
private static int S(int i)
|
||||
{
|
||||
int[] s = { 7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21 };
|
||||
return s[(i / 16) * 4 + (i % 4)];
|
||||
}
|
||||
|
||||
private static uint LeftRotate(uint x, int c) => (x << c) | (x >> (32 - c));
|
||||
|
||||
// Convenience method to get hex string
|
||||
public static string GetString(string input)
|
||||
{
|
||||
var hash = ComputeHash(System.Text.Encoding.UTF8.GetBytes(input));
|
||||
return BitConverter.ToString(hash).Replace("-", "");
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,17 @@ using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
|
||||
namespace Speckle.Sdk.Helpers;
|
||||
namespace Speckle.Sdk.Common;
|
||||
|
||||
public static class Crypt
|
||||
public static class Sha256
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
/// <param name="input">the value to hash</param>
|
||||
/// <param name="format"><c>"x2"</c> for lower case, <c>"X2"</c> for uppercase.</param>
|
||||
/// <param name="length">Desired length of the returned string. Must be 2 ≤ Length ≤ 64, and must be a multiple of 2</param>
|
||||
/// <returns><inheritdoc cref="Sha256(string, string?, int)"/></returns>
|
||||
/// <returns><inheritdoc cref="GetString(string, string?, int)"/></returns>
|
||||
[Pure]
|
||||
public static string Sha256(
|
||||
public static string GetString(
|
||||
ReadOnlySpan<char> input,
|
||||
[StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = "x2",
|
||||
int length = SHA256.HashSizeInBytes * sizeof(char)
|
||||
@@ -45,7 +45,7 @@ public static class Crypt
|
||||
/// <exception cref="FormatException"><paramref name="format"/> is not a recognised numeric format</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><inheritdoc cref="StringBuilder.ToString(int, int)"/></exception>
|
||||
[Pure]
|
||||
public static string Sha256(
|
||||
public static string GetString(
|
||||
string input,
|
||||
[StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = "x2",
|
||||
int length = 64
|
||||
@@ -67,30 +67,4 @@ public static class Crypt
|
||||
|
||||
return sb.ToString(0, length);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Sha256(string, string?, int)"/>
|
||||
/// <remarks>MD5 is a broken cryptographic algorithm and should be used subject to review see CA5351</remarks>
|
||||
[Pure]
|
||||
[SuppressMessage("Security", "CA5351:Do Not Use Broken Cryptographic Algorithms")]
|
||||
public static string Md5(
|
||||
string input,
|
||||
[StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = "x2",
|
||||
int length = 32
|
||||
)
|
||||
{
|
||||
byte[] inputBytes = Encoding.ASCII.GetBytes(input.ToLowerInvariant());
|
||||
#if NETSTANDARD2_0
|
||||
using MD5 md5 = MD5.Create();
|
||||
byte[] hashBytes = md5.ComputeHash(inputBytes);
|
||||
#else
|
||||
byte[] hashBytes = MD5.HashData(inputBytes);
|
||||
#endif
|
||||
StringBuilder sb = new(32);
|
||||
for (int i = 0; i < hashBytes.Length; i++)
|
||||
{
|
||||
sb.Append(hashBytes[i].ToString(format));
|
||||
}
|
||||
|
||||
return sb.ToString(0, length);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Speckle.Sdk.Api.GraphQL.Models;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.Common;
|
||||
|
||||
namespace Speckle.Sdk.Credentials;
|
||||
|
||||
@@ -25,7 +25,7 @@ public class Account : IEquatable<Account>
|
||||
throw new InvalidOperationException("Incomplete account info: cannot generate id.");
|
||||
}
|
||||
|
||||
_id = Crypt.Md5(userInfo.email + serverInfo.url, "X2");
|
||||
_id = Md5.GetString(userInfo.email + serverInfo.url).ToUpperInvariant();
|
||||
}
|
||||
return _id;
|
||||
}
|
||||
@@ -62,13 +62,13 @@ public class Account : IEquatable<Account>
|
||||
public string GetHashedEmail()
|
||||
{
|
||||
string email = userInfo?.email ?? "unknown";
|
||||
return "@" + Crypt.Md5(email, "X2");
|
||||
return "@" + Md5.GetString(email).ToUpperInvariant();
|
||||
}
|
||||
|
||||
public string GetHashedServer()
|
||||
{
|
||||
string url = serverInfo?.url ?? AccountManager.DEFAULT_SERVER_URL;
|
||||
return Crypt.Md5(CleanURL(url), "X2");
|
||||
return Md5.GetString(CleanURL(url)).ToUpperInvariant();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation;
|
||||
@@ -10,9 +10,9 @@ public static class IdGenerator
|
||||
public static Id ComputeId(Json serialized)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
string hash = Crypt.Sha256(serialized.Value.AsSpan(), length: HashUtility.HASH_LENGTH);
|
||||
string hash = Sha256.GetString(serialized.Value.AsSpan(), length: HashUtility.HASH_LENGTH);
|
||||
#else
|
||||
string hash = Crypt.Sha256(serialized.Value, length: HashUtility.HASH_LENGTH);
|
||||
string hash = Sha256.GetString(serialized.Value, length: HashUtility.HASH_LENGTH);
|
||||
#endif
|
||||
return new Id(hash);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using Speckle.Sdk.Helpers;
|
||||
|
||||
namespace Speckle.Sdk.Tests.Performance.Benchmarks;
|
||||
|
||||
@@ -20,12 +19,12 @@ public class CryptSha256Hash
|
||||
[Benchmark]
|
||||
public string Sha256()
|
||||
{
|
||||
return Crypt.Sha256(testData);
|
||||
return Speckle.Sdk.Common.Sha256.GetString(testData);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public string Sha256_Span()
|
||||
{
|
||||
return Crypt.Sha256(testData.AsSpan());
|
||||
return Speckle.Sdk.Common.Sha256.GetString(testData.AsSpan());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace Speckle.Sdk.Tests.Unit.Common;
|
||||
|
||||
public class Md5Tests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("", "d41d8cd98f00b204e9800998ecf8427e")]
|
||||
[InlineData("a", "0cc175b9c0f1b6a831c399e269772661")]
|
||||
[InlineData("abc", "900150983cd24fb0d6963f7d28e17f72")]
|
||||
[InlineData("message digest", "f96b697d7cb7938d525a2f31aaf161d0")]
|
||||
[InlineData("abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b")]
|
||||
[InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "d174ab98d277d9f5a5611c2c9f419d9f")]
|
||||
[InlineData(
|
||||
"12345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||
"57edf4a22be3c955ac49da2e2107b67a"
|
||||
)]
|
||||
public void Md5_Hash_Is_Correct(string input, string expectedHex)
|
||||
{
|
||||
string actual = Speckle.Sdk.Common.Md5.GetString(input).ToLowerInvariant();
|
||||
expectedHex.Should().Be(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("", "d41d8cd98f00b204e9800998ecf8427e")]
|
||||
[InlineData("a", "0cc175b9c0f1b6a831c399e269772661")]
|
||||
[InlineData("abc", "900150983cd24fb0d6963f7d28e17f72")]
|
||||
[InlineData("message digest", "f96b697d7cb7938d525a2f31aaf161d0")]
|
||||
[InlineData("abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b")]
|
||||
[InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "d174ab98d277d9f5a5611c2c9f419d9f")]
|
||||
[InlineData(
|
||||
"12345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||
"57edf4a22be3c955ac49da2e2107b67a"
|
||||
)]
|
||||
public void Md5_Compare(string input, string expectedHex)
|
||||
{
|
||||
//old always did to lower for some reason.
|
||||
string actual = Speckle.Sdk.Common.Md5.GetString(input).ToLowerInvariant();
|
||||
string old = OldMd5(input);
|
||||
expectedHex.Should().Be(actual);
|
||||
expectedHex.Should().Be(old);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
[SuppressMessage("Security", "CA5351:Do Not Use Broken Cryptographic Algorithms")]
|
||||
public static string OldMd5(
|
||||
string input,
|
||||
[StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format = "x2",
|
||||
int length = 32
|
||||
)
|
||||
{
|
||||
byte[] inputBytes = Encoding.ASCII.GetBytes(input);
|
||||
#if NETSTANDARD2_0
|
||||
using MD5 md5 = MD5.Create();
|
||||
byte[] hashBytes = md5.ComputeHash(inputBytes);
|
||||
#else
|
||||
byte[] hashBytes = MD5.HashData(inputBytes);
|
||||
#endif
|
||||
StringBuilder sb = new(32);
|
||||
for (int i = 0; i < hashBytes.Length; i++)
|
||||
{
|
||||
sb.Append(hashBytes[i].ToString(format));
|
||||
}
|
||||
|
||||
return sb.ToString(0, length);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using FluentAssertions;
|
||||
using Speckle.Sdk.Dependencies;
|
||||
using Speckle.Sdk.Helpers;
|
||||
|
||||
namespace Speckle.Sdk.Tests.Unit.Models;
|
||||
|
||||
@@ -45,9 +44,6 @@ public sealed class HashUtilityTests
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> SmallTestCasesMd5() =>
|
||||
SmallTestCases(SmallTestCases(), EnumerableExtensions.RangeFrom(0, 32));
|
||||
|
||||
public static IEnumerable<object[]> SmallTestCasesSha256() =>
|
||||
SmallTestCases(SmallTestCases(), EnumerableExtensions.RangeFrom(2, 64));
|
||||
|
||||
@@ -69,24 +65,12 @@ public sealed class HashUtilityTests
|
||||
];
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(SmallTestCasesMd5))]
|
||||
public void Md5(string input, string _, string expected, int length)
|
||||
{
|
||||
var resultLower = Crypt.Md5(input, "x2", length);
|
||||
var resultUpper = Crypt.Md5(input, "X2", length);
|
||||
|
||||
resultLower.Should().Be(new string(expected.ToLower()[..length]));
|
||||
|
||||
resultUpper.Should().Be(new string(expected.ToUpper()[..length]));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(SmallTestCasesSha256))]
|
||||
public void Sha256(string input, string expected, string _, int length)
|
||||
{
|
||||
var resultLower = Crypt.Sha256(input, "x2", length);
|
||||
var resultUpper = Crypt.Sha256(input, "X2", length);
|
||||
var resultLower = Speckle.Sdk.Common.Sha256.GetString(input, "x2", length);
|
||||
var resultUpper = Speckle.Sdk.Common.Sha256.GetString(input, "X2", length);
|
||||
|
||||
resultLower.Should().Be(new string(expected.ToLower()[..length]));
|
||||
|
||||
@@ -102,8 +86,8 @@ public sealed class HashUtilityTests
|
||||
int length //Span version of the function must have multiple of 2
|
||||
)
|
||||
{
|
||||
var resultLowerSpan = Crypt.Sha256(input.AsSpan(), "x2", length);
|
||||
var resultUpperSpan = Crypt.Sha256(input.AsSpan(), "X2", length);
|
||||
var resultLowerSpan = Speckle.Sdk.Common.Sha256.GetString(input.AsSpan(), "x2", length);
|
||||
var resultUpperSpan = Speckle.Sdk.Common.Sha256.GetString(input.AsSpan(), "X2", length);
|
||||
|
||||
resultLowerSpan.Should().Be(new string(expected.ToLower()[..length]));
|
||||
|
||||
@@ -114,7 +98,7 @@ public sealed class HashUtilityTests
|
||||
[MemberData(nameof(LargeTestCases))]
|
||||
public void Sha256_LargeDataTests(string input, string expected)
|
||||
{
|
||||
var computedHash = Crypt.Sha256(input.AsSpan());
|
||||
var computedHash = Speckle.Sdk.Common.Sha256.GetString(input.AsSpan());
|
||||
computedHash.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user