diff --git a/OnixLabs.Core.UnitTests/StringExtensionTests.cs b/OnixLabs.Core.UnitTests/StringExtensionTests.cs new file mode 100644 index 0000000..614d237 --- /dev/null +++ b/OnixLabs.Core.UnitTests/StringExtensionTests.cs @@ -0,0 +1,117 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Xunit; + +namespace OnixLabs.Core.UnitTests +{ + public sealed class StringExtensionTests + { + [Theory(DisplayName = "SubstringBefore should return the expected result (char)")] + [InlineData("First:Second", "First", ':')] + [InlineData("12345+678910", "12345", '+')] + public void SubstringBeforeShouldReturnTheExpectedResultC(string value, string expected, char delimiter) + { + // Arrange / Act + string actual = value.SubstringBefore(delimiter); + + // Assert + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "SubstringBefore should return the expected result (string)")] + [InlineData("First:Second", "First", ":")] + [InlineData("12345+678910", "12345", "+")] + public void SubstringBeforeShouldReturnTheExpectedResultS(string value, string expected, string delimiter) + { + // Arrange / Act + string actual = value.SubstringBefore(delimiter); + + // Assert + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "SubstringBeforeLast should return the expected result (char)")] + [InlineData("First:Second:Third", "First:Second", ':')] + [InlineData("12345+678910+12345", "12345+678910", '+')] + public void SubstringBeforeLastShouldReturnTheExpectedResultC(string value, string expected, char delimiter) + { + // Arrange / Act + string actual = value.SubstringBeforeLast(delimiter); + + // Assert + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "SubstringBeforeLast should return the expected result (string)")] + [InlineData("First:Second:Third", "First:Second", ":")] + [InlineData("12345+678910+12345", "12345+678910", "+")] + public void SubstringBeforeLastShouldReturnTheExpectedResultS(string value, string expected, string delimiter) + { + // Arrange / Act + string actual = value.SubstringBeforeLast(delimiter); + + // Assert + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "SubstringAfter should return the expected result (char)")] + [InlineData("First:Second", "Second", ':')] + [InlineData("12345+678910", "678910", '+')] + public void SubstringAfterShouldReturnTheExpectedResultC(string value, string expected, char delimiter) + { + // Arrange / Act + string actual = value.SubstringAfter(delimiter); + + // Assert + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "SubstringAfter should return the expected result (string)")] + [InlineData("First:Second", "Second", ":")] + [InlineData("12345+678910", "678910", "+")] + public void SubstringAfterShouldReturnTheExpectedResultS(string value, string expected, string delimiter) + { + // Arrange / Act + string actual = value.SubstringAfter(delimiter); + + // Assert + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "SubstringAfterLast should return the expected result (char)")] + [InlineData("First:Second:Third", "Third", ':')] + [InlineData("12345+678910+12345", "12345", '+')] + public void SubstringAfterLastShouldReturnTheExpectedResultC(string value, string expected, char delimiter) + { + // Arrange / Act + string actual = value.SubstringAfterLast(delimiter); + + // Assert + Assert.Equal(expected, actual); + } + + [Theory(DisplayName = "SubstringAfterLast should return the expected result (string)")] + [InlineData("First:Second:Third", "Third", ":")] + [InlineData("12345+678910+12345", "12345", "+")] + public void SubstringAfterLastShouldReturnTheExpectedResultS(string value, string expected, string delimiter) + { + // Arrange / Act + string actual = value.SubstringAfterLast(delimiter); + + // Assert + Assert.Equal(expected, actual); + } + } +} diff --git a/OnixLabs.Core/OnixLabs.Core.csproj b/OnixLabs.Core/OnixLabs.Core.csproj index 55772a8..8f94224 100644 --- a/OnixLabs.Core/OnixLabs.Core.csproj +++ b/OnixLabs.Core/OnixLabs.Core.csproj @@ -8,10 +8,11 @@ OnixLabs.Core ONIXLabs ONIXLabs Core API for .NET - 1.0.0 + 2.0.0 en Copyright © ONIXLabs 2020-2021 https://github.com/onix-labs/onixlabs-dotnet + 2.0.0 @@ -19,4 +20,9 @@ bin\Debug\net5.0\OnixLabs.Core.xml + + bin\Release\net5.0\OnixLabs.Core.xml + true + + diff --git a/OnixLabs.Core/StringExtensions.cs b/OnixLabs.Core/StringExtensions.cs new file mode 100644 index 0000000..e0c208a --- /dev/null +++ b/OnixLabs.Core/StringExtensions.cs @@ -0,0 +1,207 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.ComponentModel; + +namespace OnixLabs.Core +{ + /// + /// Provides extension methods for strings. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class StringExtensions + { + /// + /// The value of an index not found. + /// + private const int IndexNotFound = -1; + + /// + /// Returns a substring before the first occurrence of the specified delimiter. + /// + /// The original from which to obtain a sub-string. + /// The delimiter to find within the original string. + /// The value to return if the delimiter is not found, which defaults to the original string. + /// Returns a substring before the first occurrence of the specified delimiter. + public static string SubstringBefore( + this string value, + char delimiter, + string? defaultValue = null) + { + int index = value.IndexOf(delimiter); + + return index switch + { + IndexNotFound => defaultValue ?? value, + _ => value[..index] + }; + } + + /// + /// Returns a substring before the first occurrence of the specified delimiter. + /// + /// The original from which to obtain a sub-string. + /// The delimiter to find within the original string. + /// The value to return if the delimiter is not found, which defaults to the original string. + /// Specifies the string comparison rule for the search. + /// Returns a substring before the first occurrence of the specified delimiter. + public static string SubstringBefore( + this string value, + string delimiter, + string? defaultValue = null, + StringComparison comparison = StringComparison.Ordinal) + { + int index = value.IndexOf(delimiter, comparison); + + return index switch + { + IndexNotFound => defaultValue ?? value, + _ => value[..index] + }; + } + + /// + /// Returns a substring before the last occurrence of the specified delimiter. + /// + /// The original from which to obtain a sub-string. + /// The delimiter to find within the original string. + /// The value to return if the delimiter is not found, which defaults to the original string. + /// Returns a substring before the last occurrence of the specified delimiter. + public static string SubstringBeforeLast( + this string value, + char delimiter, + string? defaultValue = null) + { + int index = value.LastIndexOf(delimiter); + + return index switch + { + IndexNotFound => defaultValue ?? value, + _ => value[..index] + }; + } + + /// + /// Returns a substring before the last occurrence of the specified delimiter. + /// + /// The original from which to obtain a sub-string. + /// The delimiter to find within the original string. + /// The value to return if the delimiter is not found, which defaults to the original string. + /// Specifies the string comparison rule for the search. + /// Returns a substring before the last occurrence of the specified delimiter. + public static string SubstringBeforeLast( + this string value, + string delimiter, + string? defaultValue = null, + StringComparison comparison = StringComparison.Ordinal) + { + int index = value.LastIndexOf(delimiter, comparison); + + return index switch + { + IndexNotFound => defaultValue ?? value, + _ => value[..index] + }; + } + + /// + /// Returns a substring after the first occurrence of the specified delimiter. + /// + /// The original from which to obtain a sub-string. + /// The delimiter to find within the original string. + /// The value to return if the delimiter is not found, which defaults to the original string. + /// Returns a substring after the first occurrence of the specified delimiter. + public static string SubstringAfter( + this string value, + char delimiter, + string? defaultValue = null) + { + int index = value.IndexOf(delimiter); + + return index switch + { + IndexNotFound => defaultValue ?? value, + _ => value[(index + 1)..value.Length] + }; + } + + /// + /// Returns a substring after the first occurrence of the specified delimiter. + /// + /// The original from which to obtain a sub-string. + /// The delimiter to find within the original string. + /// The value to return if the delimiter is not found, which defaults to the original string. + /// Specifies the string comparison rule for the search. + /// Returns a substring after the first occurrence of the specified delimiter. + public static string SubstringAfter( + this string value, + string delimiter, + string? defaultValue = null, + StringComparison comparison = StringComparison.Ordinal) + { + int index = value.IndexOf(delimiter, comparison); + + return index switch + { + IndexNotFound => defaultValue ?? value, + _ => value[(index + delimiter.Length)..value.Length] + }; + } + + /// + /// Returns a substring after the last occurrence of the specified delimiter. + /// + /// The original from which to obtain a sub-string. + /// The delimiter to find within the original string. + /// The value to return if the delimiter is not found, which defaults to the original string. + /// Returns a substring after the last occurrence of the specified delimiter. + public static string SubstringAfterLast( + this string value, + char delimiter, + string? defaultValue = null) + { + int index = value.LastIndexOf(delimiter); + + return index switch + { + IndexNotFound => defaultValue ?? value, + _ => value[(index + 1)..value.Length] + }; + } + + /// + /// Returns a substring after the last occurrence of the specified delimiter. + /// + /// The original from which to obtain a sub-string. + /// The delimiter to find within the original string. + /// The value to return if the delimiter is not found, which defaults to the original string. + /// Specifies the string comparison rule for the search. + /// Returns a substring after the last occurrence of the specified delimiter. + public static string SubstringAfterLast( + this string value, + string delimiter, + string? defaultValue = null, + StringComparison comparison = StringComparison.Ordinal) + { + int index = value.LastIndexOf(delimiter, comparison); + + return index switch + { + IndexNotFound => defaultValue ?? value, + _ => value[(index + delimiter.Length)..value.Length] + }; + } + } +} diff --git a/OnixLabs.Core/Text/Base16.Equatable.cs b/OnixLabs.Core/Text/Base16.Equatable.cs index 022e921..b34f0f3 100644 --- a/OnixLabs.Core/Text/Base16.Equatable.cs +++ b/OnixLabs.Core/Text/Base16.Equatable.cs @@ -51,7 +51,7 @@ namespace OnixLabs.Core.Text /// true if the object is equal to this instance; otherwise, false. public bool Equals(Base16 other) { - return other.value.SequenceEqual(value); + return other.Value.SequenceEqual(Value); } /// @@ -70,7 +70,7 @@ public override bool Equals(object? obj) /// A hash code for this instance. public override int GetHashCode() { - return HashCode.Combine(value); + return HashCode.Combine(Value); } } } diff --git a/OnixLabs.Core/Text/Base16.To.cs b/OnixLabs.Core/Text/Base16.To.cs index 73cb131..47e054f 100644 --- a/OnixLabs.Core/Text/Base16.To.cs +++ b/OnixLabs.Core/Text/Base16.To.cs @@ -28,7 +28,7 @@ public readonly partial struct Base16 /// Returns a array that represents the current object. public byte[] ToByteArray() { - return value.Copy(); + return Value.Copy(); } /// @@ -47,7 +47,7 @@ public string ToPlainTextString() /// Returns a that represents the current object in plain text. public string ToPlainTextString(Encoding encoding) { - return encoding.GetString(value); + return encoding.GetString(Value); } /// @@ -56,7 +56,7 @@ public string ToPlainTextString(Encoding encoding) /// A that represents the current object. public override string ToString() { - return Convert.ToHexString(value).ToLower(); + return Convert.ToHexString(Value).ToLower(); } } } diff --git a/OnixLabs.Core/Text/Base16.cs b/OnixLabs.Core/Text/Base16.cs index 7a66bff..1dde93f 100644 --- a/OnixLabs.Core/Text/Base16.cs +++ b/OnixLabs.Core/Text/Base16.cs @@ -19,18 +19,18 @@ namespace OnixLabs.Core.Text /// public readonly partial struct Base16 { - /// - /// The underlying value. - /// - private readonly byte[] value; - /// /// Initializes a new instance of the struct. /// /// The underlying value. private Base16(byte[] value) { - this.value = value; + Value = value; } + + /// + /// Gets the underlying value. + /// + private byte[] Value { get; } } } diff --git a/OnixLabs.Core/Text/Base32.Equatable.cs b/OnixLabs.Core/Text/Base32.Equatable.cs index 340427e..0272094 100644 --- a/OnixLabs.Core/Text/Base32.Equatable.cs +++ b/OnixLabs.Core/Text/Base32.Equatable.cs @@ -51,9 +51,9 @@ namespace OnixLabs.Core.Text /// true if the object is equal to this instance; otherwise, false. public bool Equals(Base32 other) { - return other.value.SequenceEqual(value) - && other.alphabet == alphabet - && other.padding == padding; + return other.Value.SequenceEqual(Value) + && other.Alphabet == Alphabet + && other.Padding == Padding; } /// @@ -72,7 +72,7 @@ public override bool Equals(object? obj) /// A hash code for this instance. public override int GetHashCode() { - return HashCode.Combine(value); + return HashCode.Combine(Value); } } } diff --git a/OnixLabs.Core/Text/Base32.To.cs b/OnixLabs.Core/Text/Base32.To.cs index c449694..e4de54c 100644 --- a/OnixLabs.Core/Text/Base32.To.cs +++ b/OnixLabs.Core/Text/Base32.To.cs @@ -27,7 +27,7 @@ public readonly partial struct Base32 /// Returns a array that represents the current object. public byte[] ToByteArray() { - return value.Copy(); + return Value.Copy(); } /// @@ -46,7 +46,7 @@ public string ToPlainTextString() /// Returns a that represents the current object in plain text. public string ToPlainTextString(Encoding encoding) { - return encoding.GetString(value); + return encoding.GetString(Value); } /// @@ -55,7 +55,7 @@ public string ToPlainTextString(Encoding encoding) /// A that represents the current object. public override string ToString() { - return Encode(value, alphabet.Alphabet, padding); + return Encode(Value, Alphabet.Alphabet, Padding); } } } diff --git a/OnixLabs.Core/Text/Base32.cs b/OnixLabs.Core/Text/Base32.cs index 0a18f2c..1637779 100644 --- a/OnixLabs.Core/Text/Base32.cs +++ b/OnixLabs.Core/Text/Base32.cs @@ -20,31 +20,31 @@ namespace OnixLabs.Core.Text public readonly partial struct Base32 { /// - /// The underlying value. + /// Initializes a new instance of the struct. /// - private readonly byte[] value; + /// The underlying value. + /// The alphabet that will be used for Base-32 encoding and decoding operations. + /// Determines whether padding should be applied for Base-32 encoding and decoding operations. + private Base32(byte[] value, Base32Alphabet alphabet, bool padding) + { + Value = value; + Alphabet = alphabet; + Padding = padding; + } /// - /// The alphabet that will be used for Base-32 encoding and decoding operations. + /// Gets the underlying value. /// - private readonly Base32Alphabet alphabet; + private byte[] Value { get; } /// - /// Determines whether padding should be applied for Base-32 encoding and decoding operations. + /// Gets the alphabet that will be used for Base-32 encoding and decoding operations. /// - private readonly bool padding; + private Base32Alphabet Alphabet { get; } /// - /// Initializes a new instance of the struct. + /// Gets a value that determines whether padding should be applied for Base-32 encoding and decoding operations. /// - /// The underlying value. - /// The alphabet that will be used for Base-32 encoding and decoding operations. - /// Determines whether padding should be applied for Base-32 encoding and decoding operations. - private Base32(byte[] value, Base32Alphabet alphabet, bool padding) - { - this.value = value; - this.alphabet = alphabet; - this.padding = padding; - } + private bool Padding { get; } } } diff --git a/OnixLabs.Core/Text/Base58.Equatable.cs b/OnixLabs.Core/Text/Base58.Equatable.cs index 9e56587..844a4cf 100644 --- a/OnixLabs.Core/Text/Base58.Equatable.cs +++ b/OnixLabs.Core/Text/Base58.Equatable.cs @@ -51,8 +51,8 @@ namespace OnixLabs.Core.Text /// true if the object is equal to this instance; otherwise, false. public bool Equals(Base58 other) { - return other.value.SequenceEqual(value) - && other.alphabet == alphabet; + return other.Value.SequenceEqual(Value) + && other.Alphabet == Alphabet; } /// @@ -71,7 +71,7 @@ public override bool Equals(object? obj) /// A hash code for this instance. public override int GetHashCode() { - return HashCode.Combine(value); + return HashCode.Combine(Value); } } } diff --git a/OnixLabs.Core/Text/Base58.To.cs b/OnixLabs.Core/Text/Base58.To.cs index 97d27c8..a9d47b4 100644 --- a/OnixLabs.Core/Text/Base58.To.cs +++ b/OnixLabs.Core/Text/Base58.To.cs @@ -27,7 +27,7 @@ public readonly partial struct Base58 /// Returns a array that represents the current object. public byte[] ToByteArray() { - return value.Copy(); + return Value.Copy(); } /// @@ -36,8 +36,8 @@ public byte[] ToByteArray() /// A that represents the current object, with a checksum. public string ToStringWithChecksum() { - byte[] valueWithChecksum = AddChecksum(value); - return Encode(valueWithChecksum, alphabet.Alphabet); + byte[] valueWithChecksum = AddChecksum(Value); + return Encode(valueWithChecksum, Alphabet.Alphabet); } /// @@ -56,7 +56,7 @@ public string ToPlainTextString() /// Returns a that represents the current object in plain text. public string ToPlainTextString(Encoding encoding) { - return encoding.GetString(value); + return encoding.GetString(Value); } @@ -66,7 +66,7 @@ public string ToPlainTextString(Encoding encoding) /// A that represents the current object. public override string ToString() { - return Encode(value, alphabet.Alphabet); + return Encode(Value, Alphabet.Alphabet); } } } diff --git a/OnixLabs.Core/Text/Base58.cs b/OnixLabs.Core/Text/Base58.cs index 42028e4..0e8c674 100644 --- a/OnixLabs.Core/Text/Base58.cs +++ b/OnixLabs.Core/Text/Base58.cs @@ -19,16 +19,6 @@ namespace OnixLabs.Core.Text /// public readonly partial struct Base58 { - /// - /// The underlying value. - /// - private readonly byte[] value; - - /// - /// The alphabet that will be used for Base-58 encoding and decoding operations. - /// - private readonly Base58Alphabet alphabet; - /// /// Initializes a new instance of the struct. /// @@ -36,8 +26,18 @@ public readonly partial struct Base58 /// The alphabet that will be used for Base-58 encoding and decoding operations. private Base58(byte[] value, Base58Alphabet alphabet) { - this.value = value; - this.alphabet = alphabet; + Value = value; + Alphabet = alphabet; } + + /// + /// Gets the underlying value. + /// + private byte[] Value { get; } + + /// + /// Gets the alphabet that will be used for Base-58 encoding and decoding operations. + /// + private Base58Alphabet Alphabet { get; } } } diff --git a/OnixLabs.Core/Text/Base64.Equatable.cs b/OnixLabs.Core/Text/Base64.Equatable.cs index d9cdcf4..302f1a2 100644 --- a/OnixLabs.Core/Text/Base64.Equatable.cs +++ b/OnixLabs.Core/Text/Base64.Equatable.cs @@ -51,7 +51,7 @@ namespace OnixLabs.Core.Text /// true if the object is equal to this instance; otherwise, false. public bool Equals(Base64 other) { - return other.value.SequenceEqual(value); + return other.Value.SequenceEqual(Value); } /// @@ -70,7 +70,7 @@ public override bool Equals(object? obj) /// A hash code for this instance. public override int GetHashCode() { - return HashCode.Combine(value); + return HashCode.Combine(Value); } } } diff --git a/OnixLabs.Core/Text/Base64.To.cs b/OnixLabs.Core/Text/Base64.To.cs index 831a46e..7eef932 100644 --- a/OnixLabs.Core/Text/Base64.To.cs +++ b/OnixLabs.Core/Text/Base64.To.cs @@ -28,7 +28,7 @@ public readonly partial struct Base64 /// Returns a array that represents the current object. public byte[] ToByteArray() { - return value.Copy(); + return Value.Copy(); } /// @@ -47,7 +47,7 @@ public string ToPlainTextString() /// Returns a that represents the current object in plain text. public string ToPlainTextString(Encoding encoding) { - return encoding.GetString(value); + return encoding.GetString(Value); } /// @@ -56,7 +56,7 @@ public string ToPlainTextString(Encoding encoding) /// A that represents the current object. public override string ToString() { - return Convert.ToBase64String(value); + return Convert.ToBase64String(Value); } } } diff --git a/OnixLabs.Core/Text/Base64.cs b/OnixLabs.Core/Text/Base64.cs index 051d05d..9c99edc 100644 --- a/OnixLabs.Core/Text/Base64.cs +++ b/OnixLabs.Core/Text/Base64.cs @@ -19,18 +19,18 @@ namespace OnixLabs.Core.Text /// public readonly partial struct Base64 { - /// - /// The underlying value. - /// - private readonly byte[] value; - /// /// Initializes a new instance of the struct. /// /// The underlying value. private Base64(byte[] value) { - this.value = value; + Value = value; } + + /// + /// Gets the underlying value. + /// + private byte[] Value { get; } } } diff --git a/OnixLabs.Playground/OnixLabs.Playground.csproj b/OnixLabs.Playground/OnixLabs.Playground.csproj index af40665..c26e393 100644 --- a/OnixLabs.Playground/OnixLabs.Playground.csproj +++ b/OnixLabs.Playground/OnixLabs.Playground.csproj @@ -8,6 +8,7 @@ + diff --git a/OnixLabs.Security.Cryptography.UnitTests/HashTests.cs b/OnixLabs.Security.Cryptography.UnitTests/HashTests.cs index 26f430e..714fab0 100644 --- a/OnixLabs.Security.Cryptography.UnitTests/HashTests.cs +++ b/OnixLabs.Security.Cryptography.UnitTests/HashTests.cs @@ -39,5 +39,34 @@ public void DifferentHashesShouldNotBeConsideredEqual() // Assert Assert.NotEqual(a, b); } + + [Fact(DisplayName = "Parse should be able to parse a known hash")] + public void ParseShouldBeAbleToParseAKnownHash() + { + // Arrange + const string expected = "Sha2Hash256:dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"; + + // Act + Hash hash = Hash.Parse(expected); + string actual = hash.ToStringWithAlgorithmType(); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Parse should be able to parse an unknown hash")] + public void ParseShouldBeAbleToParseAnUnknownHash() + { + // Arrange + const string value = "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"; + const string expected = "Unknown:dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"; + + // Act + Hash hash = Hash.Parse(value); + string actual = hash.ToStringWithAlgorithmType(); + + // Assert + Assert.Equal(expected, actual); + } } } diff --git a/OnixLabs.Security.Cryptography.UnitTests/HmacTests.cs b/OnixLabs.Security.Cryptography.UnitTests/HmacTests.cs new file mode 100644 index 0000000..c1a9896 --- /dev/null +++ b/OnixLabs.Security.Cryptography.UnitTests/HmacTests.cs @@ -0,0 +1,69 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Xunit; + +namespace OnixLabs.Security.Cryptography.UnitTests +{ + public sealed class HmacTests + { + [Fact(DisplayName = "Identical HMACs should be considered equal")] + public void IdenticalHashesShouldBeConsideredEqual() + { + // Arrange + Hmac a = Hmac.ComputeSha2Hmac256("abc", "key"); + Hmac b = Hmac.ComputeSha2Hmac256("abc", "key"); + + // Assert + Assert.Equal(a, b); + } + + [Fact(DisplayName = "Different HMACs should not be considered equal (different data)")] + public void DifferentHashesShouldNotBeConsideredEqualWithDifferentData() + { + // Arrange + Hmac a = Hmac.ComputeSha2Hmac256("abc", "key"); + Hmac b = Hmac.ComputeSha2Hmac256("xyz", "key"); + + // Assert + Assert.NotEqual(a, b); + } + + [Fact(DisplayName = "Different HMACs should not be considered equal (different keys)")] + public void DifferentHashesShouldNotBeConsideredEqualWithDifferentKeys() + { + // Arrange + Hmac a = Hmac.ComputeSha2Hmac256("abc", "key"); + Hmac b = Hmac.ComputeSha2Hmac256("abc", "123"); + + // Assert + Assert.NotEqual(a, b); + } + + [Fact(DisplayName = "Parse should be able to parse a known hash")] + public void ParseShouldBeAbleToParseAKnownHash() + { + // Arrange + const string expected = + "Sha2Hmac256:73ac6fa8599f4bde8dfee594c7f5f6ff03023b2d99ca71a7eccf729a8fc5c324:48656c6c6f2c20576f726c6421"; + + // Act + Hmac hash = Hmac.Parse(expected); + string actual = hash.ToStringWithAlgorithmType(); + + // Assert + Assert.Equal(expected, actual); + } + } +} diff --git a/OnixLabs.Security.Cryptography/DigitalSignature.Equatable.cs b/OnixLabs.Security.Cryptography/DigitalSignature.Equatable.cs index 5750a94..706289c 100644 --- a/OnixLabs.Security.Cryptography/DigitalSignature.Equatable.cs +++ b/OnixLabs.Security.Cryptography/DigitalSignature.Equatable.cs @@ -51,7 +51,7 @@ namespace OnixLabs.Security.Cryptography /// true if the object is equal to this instance; otherwise, false. public bool Equals(DigitalSignature other) { - return other.value.SequenceEqual(value); + return other.Value.SequenceEqual(Value); } /// @@ -70,7 +70,7 @@ public override bool Equals(object? obj) /// A hash code for this instance. public override int GetHashCode() { - return HashCode.Combine(value); + return HashCode.Combine(Value); } } } diff --git a/OnixLabs.Security.Cryptography/DigitalSignature.To.cs b/OnixLabs.Security.Cryptography/DigitalSignature.To.cs index 47e7fe1..acfe520 100644 --- a/OnixLabs.Security.Cryptography/DigitalSignature.To.cs +++ b/OnixLabs.Security.Cryptography/DigitalSignature.To.cs @@ -29,7 +29,7 @@ public readonly partial struct DigitalSignature /// A byte array containing the underlying signed data. public byte[] ToByteArray() { - return value.Copy(); + return Value.Copy(); } /// @@ -38,7 +38,7 @@ public byte[] ToByteArray() /// Returns a value that represents the underlying signature data. public Base16 ToBase16() { - return Base16.FromByteArray(value); + return Base16.FromByteArray(Value); } /// @@ -57,7 +57,7 @@ public Base32 ToBase32() /// Returns a value that represents the underlying signature data. public Base32 ToBase32(Base32Alphabet alphabet) { - return Base32.FromByteArray(value, alphabet); + return Base32.FromByteArray(Value, alphabet); } /// @@ -76,7 +76,7 @@ public Base58 ToBase58() /// Returns a value that represents the underlying signature data. public Base58 ToBase58(Base58Alphabet alphabet) { - return Base58.FromByteArray(value, alphabet); + return Base58.FromByteArray(Value, alphabet); } /// @@ -85,7 +85,7 @@ public Base58 ToBase58(Base58Alphabet alphabet) /// Returns a value that represents the underlying signature data. public Base64 ToBase64() { - return Base64.FromByteArray(value); + return Base64.FromByteArray(Value); } /// @@ -94,7 +94,7 @@ public Base64 ToBase64() /// A that represents the current object. public override string ToString() { - return Convert.ToHexString(value).ToLower(); + return Convert.ToHexString(Value).ToLower(); } } } diff --git a/OnixLabs.Security.Cryptography/DigitalSignature.cs b/OnixLabs.Security.Cryptography/DigitalSignature.cs index 159adea..c89644a 100644 --- a/OnixLabs.Security.Cryptography/DigitalSignature.cs +++ b/OnixLabs.Security.Cryptography/DigitalSignature.cs @@ -19,18 +19,18 @@ namespace OnixLabs.Security.Cryptography /// public readonly partial struct DigitalSignature { - /// - /// The underlying digitally signed data. - /// - private readonly byte[] value; - /// /// Initializes a new instance of the struct. /// /// The digitally signed data. private DigitalSignature(byte[] value) { - this.value = value; + Value = value; } + + /// + /// Gets the underlying digitally signed data. + /// + private byte[] Value { get; } } } diff --git a/OnixLabs.Security.Cryptography/Hash.Equatable.cs b/OnixLabs.Security.Cryptography/Hash.Equatable.cs index f7f25c1..9d30459 100644 --- a/OnixLabs.Security.Cryptography/Hash.Equatable.cs +++ b/OnixLabs.Security.Cryptography/Hash.Equatable.cs @@ -51,7 +51,8 @@ namespace OnixLabs.Security.Cryptography /// true if the object is equal to this instance; otherwise, false. public bool Equals(Hash other) { - return other.value.SequenceEqual(value) && other.AlgorithmType == AlgorithmType; + return other.AlgorithmType == AlgorithmType + && other.Value.SequenceEqual(Value); } /// @@ -61,7 +62,7 @@ public bool Equals(Hash other) /// true if the object is equal to this instance; otherwise, false. public override bool Equals(object? obj) { - return obj is Hash hash && Equals(hash); + return obj is Hash other && Equals(other); } /// @@ -70,7 +71,7 @@ public override bool Equals(object? obj) /// A hash code for this instance. public override int GetHashCode() { - return HashCode.Combine(value, AlgorithmType); + return HashCode.Combine(Value, AlgorithmType); } } } diff --git a/OnixLabs.Security.Cryptography/Hash.Parse.cs b/OnixLabs.Security.Cryptography/Hash.Parse.cs index 9b3d8d0..6e22d53 100644 --- a/OnixLabs.Security.Cryptography/Hash.Parse.cs +++ b/OnixLabs.Security.Cryptography/Hash.Parse.cs @@ -13,6 +13,7 @@ // limitations under the License. using System; +using OnixLabs.Core; namespace OnixLabs.Security.Cryptography { @@ -22,26 +23,81 @@ namespace OnixLabs.Security.Cryptography public readonly partial struct Hash { /// - /// Converts a hexadecimal representation of a hash into a instance. - /// This will create a hash of an unknown type. + /// Parses a hexadecimal representation of a hash into a instance. /// /// A that contains a hash to convert. + /// The hash algorithm type of the hash. /// A new instance. - public static Hash Parse(string value) + public static Hash Parse(string value, HashAlgorithmType? type = null) { - return Parse(value, HashAlgorithmType.Unknown); + HashAlgorithmType parsedType = GetParsedHashAlgorithmType(value); + byte[] parsedValue = GetParsedHashValue(value); + + if (type is not null) + { + CheckMatchingHashAlgorithms(parsedType, type); + } + + return FromByteArray(parsedValue, parsedType); } /// - /// Converts a hexadecimal representation of a hash into a instance. + /// Attempts to parse a hexadecimal representation of a hash into a instance. /// /// A that contains a hash to convert. /// The hash algorithm type of the hash. - /// A new instance. - public static Hash Parse(string value, HashAlgorithmType type) + /// The result if conversion was successful. + /// Returns true if the hash conversion was successful; otherwise, false. + public static bool TryParse(string value, HashAlgorithmType? type, out Hash hash) + { + try + { + hash = Parse(value, type); + return true; + } + catch + { + hash = default; + return false; + } + } + + /// + /// Parses a from the specified value. + /// + /// The hash value to parse. + /// Returns a from the specified value. + private static HashAlgorithmType GetParsedHashAlgorithmType(string value) + { + string defaultHashAlgorithmType = HashAlgorithmType.Unknown.Name; + string parsedType = value.SubstringBefore(':', defaultHashAlgorithmType); + return HashAlgorithmType.FromName(parsedType); + } + + /// + /// Parses a array from the specified value. + /// + /// The hash value to parse. + /// Returns a array from the specified value. + private static byte[] GetParsedHashValue(string value) + { + string parsedValue = value.SubstringAfter(':'); + return Convert.FromHexString(parsedValue); + } + + /// + /// Checks that the parsed and specified instances match. + /// + /// The parsed to check. + /// The specified to check. + /// If the instances do not match. + private static void CheckMatchingHashAlgorithms(HashAlgorithmType parsed, HashAlgorithmType specified) { - byte[] bytes = Convert.FromHexString(value); - return new Hash(bytes, type); + if (parsed != specified) + { + throw new InvalidOperationException( + $"The parsed hash algorithm type '{parsed}' does not match the expected hash algorithm type '{specified}'."); + } } } } diff --git a/OnixLabs.Security.Cryptography/Hash.To.cs b/OnixLabs.Security.Cryptography/Hash.To.cs index 49c80d2..c2af356 100644 --- a/OnixLabs.Security.Cryptography/Hash.To.cs +++ b/OnixLabs.Security.Cryptography/Hash.To.cs @@ -29,7 +29,7 @@ public readonly partial struct Hash /// A array containing the underlying hash data. public byte[] ToByteArray() { - return value.Copy(); + return Value.Copy(); } /// @@ -38,7 +38,7 @@ public byte[] ToByteArray() /// Returns a value that represents the underlying hash data. public Base16 ToBase16() { - return Base16.FromByteArray(value); + return Base16.FromByteArray(Value); } /// @@ -57,7 +57,7 @@ public Base32 ToBase32() /// Returns a value that represents the underlying hash data. public Base32 ToBase32(Base32Alphabet alphabet) { - return Base32.FromByteArray(value, alphabet); + return Base32.FromByteArray(Value, alphabet); } /// @@ -76,7 +76,7 @@ public Base58 ToBase58() /// Returns a value that represents the underlying hash data. public Base58 ToBase58(Base58Alphabet alphabet) { - return Base58.FromByteArray(value, alphabet); + return Base58.FromByteArray(Value, alphabet); } /// @@ -85,7 +85,7 @@ public Base58 ToBase58(Base58Alphabet alphabet) /// Returns a value that represents the underlying hash data. public Base64 ToBase64() { - return Base64.FromByteArray(value); + return Base64.FromByteArray(Value); } /// @@ -94,7 +94,16 @@ public Base64 ToBase64() /// A that represents the current object. public override string ToString() { - return Convert.ToHexString(value).ToLower(); + return Convert.ToHexString(Value).ToLower(); + } + + /// + /// Returns a that represents the current object, including the hash algorithm type. + /// + /// A that represents the current object, including the hash algorithm type. + public string ToStringWithAlgorithmType() + { + return $"{AlgorithmType.Name}:{ToString()}"; } } } diff --git a/OnixLabs.Security.Cryptography/Hash.cs b/OnixLabs.Security.Cryptography/Hash.cs index 99ca022..c97952c 100644 --- a/OnixLabs.Security.Cryptography/Hash.cs +++ b/OnixLabs.Security.Cryptography/Hash.cs @@ -19,11 +19,6 @@ namespace OnixLabs.Security.Cryptography /// public readonly partial struct Hash { - /// - /// The underlying hexadecimal value of the hash. - /// - private readonly byte[] value; - /// /// Initializes a new instance of the struct. /// @@ -33,13 +28,18 @@ private Hash(byte[] value, HashAlgorithmType type) { type.VerifyHashLength(value); - this.value = value; AlgorithmType = type; + Value = value; } /// /// Gets the hash algorithm type of the hash. /// public HashAlgorithmType AlgorithmType { get; } + + /// + /// Gets the underlying value of the hash. + /// + private byte[] Value { get; } } } diff --git a/OnixLabs.Security.Cryptography/HashAlgorithmType.Enumerations.cs b/OnixLabs.Security.Cryptography/HashAlgorithmType.Enumerations.cs new file mode 100644 index 0000000..a411c56 --- /dev/null +++ b/OnixLabs.Security.Cryptography/HashAlgorithmType.Enumerations.cs @@ -0,0 +1,107 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Specifies values that define known hash algorithm types. + /// + public sealed partial class HashAlgorithmType + { + /// + /// An unknown hash algorithm. + /// + public static readonly HashAlgorithmType Unknown = new(0, nameof(Unknown), UnknownLength, false); + + /// + /// The MD5 hash algorithm. + /// + public static readonly HashAlgorithmType Md5Hash = new(1, nameof(Md5Hash), 16, false); + + /// + /// The SHA-1 hash algorithm. + /// + public static readonly HashAlgorithmType Sha1Hash = new(2, nameof(Sha1Hash), 20, false); + + /// + /// The SHA-2 256-bit hash algorithm. + /// + public static readonly HashAlgorithmType Sha2Hash256 = new(3, nameof(Sha2Hash256), 32, false); + + /// + /// The SHA-2 384-bit hash algorithm. + /// + public static readonly HashAlgorithmType Sha2Hash384 = new(4, nameof(Sha2Hash384), 48, false); + + /// + /// The SHA-2 512-bit hash algorithm. + /// + public static readonly HashAlgorithmType Sha2Hash512 = new(5, nameof(Sha2Hash512), 64, false); + + /// + /// The SHA-3 224-bit hash algorithm. + /// + public static readonly HashAlgorithmType Sha3Hash224 = new(6, nameof(Sha3Hash224), 28, false); + + /// + /// The SHA-3 256-bit hash algorithm. + /// + public static readonly HashAlgorithmType Sha3Hash256 = new(7, nameof(Sha3Hash256), 32, false); + + /// + /// The SHA-3 384-bit hash algorithm. + /// + public static readonly HashAlgorithmType Sha3Hash384 = new(8, nameof(Sha3Hash384), 48, false); + + /// + /// The SHA-3 512-bit hash algorithm. + /// + public static readonly HashAlgorithmType Sha3Hash512 = new(9, nameof(Sha3Hash512), 64, false); + + /// + /// The SHA-3 Shake 128-bit hash algorithm. + /// + public static readonly HashAlgorithmType Sha3Shake128 = new(10, nameof(Sha3Shake128), UnknownLength, false); + + /// + /// The SHA-3 Shake 256-bit hash algorithm. + /// + public static readonly HashAlgorithmType Sha3Shake256 = new(11, nameof(Sha3Shake256), UnknownLength, false); + + /// + /// The MD5 HMAC keyed hash algorithm. + /// + public static readonly HashAlgorithmType Md5Hmac = new(12, nameof(Md5Hmac), 16, true); + + /// + /// The SHA-1 HMAC keyed hash algorithm. + /// + public static readonly HashAlgorithmType Sha1Hmac = new(13, nameof(Sha1Hmac), 20, true); + + /// + /// The SHA-2 256-bit HMAC keyed hash algorithm. + /// + public static readonly HashAlgorithmType Sha2Hmac256 = new(14, nameof(Sha2Hmac256), 32, true); + + /// + /// The SHA-2 384-bit HMAC keyed hash algorithm. + /// + public static readonly HashAlgorithmType Sha2Hmac384 = new(15, nameof(Sha2Hmac384), 48, true); + + /// + /// The SHA-2 512-bit HMAC keyed hash algorithm. + /// + public static readonly HashAlgorithmType Sha2Hmac512 = new(16, nameof(Sha2Hmac512), 64, true); + } +} diff --git a/OnixLabs.Security.Cryptography/HashAlgorithmType.Equatable.cs b/OnixLabs.Security.Cryptography/HashAlgorithmType.Equatable.cs new file mode 100644 index 0000000..2d99054 --- /dev/null +++ b/OnixLabs.Security.Cryptography/HashAlgorithmType.Equatable.cs @@ -0,0 +1,38 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Specifies values that define known hash algorithm types. + /// + public sealed partial class HashAlgorithmType + { + /// + /// Checks for equality between this and another . + /// This will return false if either this, or the other is . + /// + /// The object to check for equality. + /// + /// Returns true if this is equal to the other; otherwise, false. + /// If either is , this always returns false. + /// + public override bool Equals(HashAlgorithmType? other) + { + return !ReferenceEquals(this, Unknown) + && !ReferenceEquals(other, Unknown) + && base.Equals(other); + } + } +} diff --git a/OnixLabs.Security.Cryptography/HashAlgorithmType.Get.cs b/OnixLabs.Security.Cryptography/HashAlgorithmType.Get.cs new file mode 100644 index 0000000..d53777e --- /dev/null +++ b/OnixLabs.Security.Cryptography/HashAlgorithmType.Get.cs @@ -0,0 +1,98 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Security.Cryptography; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Specifies values that define known hash algorithm types. + /// + public sealed partial class HashAlgorithmType + { + /// + /// Gets the hash algorithm for this instance. + /// + /// Specifies the expected output hash length. + /// Returns a for this . + /// If the hash algorithm does not expect an output length. + /// If the hash algorithm is unknown. + public HashAlgorithm GetHashAlgorithm(int length = UnknownLength) + { + if (length != UnknownLength && Length > UnknownLength) + { + throw new ArgumentException($"Output length not expected for the specified hash algorithm: {Name}"); + } + + return Name switch + { + nameof(Md5Hash) => MD5.Create(), + nameof(Sha1Hash) => SHA1.Create(), + nameof(Sha2Hash256) => SHA256.Create(), + nameof(Sha2Hash384) => SHA384.Create(), + nameof(Sha2Hash512) => SHA512.Create(), + nameof(Sha3Hash224) => Sha3.CreateSha3Hash224(), + nameof(Sha3Hash256) => Sha3.CreateSha3Hash256(), + nameof(Sha3Hash384) => Sha3.CreateSha3Hash384(), + nameof(Sha3Hash512) => Sha3.CreateSha3Hash512(), + nameof(Sha3Shake128) => Sha3.CreateSha3Shake128(length), + nameof(Sha3Shake256) => Sha3.CreateSha3Shake256(length), + _ => throw new ArgumentException($"Hash algorithm '{Name}' is unknown.") + }; + } + + /// + /// Gets the keyed hash algorithm for this instance. + /// + /// The key that should be used by the keyed hash algorithm. + /// Returns a for this . + /// If the hash algorithm is unknown or is not a keyed hash algorithm. + public KeyedHashAlgorithm GetKeyedHashAlgorithm(byte[]? key = null) + { + if (!Keyed) + { + throw new ArgumentException($"Hash algorithm type '{Name}' is not a keyed hash algorithm."); + } + + return Name switch + { + nameof(Md5Hmac) => key is null ? new HMACMD5() : new HMACMD5(key), + nameof(Sha1Hmac) => key is null ? new HMACSHA1() : new HMACSHA1(key), + nameof(Sha2Hmac256) => key is null ? new HMACSHA256() : new HMACSHA256(key), + nameof(Sha2Hmac384) => key is null ? new HMACSHA384() : new HMACSHA384(key), + nameof(Sha2Hmac512) => key is null ? new HMACSHA512() : new HMACSHA512(key), + _ => throw new ArgumentException($"Hash algorithm '{Name}' is unknown.") + }; + } + + /// + /// Gets the equivalent for this . + /// + /// Returns the equivalent for this . + /// If there is no corresponding equivalent for this . + public HashAlgorithmName GetHashAlgorithmName() + { + return Name switch + { + nameof(Md5Hash) => HashAlgorithmName.MD5, + nameof(Sha1Hash) => HashAlgorithmName.SHA1, + nameof(Sha2Hash256) => HashAlgorithmName.SHA256, + nameof(Sha2Hash384) => HashAlgorithmName.SHA384, + nameof(Sha2Hash512) => HashAlgorithmName.SHA512, + _ => throw new ArgumentException($"No corresponding {nameof(HashAlgorithmName)} for '{Name}'.") + }; + } + } +} diff --git a/OnixLabs.Security.Cryptography/HashAlgorithmType.Verify.cs b/OnixLabs.Security.Cryptography/HashAlgorithmType.Verify.cs new file mode 100644 index 0000000..f5deb72 --- /dev/null +++ b/OnixLabs.Security.Cryptography/HashAlgorithmType.Verify.cs @@ -0,0 +1,47 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Specifies values that define known hash algorithm types. + /// + public sealed partial class HashAlgorithmType + { + /// + /// Determines whether the length of a byte array is valid. + /// + /// The byte array to length check. + /// Returns true if the length of the byte array is valid; otherwise, false. + public bool IsValidHashLength(byte[] value) + { + return Length == -1 || Length == value.Length; + } + + /// + /// Verifies that the length of a byte array is valid. + /// + /// The byte array to length check. + /// If the length of the byte array is invalid. + public void VerifyHashLength(byte[] value) + { + if (!IsValidHashLength(value)) + { + throw new ArgumentException("The length of the hash is invalid.", nameof(value)); + } + } + } +} diff --git a/OnixLabs.Security.Cryptography/HashAlgorithmType.cs b/OnixLabs.Security.Cryptography/HashAlgorithmType.cs index 1ed691f..20645f4 100644 --- a/OnixLabs.Security.Cryptography/HashAlgorithmType.cs +++ b/OnixLabs.Security.Cryptography/HashAlgorithmType.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using System.Security.Cryptography; using OnixLabs.Core; namespace OnixLabs.Security.Cryptography @@ -21,7 +19,7 @@ namespace OnixLabs.Security.Cryptography /// /// Specifies values that define known hash algorithm types. /// - public sealed class HashAlgorithmType : Enumeration + public sealed partial class HashAlgorithmType : Enumeration { /// /// A constant that defines an unknown hash algorithm length. @@ -29,74 +27,16 @@ public sealed class HashAlgorithmType : Enumeration public const int UnknownLength = -1; /// - /// An unknown hash algorithm. - /// - public static readonly HashAlgorithmType Unknown = new(0, nameof(Unknown), UnknownLength); - - /// - /// The MD5 hash algorithm. - /// - public static readonly HashAlgorithmType Md5Hash = new(1, nameof(Md5Hash), 16); - - /// - /// The SHA-1 hash algorithm. - /// - public static readonly HashAlgorithmType Sha1Hash = new(3, nameof(Sha1Hash), 20); - - /// - /// The SHA-2 256-bit hash algorithm. - /// - public static readonly HashAlgorithmType Sha2Hash256 = new(4, nameof(Sha2Hash256), 32); - - /// - /// The SHA-2 384-bit hash algorithm. - /// - public static readonly HashAlgorithmType Sha2Hash384 = new(5, nameof(Sha2Hash384), 48); - - /// - /// The SHA-2 512-bit hash algorithm. - /// - public static readonly HashAlgorithmType Sha2Hash512 = new(6, nameof(Sha2Hash512), 64); - - /// - /// The SHA-3 224-bit hash algorithm. - /// - public static readonly HashAlgorithmType Sha3Hash224 = new(7, nameof(Sha3Hash224), 28); - - /// - /// The SHA-3 256-bit hash algorithm. - /// - public static readonly HashAlgorithmType Sha3Hash256 = new(8, nameof(Sha3Hash256), 32); - - /// - /// The SHA-3 384-bit hash algorithm. - /// - public static readonly HashAlgorithmType Sha3Hash384 = new(9, nameof(Sha3Hash384), 48); - - /// - /// The SHA-3 512-bit hash algorithm. - /// - public static readonly HashAlgorithmType Sha3Hash512 = new(10, nameof(Sha3Hash512), 64); - - /// - /// The SHA-3 Shake 128-bit hash algorithm. - /// - public static readonly HashAlgorithmType Sha3Shake128 = new(11, nameof(Sha3Shake128), UnknownLength); - - /// - /// The SHA-3 Shake 256-bit hash algorithm. - /// - public static readonly HashAlgorithmType Sha3Shake256 = new(12, nameof(Sha3Shake256), UnknownLength); - - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The value of the hash algorithm type. /// The name of the hash algorithm type. /// The length in bytes of the hash algorithm type. - private HashAlgorithmType(int value, string name, int length) : base(value, name) + /// Determines whether the algorithm is a keyed hash algorithm. + private HashAlgorithmType(int value, string name, int length, bool keyed) : base(value, name) { Length = length; + Keyed = keyed; } /// @@ -106,94 +46,8 @@ private HashAlgorithmType(int value, string name, int length) : base(value, name public int Length { get; } /// - /// Gets the hash algorithm for this instance. - /// - /// Specifies the expected output hash length. - /// Returns a for this . - /// If the hash algorithm does not expect an output length. - /// If the hash algorithm is unknown. - public HashAlgorithm GetHashAlgorithm(int length = UnknownLength) - { - if (length != UnknownLength && Length > UnknownLength) - { - throw new ArgumentException($"Output length not expected for the specified hash algorithm: {Name}"); - } - - return Name switch - { - nameof(Md5Hash) => MD5.Create(), - nameof(Sha1Hash) => SHA1.Create(), - nameof(Sha2Hash256) => SHA256.Create(), - nameof(Sha2Hash384) => SHA384.Create(), - nameof(Sha2Hash512) => SHA512.Create(), - nameof(Sha3Hash224) => Sha3.CreateSha3Hash224(), - nameof(Sha3Hash256) => Sha3.CreateSha3Hash256(), - nameof(Sha3Hash384) => Sha3.CreateSha3Hash384(), - nameof(Sha3Hash512) => Sha3.CreateSha3Hash512(), - nameof(Sha3Shake128) => Sha3.CreateSha3Shake128(length), - nameof(Sha3Shake256) => Sha3.CreateSha3Shake256(length), - _ => throw new ArgumentException($"Unknown hash algorithm: {Name}.") - }; - } - - /// - /// Gets the equivalent for this . - /// - /// Returns the equivalent for this . - /// If there is no corresponding equivalent for this . - public HashAlgorithmName GetHashAlgorithmName() - { - return Name switch - { - nameof(Md5Hash) => HashAlgorithmName.MD5, - nameof(Sha1Hash) => HashAlgorithmName.SHA1, - nameof(Sha2Hash256) => HashAlgorithmName.SHA256, - nameof(Sha2Hash384) => HashAlgorithmName.SHA384, - nameof(Sha2Hash512) => HashAlgorithmName.SHA512, - _ => throw new ArgumentException($"No corresponding {nameof(HashAlgorithmName)} for {Name}.") - }; - } - - /// - /// Determines whether the length of a byte array is valid. - /// - /// The byte array to length check. - /// Returns true if the length of the byte array is valid; otherwise, false. - public bool IsValidHashLength(byte[] value) - { - return Length == -1 || Length == value.Length; - } - - /// - /// Verifies that the length of a byte array is valid. + /// Gets a value that determines whether the algorithm is a keyed hash algorithm. /// - /// The byte array to length check. - /// If the length of the byte array is invalid. - public void VerifyHashLength(byte[] value) - { - if (!IsValidHashLength(value)) - { - throw new ArgumentException("The length of the hash is invalid.", nameof(value)); - } - } - - /// - /// Checks for equality between this and another . - /// This will return false if either this, or the other is . - /// - /// The object to check for equality. - /// - /// Returns true if this is equal to the other; otherwise, false. - /// If either is , this always returns false. - /// - public override bool Equals(HashAlgorithmType? other) - { - if (ReferenceEquals(this, Unknown) || ReferenceEquals(other, Unknown)) - { - return false; - } - - return base.Equals(other); - } + public bool Keyed { get; } } } diff --git a/OnixLabs.Security.Cryptography/Hmac.Compute.Md5Hmac.cs b/OnixLabs.Security.Cryptography/Hmac.Compute.Md5Hmac.cs new file mode 100644 index 0000000..d8a2105 --- /dev/null +++ b/OnixLabs.Security.Cryptography/Hmac.Compute.Md5Hmac.cs @@ -0,0 +1,62 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Text; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a hashed message authentication code (HMAC). + /// + public readonly partial struct Hmac + { + /// + /// Computes an MD5 HMAC from the specified value and key. + /// This will use the default encoding to convert the input value and key into a byte array. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// Returns a of the input value and key. + public static Hmac ComputeMd5Hmac(string value, string key) + { + return ComputeMd5Hmac(value, key, Encoding.Default); + } + + /// + /// Computes an MD5 HMAC from the specified value and key. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// The encoding which will be used to convert the value and key into a byte array. + /// Returns a of the input value and key. + public static Hmac ComputeMd5Hmac(string value, string key, Encoding encoding) + { + byte[] valueBytes = encoding.GetBytes(value); + byte[] keyBytes = encoding.GetBytes(key); + + return ComputeMd5Hmac(valueBytes, keyBytes); + } + + /// + /// Computes an MD5 HMAC from the specified value and key. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// Returns a of the input value and key. + public static Hmac ComputeMd5Hmac(byte[] value, byte[] key) + { + return ComputeHmac(value, key, HashAlgorithmType.Md5Hmac); + } + } +} diff --git a/OnixLabs.Security.Cryptography/Hmac.Compute.Sha1Hmac.cs b/OnixLabs.Security.Cryptography/Hmac.Compute.Sha1Hmac.cs new file mode 100644 index 0000000..0f62496 --- /dev/null +++ b/OnixLabs.Security.Cryptography/Hmac.Compute.Sha1Hmac.cs @@ -0,0 +1,62 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Text; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a hashed message authentication code (HMAC). + /// + public readonly partial struct Hmac + { + /// + /// Computes a SHA-1 HMAC from the specified value and key. + /// This will use the default encoding to convert the input value and key into a byte array. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// Returns a of the input value and key. + public static Hmac ComputeSha1Hmac(string value, string key) + { + return ComputeSha1Hmac(value, key, Encoding.Default); + } + + /// + /// Computes a SHA-1 HMAC from the specified value and key. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// The encoding which will be used to convert the value and key into a byte array. + /// Returns a of the input value and key. + public static Hmac ComputeSha1Hmac(string value, string key, Encoding encoding) + { + byte[] valueBytes = encoding.GetBytes(value); + byte[] keyBytes = encoding.GetBytes(key); + + return ComputeSha1Hmac(valueBytes, keyBytes); + } + + /// + /// Computes a SHA-1 HMAC from the specified value and key. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// Returns a of the input value and key. + public static Hmac ComputeSha1Hmac(byte[] value, byte[] key) + { + return ComputeHmac(value, key, HashAlgorithmType.Sha1Hmac); + } + } +} diff --git a/OnixLabs.Security.Cryptography/Hmac.Compute.Sha2Hmac256.cs b/OnixLabs.Security.Cryptography/Hmac.Compute.Sha2Hmac256.cs new file mode 100644 index 0000000..f7d863d --- /dev/null +++ b/OnixLabs.Security.Cryptography/Hmac.Compute.Sha2Hmac256.cs @@ -0,0 +1,62 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Text; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a hashed message authentication code (HMAC). + /// + public readonly partial struct Hmac + { + /// + /// Computes a SHA-2 256-bit HMAC from the specified value and key. + /// This will use the default encoding to convert the input value and key into a byte array. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// Returns a of the input value and key. + public static Hmac ComputeSha2Hmac256(string value, string key) + { + return ComputeSha2Hmac256(value, key, Encoding.Default); + } + + /// + /// Computes a SHA-2 256-bit HMAC from the specified value and key. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// The encoding which will be used to convert the value and key into a byte array. + /// Returns a of the input value and key. + public static Hmac ComputeSha2Hmac256(string value, string key, Encoding encoding) + { + byte[] valueBytes = encoding.GetBytes(value); + byte[] keyBytes = encoding.GetBytes(key); + + return ComputeSha2Hmac256(valueBytes, keyBytes); + } + + /// + /// Computes a SHA-2 256-bit HMAC from the specified value and key. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// Returns a of the input value and key. + public static Hmac ComputeSha2Hmac256(byte[] value, byte[] key) + { + return ComputeHmac(value, key, HashAlgorithmType.Sha2Hmac256); + } + } +} diff --git a/OnixLabs.Security.Cryptography/Hmac.Compute.Sha2Hmac384.cs b/OnixLabs.Security.Cryptography/Hmac.Compute.Sha2Hmac384.cs new file mode 100644 index 0000000..ac7e34f --- /dev/null +++ b/OnixLabs.Security.Cryptography/Hmac.Compute.Sha2Hmac384.cs @@ -0,0 +1,62 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Text; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a hashed message authentication code (HMAC). + /// + public readonly partial struct Hmac + { + /// + /// Computes a SHA-2 384-bit HMAC from the specified value and key. + /// This will use the default encoding to convert the input value and key into a byte array. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// Returns a of the input value and key. + public static Hmac ComputeSha2Hmac384(string value, string key) + { + return ComputeSha2Hmac384(value, key, Encoding.Default); + } + + /// + /// Computes a SHA-2 384-bit HMAC from the specified value and key. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// The encoding which will be used to convert the value and key into a byte array. + /// Returns a of the input value and key. + public static Hmac ComputeSha2Hmac384(string value, string key, Encoding encoding) + { + byte[] valueBytes = encoding.GetBytes(value); + byte[] keyBytes = encoding.GetBytes(key); + + return ComputeSha2Hmac384(valueBytes, keyBytes); + } + + /// + /// Computes a SHA-2 384-bit HMAC from the specified value and key. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// Returns a of the input value and key. + public static Hmac ComputeSha2Hmac384(byte[] value, byte[] key) + { + return ComputeHmac(value, key, HashAlgorithmType.Sha2Hmac384); + } + } +} diff --git a/OnixLabs.Security.Cryptography/Hmac.Compute.Sha2Hmac512.cs b/OnixLabs.Security.Cryptography/Hmac.Compute.Sha2Hmac512.cs new file mode 100644 index 0000000..5c4eb3a --- /dev/null +++ b/OnixLabs.Security.Cryptography/Hmac.Compute.Sha2Hmac512.cs @@ -0,0 +1,62 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Text; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a hashed message authentication code (HMAC). + /// + public readonly partial struct Hmac + { + /// + /// Computes a SHA-2 512-bit HMAC from the specified value and key. + /// This will use the default encoding to convert the input value and key into a byte array. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// Returns a of the input value and key. + public static Hmac ComputeSha2Hmac512(string value, string key) + { + return ComputeSha2Hmac512(value, key, Encoding.Default); + } + + /// + /// Computes a SHA-2 512-bit HMAC from the specified value and key. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// The encoding which will be used to convert the value and key into a byte array. + /// Returns a of the input value and key. + public static Hmac ComputeSha2Hmac512(string value, string key, Encoding encoding) + { + byte[] valueBytes = encoding.GetBytes(value); + byte[] keyBytes = encoding.GetBytes(key); + + return ComputeSha2Hmac512(valueBytes, keyBytes); + } + + /// + /// Computes a SHA-2 512-bit HMAC from the specified value and key. + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// Returns a of the input value and key. + public static Hmac ComputeSha2Hmac512(byte[] value, byte[] key) + { + return ComputeHmac(value, key, HashAlgorithmType.Sha2Hmac512); + } + } +} diff --git a/OnixLabs.Security.Cryptography/Hmac.Compute.cs b/OnixLabs.Security.Cryptography/Hmac.Compute.cs new file mode 100644 index 0000000..da80dc7 --- /dev/null +++ b/OnixLabs.Security.Cryptography/Hmac.Compute.cs @@ -0,0 +1,70 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Security.Cryptography; +using System.Text; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a hashed message authentication code (HMAC). + /// + public readonly partial struct Hmac + { + /// + /// Computes a hashed message authentication code (HMAC). + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// The hash algorithm type of the computed HMAC. + /// Returns a representing the specified value and key. + public static Hmac ComputeHmac(string value, string key, HashAlgorithmType type) + { + return ComputeHmac(value, key, type, Encoding.Default); + } + + /// + /// Computes a hashed message authentication code (HMAC). + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// The hash algorithm type of the computed HMAC. + /// The encoding which will be used to convert the value and key into a byte array. + /// Returns a representing the specified value and key. + public static Hmac ComputeHmac(string value, string key, HashAlgorithmType type, Encoding encoding) + { + byte[] valueBytes = encoding.GetBytes(value); + byte[] keyBytes = encoding.GetBytes(key); + + return ComputeHmac(valueBytes, keyBytes, type); + } + + /// + /// Computes a hashed message authentication code (HMAC). + /// + /// The value for which to compute a HMAC. + /// The key for which to compute a HMAC. + /// The hash algorithm type of the computed HMAC. + /// Returns a representing the specified value and key. + public static Hmac ComputeHmac(byte[] value, byte[] key, HashAlgorithmType type) + { + using KeyedHashAlgorithm algorithm = type.GetKeyedHashAlgorithm(key); + + byte[] data = algorithm.ComputeHash(value); + Hash hash = Hash.FromByteArray(data, type); + + return Create(hash, value); + } + } +} diff --git a/OnixLabs.Security.Cryptography/Hmac.Create.cs b/OnixLabs.Security.Cryptography/Hmac.Create.cs new file mode 100644 index 0000000..395b682 --- /dev/null +++ b/OnixLabs.Security.Cryptography/Hmac.Create.cs @@ -0,0 +1,33 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a hashed message authentication code (HMAC). + /// + public readonly partial struct Hmac + { + /// + /// Creates a hashed message authentication code (HMAC). + /// + /// The representing the HMAC. + /// The underlying un-hashed data. + /// Returns a new instance. + public static Hmac Create(Hash hash, byte[] data) + { + return new Hmac(hash, data); + } + } +} diff --git a/OnixLabs.Security.Cryptography/Hmac.Equatable.cs b/OnixLabs.Security.Cryptography/Hmac.Equatable.cs new file mode 100644 index 0000000..72608ec --- /dev/null +++ b/OnixLabs.Security.Cryptography/Hmac.Equatable.cs @@ -0,0 +1,77 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Linq; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a hashed message authentication code (HMAC). + /// + public readonly partial struct Hmac : IEquatable + { + /// + /// Performs an equality check between two object instances. + /// + /// Instance a. + /// Instance b. + /// True if the instances are equal; otherwise, false. + public static bool operator ==(Hmac a, Hmac b) + { + return Equals(a, b); + } + + /// + /// Performs an inequality check between two object instances. + /// + /// Instance a. + /// Instance b. + /// True if the instances are not equal; otherwise, false. + public static bool operator !=(Hmac a, Hmac b) + { + return !Equals(a, b); + } + + /// + /// Checks for equality between this instance and another object. + /// + /// The object to check for equality. + /// true if the object is equal to this instance; otherwise, false. + public bool Equals(Hmac other) + { + return other.Hash == Hash + && other.Data.SequenceEqual(Data); + } + + /// + /// Checks for equality between this instance and another object. + /// + /// The object to check for equality. + /// true if the object is equal to this instance; otherwise, false. + public override bool Equals(object? obj) + { + return obj is Hmac other && Equals(other); + } + + /// + /// Serves as a hash code function for this instance. + /// + /// A hash code for this instance. + public override int GetHashCode() + { + return HashCode.Combine(Hash, Data); + } + } +} diff --git a/OnixLabs.Security.Cryptography/Hmac.Parse.cs b/OnixLabs.Security.Cryptography/Hmac.Parse.cs new file mode 100644 index 0000000..e85dc1a --- /dev/null +++ b/OnixLabs.Security.Cryptography/Hmac.Parse.cs @@ -0,0 +1,63 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using OnixLabs.Core; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a hashed message authentication code (HMAC). + /// + public readonly partial struct Hmac + { + /// + /// Parses a hexadecimal representation of a HMAC into a instance. + /// + /// A that contains a HMAC to convert. + /// The hash algorithm type of the HMAC. + /// A new instance. + public static Hmac Parse(string value, HashAlgorithmType? type = null) + { + string hashComponent = value.SubstringBeforeLast(':'); + string dataComponent = value.SubstringAfterLast(':'); + + Hash hash = Hash.Parse(hashComponent, type); + byte[] data = Convert.FromHexString(dataComponent); + + return Create(hash, data); + } + + /// + /// Attempts to parse a hexadecimal representation of a HMAC into a instance. + /// + /// A that contains a HMAC to convert. + /// The hash algorithm type of the hash. + /// The result if conversion was successful. + /// Returns true if the hash conversion was successful; otherwise, false. + public static bool TryParse(string value, HashAlgorithmType? type, out Hmac hmac) + { + try + { + hmac = Parse(value, type); + return true; + } + catch + { + hmac = default; + return false; + } + } + } +} diff --git a/OnixLabs.Security.Cryptography/Hmac.To.cs b/OnixLabs.Security.Cryptography/Hmac.To.cs new file mode 100644 index 0000000..de0eb00 --- /dev/null +++ b/OnixLabs.Security.Cryptography/Hmac.To.cs @@ -0,0 +1,42 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a hashed message authentication code (HMAC). + /// + public readonly partial struct Hmac + { + /// + /// Returns a that represents the current object. + /// + /// A that represents the current object. + public override string ToString() + { + return $"{Hash}:{Convert.ToHexString(Data).ToLower()}"; + } + + /// + /// Returns a that represents the current object, including the hash algorithm type. + /// + /// A that represents the current object, including the hash algorithm type. + public string ToStringWithAlgorithmType() + { + return $"{Hash.AlgorithmType.Name}:{ToString()}"; + } + } +} diff --git a/OnixLabs.Security.Cryptography/Hmac.Verify.cs b/OnixLabs.Security.Cryptography/Hmac.Verify.cs new file mode 100644 index 0000000..36e7d55 --- /dev/null +++ b/OnixLabs.Security.Cryptography/Hmac.Verify.cs @@ -0,0 +1,89 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Security.Cryptography; +using System.Text; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a hashed message authentication code (HMAC). + /// + public readonly partial struct Hmac + { + /// + /// Determines whether the hashed message authentication code (HMAC) was created with the specified key. + /// + /// The key to validate against this . + /// Returns true if this was created with the specified key; otherwise, false. + public bool IsValid(string key) + { + return IsValid(key, Encoding.Default); + } + + /// + /// Determines whether the hashed message authentication code (HMAC) was created with the specified key. + /// + /// The key to validate against this . + /// The encoding which will be used to convert the key into a byte array. + /// Returns true if this was created with the specified key; otherwise, false. + public bool IsValid(string key, Encoding encoding) + { + byte[] keyBytes = encoding.GetBytes(key); + return IsValid(keyBytes); + } + + /// + /// Determines whether the hashed message authentication code (HMAC) was created with the specified key. + /// + /// The key to validate against this . + /// Returns true if this was created with the specified key; otherwise, false. + public bool IsValid(byte[] key) + { + return this == ComputeHmac(Data, key, Hash.AlgorithmType); + } + + /// + /// Verifies that the hashed message authentication code (HMAC) was created with the specified key. + /// + /// The key to validate against this . + public void Verify(string key) + { + Verify(key, Encoding.Default); + } + + /// + /// Verifies that the hashed message authentication code (HMAC) was created with the specified key. + /// + /// The encoding which will be used to convert the key into a byte array. + /// The key to validate against this . + public void Verify(string key, Encoding encoding) + { + byte[] keyBytes = encoding.GetBytes(key); + Verify(keyBytes); + } + + /// + /// Verifies that the hashed message authentication code (HMAC) was created with the specified key. + /// + /// The key to validate against this . + public void Verify(byte[] key) + { + if (!IsValid(key)) + { + throw new CryptographicException("The HMAC was could not be verified with the specified key."); + } + } + } +} diff --git a/OnixLabs.Security.Cryptography/Hmac.cs b/OnixLabs.Security.Cryptography/Hmac.cs new file mode 100644 index 0000000..aa862e8 --- /dev/null +++ b/OnixLabs.Security.Cryptography/Hmac.cs @@ -0,0 +1,50 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using OnixLabs.Core; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a hashed message authentication code (HMAC). + /// + public readonly partial struct Hmac + { + /// + /// Represents the underlying un-hashed data. + /// + private readonly byte[] data; + + /// + /// Initializes a new instance of the struct. + /// + /// The representing the HMAC. + /// The underlying un-hashed data. + private Hmac(Hash hash, byte[] data) + { + Hash = hash; + this.data = data; + } + + /// + /// Gets the representing the HMAC. + /// + public Hash Hash { get; } + + /// + /// Gets the underlying un-hashed data. + /// + public byte[] Data => data.Copy(); + } +} diff --git a/OnixLabs.Security.Cryptography/KeyPair.Create.cs b/OnixLabs.Security.Cryptography/KeyPair.Create.cs new file mode 100644 index 0000000..e21914a --- /dev/null +++ b/OnixLabs.Security.Cryptography/KeyPair.Create.cs @@ -0,0 +1,124 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Security.Cryptography; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a cryptographic public/private key pair. + /// + public sealed partial class KeyPair + { + /// + /// Creates an ECDSA public/private key pair. + /// + /// The for computing signature data. + /// Returns an ECDSA public/private key pair. + public static KeyPair CreateEcdsaKeyPair(HashAlgorithmType type) + { + using ECDsa asymmetricAlgorithm = ECDsa.Create(); + + PrivateKey privateKey = new EcdsaPrivateKey(asymmetricAlgorithm.ExportECPrivateKey(), type); + PublicKey publicKey = new EcdsaPublicKey(asymmetricAlgorithm.ExportSubjectPublicKeyInfo(), type); + + return new KeyPair(privateKey, publicKey); + } + + /// + /// Creates an ECDSA public/private key pair. + /// + /// The for computing signature data. + /// The curve to use for key generation. + /// Returns an ECDSA public/private key pair. + public static KeyPair CreateEcdsaKeyPair(HashAlgorithmType type, ECCurve curve) + { + using ECDsa asymmetricAlgorithm = ECDsa.Create(curve); + + PrivateKey privateKey = new EcdsaPrivateKey(asymmetricAlgorithm.ExportECPrivateKey(), type); + PublicKey publicKey = new EcdsaPublicKey(asymmetricAlgorithm.ExportSubjectPublicKeyInfo(), type); + + return new KeyPair(privateKey, publicKey); + } + + /// + /// Creates an ECDSA public/private key pair. + /// + /// The for computing signature data. + /// The parameters representing the key to use. + /// Returns an ECDSA public/private key pair. + public static KeyPair CreateEcdsaKeyPair(HashAlgorithmType type, ECParameters parameters) + { + using ECDsa asymmetricAlgorithm = ECDsa.Create(parameters); + + PrivateKey privateKey = new EcdsaPrivateKey(asymmetricAlgorithm.ExportECPrivateKey(), type); + PublicKey publicKey = new EcdsaPublicKey(asymmetricAlgorithm.ExportSubjectPublicKeyInfo(), type); + + return new KeyPair(privateKey, publicKey); + } + + /// + /// Creates an RSA public/private key pair. + /// + /// The for computing signature data. + /// The for computing signature data. + /// Returns an ECDSA public/private key pair. + public static KeyPair CreateRsaKeyPair(HashAlgorithmType type, RSASignaturePadding padding) + { + using RSA asymmetricAlgorithm = RSA.Create(); + + PrivateKey privateKey = new RsaPrivateKey(asymmetricAlgorithm.ExportRSAPrivateKey(), type, padding); + PublicKey publicKey = new RsaPublicKey(asymmetricAlgorithm.ExportRSAPublicKey(), type, padding); + + return new KeyPair(privateKey, publicKey); + } + + /// + /// Creates an RSA public/private key pair. + /// + /// The for computing signature data. + /// The for computing signature data. + /// The key size, in bits. + /// Returns an ECDSA public/private key pair. + public static KeyPair CreateRsaKeyPair(HashAlgorithmType type, RSASignaturePadding padding, int keySizeInBits) + { + using RSA asymmetricAlgorithm = RSA.Create(keySizeInBits); + + PrivateKey privateKey = new RsaPrivateKey(asymmetricAlgorithm.ExportRSAPrivateKey(), type, padding); + PublicKey publicKey = new RsaPublicKey(asymmetricAlgorithm.ExportRSAPublicKey(), type, padding); + + return new KeyPair(privateKey, publicKey); + } + + /// + /// Creates an RSA public/private key pair. + /// + /// The for computing signature data. + /// The for computing signature data. + /// The parameters for the RSA algorithm. + /// Returns an ECDSA public/private key pair. + public static KeyPair CreateRsaKeyPair( + HashAlgorithmType type, + RSASignaturePadding padding, + RSAParameters parameters) + { + using RSA asymmetricAlgorithm = RSA.Create(parameters); + + PrivateKey privateKey = new RsaPrivateKey(asymmetricAlgorithm.ExportRSAPrivateKey(), type, padding); + PublicKey publicKey = new RsaPublicKey(asymmetricAlgorithm.ExportRSAPublicKey(), type, padding); + + return new KeyPair(privateKey, publicKey); + } + } +} diff --git a/OnixLabs.Security.Cryptography/KeyPair.Equatable.cs b/OnixLabs.Security.Cryptography/KeyPair.Equatable.cs new file mode 100644 index 0000000..840af72 --- /dev/null +++ b/OnixLabs.Security.Cryptography/KeyPair.Equatable.cs @@ -0,0 +1,78 @@ +// Copyright 2020-2021 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace OnixLabs.Security.Cryptography +{ + /// + /// Represents a cryptographic public/private key pair. + /// + public sealed partial class KeyPair : IEquatable + { + /// + /// Performs an equality check between two object instances. + /// + /// Instance a. + /// Instance b. + /// True if the instances are equal; otherwise, false. + public static bool operator ==(KeyPair a, KeyPair b) + { + return Equals(a, b); + } + + /// + /// Performs an inequality check between two object instances. + /// + /// Instance a. + /// Instance b. + /// True if the instances are not equal; otherwise, false. + public static bool operator !=(KeyPair a, KeyPair b) + { + return !Equals(a, b); + } + + /// + /// Checks for equality between this instance and another object. + /// + /// The object to check for equality. + /// true if the object is equal to this instance; otherwise, false. + public bool Equals(KeyPair? other) + { + return ReferenceEquals(this, other) + || other is not null + && other.PrivateKey == PrivateKey + && other.PublicKey == PublicKey; + } + + /// + /// Checks for equality between this instance and another object. + /// + /// The object to check for equality. + /// true if the object is equal to this instance; otherwise, false. + public override bool Equals(object? obj) + { + return Equals(obj as KeyPair); + } + + /// + /// Serves as a hash code function for this instance. + /// + /// A hash code for this instance. + public override int GetHashCode() + { + return HashCode.Combine(PrivateKey, PublicKey); + } + } +} diff --git a/OnixLabs.Security.Cryptography/KeyPair.cs b/OnixLabs.Security.Cryptography/KeyPair.cs index f1b4ca4..26b02bc 100644 --- a/OnixLabs.Security.Cryptography/KeyPair.cs +++ b/OnixLabs.Security.Cryptography/KeyPair.cs @@ -12,15 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using System.Security.Cryptography; - namespace OnixLabs.Security.Cryptography { /// /// Represents a cryptographic public/private key pair. /// - public sealed class KeyPair : IEquatable + public sealed partial class KeyPair { /// /// Prevents new instances of the class from being created. @@ -42,168 +39,5 @@ private KeyPair(PrivateKey privateKey, PublicKey publicKey) /// Gets the public key component of this key pair. /// public PublicKey PublicKey { get; } - - /// - /// Performs an equality check between two object instances. - /// - /// Instance a. - /// Instance b. - /// True if the instances are equal; otherwise, false. - public static bool operator ==(KeyPair a, KeyPair b) - { - return Equals(a, b); - } - - /// - /// Performs an inequality check between two object instances. - /// - /// Instance a. - /// Instance b. - /// True if the instances are not equal; otherwise, false. - public static bool operator !=(KeyPair a, KeyPair b) - { - return !Equals(a, b); - } - - /// - /// Creates an ECDSA public/private key pair. - /// - /// The for computing signature data. - /// Returns an ECDSA public/private key pair. - public static KeyPair CreateEcdsaKeyPair(HashAlgorithmType type) - { - using ECDsa asymmetricAlgorithm = ECDsa.Create(); - - PrivateKey privateKey = new EcdsaPrivateKey(asymmetricAlgorithm.ExportECPrivateKey(), type); - PublicKey publicKey = new EcdsaPublicKey(asymmetricAlgorithm.ExportSubjectPublicKeyInfo(), type); - - return new KeyPair(privateKey, publicKey); - } - - /// - /// Creates an ECDSA public/private key pair. - /// - /// The for computing signature data. - /// The curve to use for key generation. - /// Returns an ECDSA public/private key pair. - public static KeyPair CreateEcdsaKeyPair(HashAlgorithmType type, ECCurve curve) - { - using ECDsa asymmetricAlgorithm = ECDsa.Create(curve); - - PrivateKey privateKey = new EcdsaPrivateKey(asymmetricAlgorithm.ExportECPrivateKey(), type); - PublicKey publicKey = new EcdsaPublicKey(asymmetricAlgorithm.ExportSubjectPublicKeyInfo(), type); - - return new KeyPair(privateKey, publicKey); - } - - /// - /// Creates an ECDSA public/private key pair. - /// - /// The for computing signature data. - /// The parameters representing the key to use. - /// Returns an ECDSA public/private key pair. - public static KeyPair CreateEcdsaKeyPair(HashAlgorithmType type, ECParameters parameters) - { - using ECDsa asymmetricAlgorithm = ECDsa.Create(parameters); - - PrivateKey privateKey = new EcdsaPrivateKey(asymmetricAlgorithm.ExportECPrivateKey(), type); - PublicKey publicKey = new EcdsaPublicKey(asymmetricAlgorithm.ExportSubjectPublicKeyInfo(), type); - - return new KeyPair(privateKey, publicKey); - } - - /// - /// Creates an RSA public/private key pair. - /// - /// The for computing signature data. - /// The for computing signature data. - /// Returns an ECDSA public/private key pair. - public static KeyPair CreateRsaKeyPair(HashAlgorithmType type, RSASignaturePadding padding) - { - using RSA asymmetricAlgorithm = RSA.Create(); - - PrivateKey privateKey = new RsaPrivateKey(asymmetricAlgorithm.ExportRSAPrivateKey(), type, padding); - PublicKey publicKey = new RsaPublicKey(asymmetricAlgorithm.ExportRSAPublicKey(), type, padding); - - return new KeyPair(privateKey, publicKey); - } - - /// - /// Creates an RSA public/private key pair. - /// - /// The for computing signature data. - /// The for computing signature data. - /// The key size, in bits. - /// Returns an ECDSA public/private key pair. - public static KeyPair CreateRsaKeyPair(HashAlgorithmType type, RSASignaturePadding padding, int keySizeInBits) - { - using RSA asymmetricAlgorithm = RSA.Create(keySizeInBits); - - PrivateKey privateKey = new RsaPrivateKey(asymmetricAlgorithm.ExportRSAPrivateKey(), type, padding); - PublicKey publicKey = new RsaPublicKey(asymmetricAlgorithm.ExportRSAPublicKey(), type, padding); - - return new KeyPair(privateKey, publicKey); - } - - /// - /// Creates an RSA public/private key pair. - /// - /// The for computing signature data. - /// The for computing signature data. - /// The parameters for the RSA algorithm. - /// Returns an ECDSA public/private key pair. - public static KeyPair CreateRsaKeyPair( - HashAlgorithmType type, - RSASignaturePadding padding, - RSAParameters parameters) - { - using RSA asymmetricAlgorithm = RSA.Create(parameters); - - PrivateKey privateKey = new RsaPrivateKey(asymmetricAlgorithm.ExportRSAPrivateKey(), type, padding); - PublicKey publicKey = new RsaPublicKey(asymmetricAlgorithm.ExportRSAPublicKey(), type, padding); - - return new KeyPair(privateKey, publicKey); - } - - /// - /// Checks for equality between this instance and another object. - /// - /// The object to check for equality. - /// true if the object is equal to this instance; otherwise, false. - public bool Equals(KeyPair? other) - { - return ReferenceEquals(this, other) - || other is not null - && other.PrivateKey == PrivateKey - && other.PublicKey == PublicKey; - } - - /// - /// Checks for equality between this instance and another object. - /// - /// The object to check for equality. - /// true if the object is equal to this instance; otherwise, false. - public override bool Equals(object? obj) - { - return Equals(obj as KeyPair); - } - - /// - /// Serves as a hash code function for this instance. - /// - /// A hash code for this instance. - public override int GetHashCode() - { - return HashCode.Combine(PrivateKey, PublicKey); - } - - /// - /// Returns a that represents the current object. - /// - /// A that represents the current object. - public override string ToString() - { - return $"{PrivateKey}:{PublicKey}"; - } } } diff --git a/OnixLabs.Security.Cryptography/MerkleTree.cs b/OnixLabs.Security.Cryptography/MerkleTree.cs index df646e8..63a4bac 100644 --- a/OnixLabs.Security.Cryptography/MerkleTree.cs +++ b/OnixLabs.Security.Cryptography/MerkleTree.cs @@ -81,7 +81,7 @@ private static void CheckIfMerkleTreesAreEmpty(IEnumerable merkleTre /// /// The to check. /// if the elements of the do not have the same hash algorithm type. - private static void CheckNodesHaveEqualHashAlgorithms(IReadOnlyList merkleTrees) + private static void CheckNodesHaveEqualHashAlgorithms(IEnumerable merkleTrees) { if (!merkleTrees.AllEqualBy(merkleTree => merkleTree.Hash.AlgorithmType)) { diff --git a/OnixLabs.Security.Cryptography/OnixLabs.Security.Cryptography.csproj b/OnixLabs.Security.Cryptography/OnixLabs.Security.Cryptography.csproj index 2764246..a6072a9 100644 --- a/OnixLabs.Security.Cryptography/OnixLabs.Security.Cryptography.csproj +++ b/OnixLabs.Security.Cryptography/OnixLabs.Security.Cryptography.csproj @@ -5,12 +5,22 @@ OnixLabs.Security.Cryptography ONIXLabs ONIXLabs Cryptography API for .NET - 1.0.0 + 2.0.0 en enable true Copyright © ONIXLabs 2020-2021 https://github.com/onix-labs/onixlabs-dotnet + 2.0.0 + + + + bin\Debug\net5.0\OnixLabs.Security.Cryptography.xml + + + + bin\Release\net5.0\OnixLabs.Security.Cryptography.xml + true