#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) #pragma warning disable using System; using System.Collections.Generic; using Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities; using Best.HTTP.SecureProtocol.Org.BouncyCastle.Utilities; namespace Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Prng.Drbg { /** * A SP800-90A Hash DRBG. */ public sealed class HashSP800Drbg : ISP80090Drbg { private readonly static byte[] ONE = { 0x01 }; private readonly static long RESEED_MAX = 1L << (48 - 1); private readonly static int MAX_BITS_REQUEST = 1 << (19 - 1); private static readonly IDictionary SeedLens = new Dictionary(StringComparer.OrdinalIgnoreCase); static HashSP800Drbg() { SeedLens.Add("SHA-1", 440); SeedLens.Add("SHA-224", 440); SeedLens.Add("SHA-256", 440); SeedLens.Add("SHA-512/256", 440); SeedLens.Add("SHA-512/224", 440); SeedLens.Add("SHA-384", 888); SeedLens.Add("SHA-512", 888); } private readonly IDigest mDigest; private readonly IEntropySource mEntropySource; private readonly int mSecurityStrength; private readonly int mSeedLength; private byte[] mV; private byte[] mC; private long mReseedCounter; /** * Construct a SP800-90A Hash DRBG. *

* Minimum entropy requirement is the security strength requested. *

* @param digest source digest to use for DRB stream. * @param securityStrength security strength required (in bits) * @param entropySource source of entropy to use for seeding/reseeding. * @param personalizationString personalization string to distinguish this DRBG (may be null). * @param nonce nonce to further distinguish this DRBG (may be null). */ public HashSP800Drbg(IDigest digest, int securityStrength, IEntropySource entropySource, byte[] personalizationString, byte[] nonce) { if (securityStrength > DrbgUtilities.GetMaxSecurityStrength(digest)) throw new ArgumentException("Requested security strength is not supported by the derivation function"); if (entropySource.EntropySize < securityStrength) throw new ArgumentException("Not enough entropy for security strength required"); mDigest = digest; mEntropySource = entropySource; mSecurityStrength = securityStrength; mSeedLength = SeedLens[digest.AlgorithmName]; // 1. seed_material = entropy_input || nonce || personalization_string. // 2. seed = Hash_df (seed_material, seedlen). // 3. V = seed. // 4. C = Hash_df ((0x00 || V), seedlen). Comment: Preceed V with a byte // of zeros. // 5. reseed_counter = 1. // 6. Return V, C, and reseed_counter as the initial_working_state byte[] entropy = GetEntropy(); byte[] seedMaterial = Arrays.ConcatenateAll(entropy, nonce, personalizationString); mV = new byte[(mSeedLength + 7) / 8]; DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength, mV); byte[] subV = new byte[mV.Length + 1]; Array.Copy(mV, 0, subV, 1, mV.Length); mC = new byte[(mSeedLength + 7) / 8]; DrbgUtilities.HashDF(mDigest, subV, mSeedLength, mC); mReseedCounter = 1; } /** * Return the block size (in bits) of the DRBG. * * @return the number of bits produced on each internal round of the DRBG. */ public int BlockSize { get { return mDigest.GetDigestSize() * 8; } } /** * Populate a passed in array with random data. * * @param output output array for generated bits. * @param additionalInput additional input to be added to the DRBG in this step. * @param predictionResistant true if a reseed should be forced, false otherwise. * * @return number of bits generated, -1 if a reseed required. */ public int Generate(byte[] output, int outputOff, int outputLen, byte[] additionalInput, bool predictionResistant) { #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER return additionalInput == null ? Generate(output.AsSpan(outputOff, outputLen), predictionResistant) : GenerateWithInput(output.AsSpan(outputOff, outputLen), additionalInput.AsSpan(), predictionResistant); #else // 1. If reseed_counter > reseed_interval, then return an indication that a // reseed is required. // 2. If (additional_input != Null), then do // 2.1 w = Hash (0x02 || V || additional_input). // 2.2 V = (V + w) mod 2^seedlen // . // 3. (returned_bits) = Hashgen (requested_number_of_bits, V). // 4. H = Hash (0x03 || V). // 5. V = (V + H + C + reseed_counter) mod 2^seedlen // . // 6. reseed_counter = reseed_counter + 1. // 7. Return SUCCESS, returned_bits, and the new values of V, C, and // reseed_counter for the new_working_state. int numberOfBits = outputLen * 8; if (numberOfBits > MAX_BITS_REQUEST) throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output"); if (mReseedCounter > RESEED_MAX) return -1; if (predictionResistant) { Reseed(additionalInput); additionalInput = null; } // 2. if (additionalInput != null) { byte[] newInput = new byte[1 + mV.Length + additionalInput.Length]; newInput[0] = 0x02; Array.Copy(mV, 0, newInput, 1, mV.Length); Array.Copy(additionalInput, 0, newInput, 1 + mV.Length, additionalInput.Length); byte[] w = Hash(newInput); AddTo(mV, w); } // 3. byte[] rv = Hashgen(mV, outputLen); // 4. byte[] subH = new byte[mV.Length + 1]; Array.Copy(mV, 0, subH, 1, mV.Length); subH[0] = 0x03; byte[] H = Hash(subH); // 5. AddTo(mV, H); AddTo(mV, mC); byte[] c = new byte[4]; Pack.UInt32_To_BE((uint)mReseedCounter, c); AddTo(mV, c); mReseedCounter++; Array.Copy(rv, 0, output, outputOff, outputLen); return numberOfBits; #endif } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER public int Generate(Span output, bool predictionResistant) { // 1. If reseed_counter > reseed_interval, then return an indication that a // reseed is required. // 2. If (additional_input != Null), then do // 2.1 w = Hash (0x02 || V || additional_input). // 2.2 V = (V + w) mod 2^seedlen // . // 3. (returned_bits) = Hashgen (requested_number_of_bits, V). // 4. H = Hash (0x03 || V). // 5. V = (V + H + C + reseed_counter) mod 2^seedlen // . // 6. reseed_counter = reseed_counter + 1. // 7. Return SUCCESS, returned_bits, and the new values of V, C, and // reseed_counter for the new_working_state. int numberOfBits = output.Length * 8; if (numberOfBits > MAX_BITS_REQUEST) throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output"); if (mReseedCounter > RESEED_MAX) return -1; if (predictionResistant) { Reseed(ReadOnlySpan.Empty); } return ImplGenerate(output); } public int GenerateWithInput(Span output, ReadOnlySpan additionalInput, bool predictionResistant) { // 1. If reseed_counter > reseed_interval, then return an indication that a // reseed is required. // 2. If (additional_input != Null), then do // 2.1 w = Hash (0x02 || V || additional_input). // 2.2 V = (V + w) mod 2^seedlen // . // 3. (returned_bits) = Hashgen (requested_number_of_bits, V). // 4. H = Hash (0x03 || V). // 5. V = (V + H + C + reseed_counter) mod 2^seedlen // . // 6. reseed_counter = reseed_counter + 1. // 7. Return SUCCESS, returned_bits, and the new values of V, C, and // reseed_counter for the new_working_state. int numberOfBits = output.Length * 8; if (numberOfBits > MAX_BITS_REQUEST) throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output"); if (mReseedCounter > RESEED_MAX) return -1; if (predictionResistant) { Reseed(additionalInput); } else { // 2. mDigest.Update(0x02); mDigest.BlockUpdate(mV); mDigest.BlockUpdate(additionalInput); int digestSize = mDigest.GetDigestSize(); Span w = digestSize <= 128 ? stackalloc byte[digestSize] : new byte[digestSize]; mDigest.DoFinal(w); AddTo(mV, w); } return ImplGenerate(output); } private int ImplGenerate(Span output) { // 3. Hashgen(mV, output); // 4. mDigest.Update(0x03); mDigest.BlockUpdate(mV); int digestSize = mDigest.GetDigestSize(); Span H = digestSize <= 128 ? stackalloc byte[digestSize] : new byte[digestSize]; mDigest.DoFinal(H); // 5. AddTo(mV, H); AddTo(mV, mC); Span c = stackalloc byte[4]; Pack.UInt32_To_BE((uint)mReseedCounter, c); AddTo(mV, c); mReseedCounter++; return output.Length * 8; } #endif private byte[] GetEntropy() { byte[] entropy = mEntropySource.GetEntropy(); if (entropy.Length < (mSecurityStrength + 7) / 8) throw new InvalidOperationException("Insufficient entropy provided by entropy source"); return entropy; } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER private int GetEntropy(Span output) { int length = mEntropySource.GetEntropy(output); if (length < (mSecurityStrength + 7) / 8) throw new InvalidOperationException("Insufficient entropy provided by entropy source"); return length; } private int GetEntropyLength() { return (mEntropySource.EntropySize + 7) / 8; } #endif // this will always add the shorter length byte array mathematically to the // longer length byte array. // be careful.... #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER private void AddTo(Span longer, ReadOnlySpan shorter) #else private void AddTo(byte[] longer, byte[] shorter) #endif { int off = longer.Length - shorter.Length; uint carry = 0; int i = shorter.Length; while (--i >= 0) { carry += (uint)longer[off + i] + shorter[i]; longer[off + i] = (byte)carry; carry >>= 8; } i = off; while (--i >= 0) { carry += longer[i]; longer[i] = (byte)carry; carry >>= 8; } } /** * Reseed the DRBG. * * @param additionalInput additional input to be added to the DRBG in this step. */ public void Reseed(byte[] additionalInput) { #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER Reseed(Spans.FromNullableReadOnly(additionalInput)); #else // 1. seed_material = 0x01 || V || entropy_input || additional_input. // // 2. seed = Hash_df (seed_material, seedlen). // // 3. V = seed. // // 4. C = Hash_df ((0x00 || V), seedlen). // // 5. reseed_counter = 1. // // 6. Return V, C, and reseed_counter for the new_working_state. // // Comment: Precede with a byte of all zeros. byte[] entropy = GetEntropy(); byte[] seedMaterial = Arrays.ConcatenateAll(ONE, mV, entropy, additionalInput); DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength, mV); byte[] subV = new byte[mV.Length + 1]; subV[0] = 0x00; Array.Copy(mV, 0, subV, 1, mV.Length); DrbgUtilities.HashDF(mDigest, subV, mSeedLength, mC); mReseedCounter = 1; #endif } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER public void Reseed(ReadOnlySpan additionalInput) { // 1. seed_material = 0x01 || V || entropy_input || additional_input. // // 2. seed = Hash_df (seed_material, seedlen). // // 3. V = seed. // // 4. C = Hash_df ((0x00 || V), seedlen). // // 5. reseed_counter = 1. // // 6. Return V, C, and reseed_counter for the new_working_state. // // Comment: Precede with a byte of all zeros. int entropyLength = GetEntropyLength(); int seedMaterialLength = 1 + mV.Length + entropyLength + additionalInput.Length; Span seedMaterial = seedMaterialLength <= 256 ? stackalloc byte[seedMaterialLength] : new byte[seedMaterialLength]; seedMaterial[0] = 0x01; mV.CopyTo(seedMaterial[1..]); GetEntropy(seedMaterial[(1 + mV.Length)..]); additionalInput.CopyTo(seedMaterial[(1 + mV.Length + entropyLength)..]); DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength, mV); int subVLength = 1 + mV.Length; Span subV = subVLength <= 128 ? stackalloc byte[subVLength] : new byte[subVLength]; subV[0] = 0x00; mV.CopyTo(subV[1..]); DrbgUtilities.HashDF(mDigest, subV, mSeedLength, mC); mReseedCounter = 1; } #endif #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER private void DoHash(ReadOnlySpan input, Span output) { mDigest.BlockUpdate(input); mDigest.DoFinal(output); } #else private void DoHash(byte[] input, byte[] output) { mDigest.BlockUpdate(input, 0, input.Length); mDigest.DoFinal(output, 0); } private byte[] Hash(byte[] input) { byte[] hash = new byte[mDigest.GetDigestSize()]; DoHash(input, hash); return hash; } #endif // 1. m = [requested_number_of_bits / outlen] // 2. data = V. // 3. W = the Null string. // 4. For i = 1 to m // 4.1 wi = Hash (data). // 4.2 W = W || wi. // 4.3 data = (data + 1) mod 2^seedlen // . // 5. returned_bits = Leftmost (requested_no_of_bits) bits of W. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER private void Hashgen(ReadOnlySpan input, Span output) { int digestSize = mDigest.GetDigestSize(); int m = output.Length / digestSize; int dataSize = input.Length; Span data = dataSize <= 256 ? stackalloc byte[input.Length] : new byte[input.Length]; input.CopyTo(data); Span dig = digestSize <= 128 ? stackalloc byte[digestSize] : new byte[digestSize]; for (int i = 0; i <= m; i++) { DoHash(data, dig); int bytesToCopy = System.Math.Min(digestSize, output.Length - i * digestSize); dig[..bytesToCopy].CopyTo(output[(i * digestSize)..]); AddTo(data, ONE); } } #else private byte[] Hashgen(byte[] input, int length) { int digestSize = mDigest.GetDigestSize(); int m = length / digestSize; byte[] data = (byte[])input.Clone(); byte[] W = new byte[length]; byte[] dig = new byte[digestSize]; for (int i = 0; i <= m; i++) { DoHash(data, dig); int bytesToCopy = System.Math.Min(digestSize, length - i * digestSize); Array.Copy(dig, 0, W, i * digestSize, bytesToCopy); AddTo(data, ONE); } return W; } #endif } } #pragma warning restore #endif