private static void CalculateHash_ValidateDeterminism(HashingAlgorithmSpecification algorithm, SaltingMode saltingMode, Boolean shouldBeDeterministic) { using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. var target = new HashingStringProcessor(randomnessProvider); var plaintextObject = "䆟`ಮ䷆ʘ‣⦸⏹ⰄͶa✰ṁ亡Zᨖ0༂⽔9㗰"; // Act. var firstHashValue = target.CalculateHash(plaintextObject, algorithm, saltingMode); // Assert. firstHashValue.Should().NotBeNullOrEmpty(); firstHashValue.Count(value => value == 0x00).Should().NotBe(firstHashValue.Length); // Act. var secondHashValue = target.CalculateHash(plaintextObject, algorithm, saltingMode); // Assert. secondHashValue.Should().NotBeNullOrEmpty(); secondHashValue.Length.Should().Be(firstHashValue.Length); secondHashValue.Count(value => value == 0x00).Should().NotBe(secondHashValue.Length); if (shouldBeDeterministic) { // Assert. firstHashValue.ComputeThirtyTwoBitHash().Should().Be(secondHashValue.ComputeThirtyTwoBitHash()); } else { // Assert. firstHashValue.ComputeThirtyTwoBitHash().Should().NotBe(secondHashValue.ComputeThirtyTwoBitHash()); } } }
internal static Int32 ToDigestBitLength(this HashingAlgorithmSpecification target) { switch (target) { case HashingAlgorithmSpecification.Unspecified: return(default(Int32)); case HashingAlgorithmSpecification.Md5: return(128); case HashingAlgorithmSpecification.ShaTwo256: return(256); case HashingAlgorithmSpecification.ShaTwo384: return(384); case HashingAlgorithmSpecification.ShaTwo512: return(512); default: throw new ArgumentException($"{target} is not a supported {nameof(HashingAlgorithmSpecification)}.", nameof(target)); } }
internal static HashAlgorithm ToHashAlgorithm(this HashingAlgorithmSpecification target) { switch (target) { case HashingAlgorithmSpecification.Unspecified: return(null); case HashingAlgorithmSpecification.Md5: return(MD5.Create()); case HashingAlgorithmSpecification.ShaTwo256: return(SHA256.Create()); case HashingAlgorithmSpecification.ShaTwo384: return(SHA384.Create()); case HashingAlgorithmSpecification.ShaTwo512: return(SHA512.Create()); default: throw new ArgumentException($"{target} is not a supported {nameof(HashingAlgorithmSpecification)}.", nameof(target)); } }
private static void ValidateDigestLength(Byte[] hashValue, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode, Int32 saltLengthInBytes) { switch (algorithm) { case HashingAlgorithmSpecification.Md5: hashValue.Length.Should().Be(16 + (saltingMode == SaltingMode.Salted ? saltLengthInBytes : 0)); break; case HashingAlgorithmSpecification.ShaTwo256: hashValue.Length.Should().Be(32 + (saltingMode == SaltingMode.Salted ? saltLengthInBytes : 0)); break; case HashingAlgorithmSpecification.ShaTwo384: hashValue.Length.Should().Be(48 + (saltingMode == SaltingMode.Salted ? saltLengthInBytes : 0)); break; case HashingAlgorithmSpecification.ShaTwo512: hashValue.Length.Should().Be(64 + (saltingMode == SaltingMode.Salted ? saltLengthInBytes : 0)); break; default: Assert.Fail(); return; } }
/// <summary> /// Initializes a new instance of the <see cref="HashTree{T}" /> class. /// </summary> /// <param name="hashingProcessor"> /// A processor that produces hash values. /// </param> /// <param name="algorithm"> /// The algorithm used to produce hash values. The default value is <see cref="HashingAlgorithmSpecification.ShaTwo256" />. /// </param> /// <param name="blocks"> /// An ordered collection of data block objects underlying the tree. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="hashingProcessor" /> is <see langword="null" /> -or- <paramref name="blocks" /> is /// <see langword="null" />. /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// <paramref name="algorithm" /> is equal to <see cref="HashingAlgorithmSpecification.Unspecified" />. /// </exception> /// <exception cref="SecurityException"> /// An exception was raised during hashing or serialization. /// </exception> public HashTree(IHashingProcessor <TBlock> hashingProcessor, HashingAlgorithmSpecification algorithm, IEnumerable <TBlock> blocks) : base() { Algorithm = algorithm.RejectIf().IsEqualToValue(HashingAlgorithmSpecification.Unspecified, nameof(algorithm)); HashingProcessor = hashingProcessor.RejectIf().IsNull(nameof(hashingProcessor)).TargetArgument; LeafNodes = new List <HashTreeNode>(); RootNode = new HashTreeNode(); AddBlockRange(blocks); }
/// <summary> /// Calculates a hash value for the specified plaintext object and compares the result with the specified hash value. /// </summary> /// <param name="hash"> /// The hash value to evaluate. /// </param> /// <param name="plaintextObject"> /// The plaintext object to evaluate. /// </param> /// <param name="algorithm"> /// The algorithm specification used to transform the plaintext. /// </param> /// <param name="saltingMode"> /// A value specifying whether or not salt is applied to the plaintext. /// </param> /// <returns> /// <see langword="true" /> if the resulting hash value matches <paramref name="hash" />, otherwise <see langword="false" />. /// </returns> /// <exception cref="SecurityException"> /// An exception was raised during hashing or serialization. /// </exception> public Boolean EvaluateHash(Byte[] hash, T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode) { try { var digestLength = (algorithm.ToDigestBitLength() / 8); Byte[] processedHash; Byte[] calculatedHash; switch (saltingMode) { case SaltingMode.Salted: var salt = new Byte[SaltLengthInBytes]; processedHash = hash.Take(digestLength).ToArray(); salt = hash.Skip(digestLength).Take(SaltLengthInBytes).ToArray(); calculatedHash = CalculateHash(plaintextObject, algorithm, salt).Take(digestLength).ToArray(); break; default: processedHash = hash; calculatedHash = CalculateHash(plaintextObject, algorithm, null); break; } if (processedHash.Length != digestLength) { return(false); } else if (calculatedHash.Length != digestLength) { return(false); } for (var i = 0; i < digestLength; i++) { if (processedHash[i] == calculatedHash[i]) { continue; } return(false); } return(true); } catch { throw new SecurityException("The hashing operation failed."); } }
/// <summary> /// Calculates a hash value for the specified plaintext binary array. /// </summary> /// <param name="plaintextBinaryArray"> /// The plaintext binary array to hash. /// </param> /// <param name="algorithm"> /// The algorithm specification used to transform the plaintext. /// </param> /// <param name="salt"> /// The salt to apply to the plaintext, or <see langword="null" /> if the plaintext is unsalted. The default value is /// <see langword="null" />. /// </param> /// <returns> /// The resulting hash value. /// </returns> /// <exception cref="SecurityException"> /// An exception was raised during hashing or serialization. /// </exception> public Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm, Byte[] salt) { try { var applySalt = (salt is null == false); var saltLengthInBytes = (applySalt ? salt.Length : 0); var plaintextLengthInBytes = plaintextBinaryArray.Length; var plaintextBufferLengthInBytes = (plaintextLengthInBytes + saltLengthInBytes); var digestLengthInBytes = (algorithm.ToDigestBitLength() / 8); var hashLengthInBytes = (digestLengthInBytes + saltLengthInBytes); var hashValue = new Byte[hashLengthInBytes]; Byte[] plaintextBuffer; if (applySalt) { plaintextBuffer = new Byte[plaintextBufferLengthInBytes]; Array.Copy(plaintextBinaryArray, plaintextBuffer, plaintextLengthInBytes); Array.Copy(salt, 0, plaintextBuffer, plaintextLengthInBytes, saltLengthInBytes); } else { plaintextBuffer = plaintextBinaryArray; } using (var hashAlgorithm = algorithm.ToHashAlgorithm()) { Array.Copy(hashAlgorithm.ComputeHash(plaintextBinaryArray), hashValue, digestLengthInBytes); } if (applySalt) { Array.Copy(salt, 0, hashValue, digestLengthInBytes, saltLengthInBytes); } return(hashValue); } catch { throw new SecurityException("The hashing operation failed."); } }
private static void EvaluateHash_ShouldProduceDesiredResults(HashingAlgorithmSpecification algorithm, SaltingMode saltingMode) { using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. var binarySerializer = new BinaryPassThroughSerializer(); var target = new HashingProcessor <Byte[]>(randomnessProvider, binarySerializer); var plaintextObject = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; var matchingHashValue = target.CalculateHash(plaintextObject, algorithm, saltingMode); var nonMatchingHashValue = matchingHashValue.PerformCircularBitShift(Core.BitShiftDirection.Left, 16); // Act. var resultOne = target.EvaluateHash(matchingHashValue, plaintextObject, algorithm, saltingMode); var resultTwo = target.EvaluateHash(nonMatchingHashValue, plaintextObject, algorithm, saltingMode); // Assert. resultOne.Should().BeTrue(); resultTwo.Should().BeFalse(); ValidateDigestLength(matchingHashValue, algorithm, saltingMode, target.SaltLengthInBytes); } }
/// <summary> /// Calculates a hash value for the specified plaintext object. /// </summary> /// <param name="plaintextObject"> /// The plaintext object to hash. /// </param> /// <param name="algorithm"> /// The algorithm specification used to transform the plaintext. /// </param> /// <param name="saltingMode"> /// A value specifying whether or not salt is applied to the plaintext. /// </param> /// <returns> /// The resulting hash value. /// </returns> /// <exception cref="SecurityException"> /// An exception was raised during hashing or serialization. /// </exception> public Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification algorithm, SaltingMode saltingMode) { try { switch (saltingMode) { case SaltingMode.Salted: var salt = new Byte[SaltLengthInBytes]; RandomnessProvider.GetBytes(salt); return(CalculateHash(plaintextObject, algorithm, salt)); default: return(CalculateHash(plaintextObject, algorithm, null)); } } catch { throw new SecurityException("The hashing operation failed."); } }
private static void CalculateHash_ValidateDeterminism(HashingAlgorithmSpecification algorithm, SaltingMode saltingMode, Boolean shouldBeDeterministic) { using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. var binarySerializer = new BinaryPassThroughSerializer(); var target = new HashingProcessor <Byte[]>(randomnessProvider, binarySerializer); var plaintextObject = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; // Act. var firstHashValue = target.CalculateHash(plaintextObject, algorithm, saltingMode); // Assert. firstHashValue.Should().NotBeNullOrEmpty(); firstHashValue.Count(value => value == 0x00).Should().NotBe(firstHashValue.Length); ValidateDigestLength(firstHashValue, algorithm, saltingMode, target.SaltLengthInBytes); // Act. var secondHashValue = target.CalculateHash(plaintextObject, algorithm, saltingMode); // Assert. secondHashValue.Should().NotBeNullOrEmpty(); secondHashValue.Length.Should().Be(firstHashValue.Length); secondHashValue.Count(value => value == 0x00).Should().NotBe(secondHashValue.Length); if (shouldBeDeterministic) { // Assert. firstHashValue.ComputeThirtyTwoBitHash().Should().Be(secondHashValue.ComputeThirtyTwoBitHash()); } else { // Assert. firstHashValue.ComputeThirtyTwoBitHash().Should().NotBe(secondHashValue.ComputeThirtyTwoBitHash()); } } }
private static void Constructor_ShouldProduceDesiredResults(Int32 blockCount, HashingAlgorithmSpecification algorithm) { using (var randomnessProvider = RandomNumberGenerator.Create()) { // Arrange. var hashingProcessor = new HashingStringProcessor(randomnessProvider); var blocks = new String[blockCount]; randomnessProvider.FillStringArray(blocks, 1, 8, false, true, true, true, false, false, false); // Act. var target = new HashTree <String>(hashingProcessor, algorithm, blocks); var duplicate = new HashTree <String>(hashingProcessor, algorithm, blocks); // Assert. target.LeafCount.Should().Be(blockCount); target.RootNode.Should().NotBeNull(); target.RootNode.Value.Should().NotBeNull(); target.RootNode.Value.ComputeThirtyTwoBitHash().Should().Be(duplicate.RootNode.Value.ComputeThirtyTwoBitHash()); target.RootNode.ToString().Should().Be(Convert.ToBase64String(target.RootNode.Value)); ValidateDigestLength(blockCount, target.RootNode.Value, algorithm); ValidateTreeHeight(blockCount, target.RootNode.Height); } }
/// <summary> /// Initializes a new instance of the <see cref="HashTree{T}" /> class. /// </summary> /// <param name="hashingProcessor"> /// A processor that produces hash values. /// </param> /// <param name="algorithm"> /// The algorithm used to produce hash values. The default value is <see cref="HashingAlgorithmSpecification.ShaTwo256" />. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="hashingProcessor" /> is <see langword="null" />. /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// <paramref name="algorithm" /> is equal to <see cref="HashingAlgorithmSpecification.Unspecified" />. /// </exception> public HashTree(IHashingProcessor <TBlock> hashingProcessor, HashingAlgorithmSpecification algorithm) : this(hashingProcessor, algorithm, Array.Empty <TBlock>()) { return; }
private Byte[] CalculateHash(T plaintextObject, HashingAlgorithmSpecification algorithm, Byte[] salt) => CalculateHash(BinarySerializer.Serialize(plaintextObject), algorithm, salt);
/// <summary> /// Calculates a hash value for the specified plaintext binary array. /// </summary> /// <param name="plaintextBinaryArray"> /// The plaintext binary array to hash. /// </param> /// <param name="algorithm"> /// The algorithm specification used to transform the plaintext. /// </param> /// <returns> /// The resulting hash value. /// </returns> /// <exception cref="SecurityException"> /// An exception was raised during hashing or serialization. /// </exception> public Byte[] CalculateHash(Byte[] plaintextBinaryArray, HashingAlgorithmSpecification algorithm) => CalculateHash(plaintextBinaryArray, algorithm, null);
private static void CalculateHash_ShouldNotBeDeterministic_ForSaltedHashing(HashingAlgorithmSpecification algorithm) => CalculateHash_ShouldNotBeDeterministic(algorithm, SaltingMode.Salted);
private static void CalculateHash_ShouldNotBeDeterministic(HashingAlgorithmSpecification algorithm, SaltingMode saltingMode) => CalculateHash_ValidateDeterminism(algorithm, saltingMode, false);
private static void ValidateDigestLength(Int32 blockCount, Byte[] rootHashValue, HashingAlgorithmSpecification algorithm) { if (blockCount == 0) { rootHashValue.Length.Should().Be(0); return; } switch (algorithm) { case HashingAlgorithmSpecification.Md5: rootHashValue.Length.Should().Be(16); break; case HashingAlgorithmSpecification.ShaTwo256: rootHashValue.Length.Should().Be(32); break; case HashingAlgorithmSpecification.ShaTwo384: rootHashValue.Length.Should().Be(48); break; case HashingAlgorithmSpecification.ShaTwo512: rootHashValue.Length.Should().Be(64); break; default: Assert.Fail(); return; } }
private static void EvaluateHash_ShouldProduceDesiredResults_ForUnsaltedHashing(HashingAlgorithmSpecification algorithm) => EvaluateHash_ShouldProduceDesiredResults(algorithm, SaltingMode.Unsalted);