From dc05f9d7cddfd6959678ed5a9736f0add2fe63ed Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sun, 9 Oct 2022 00:10:07 +0700 Subject: Improvements to PRNG classes --- crypto/src/crypto/IEntropySource.cs | 4 + crypto/src/crypto/parameters/DesParameters.cs | 10 + .../src/crypto/prng/BasicEntropySourceProvider.cs | 9 + .../crypto/prng/CryptoApiEntropySourceProvider.cs | 9 + crypto/src/crypto/prng/SP800SecureRandom.cs | 10 +- crypto/src/crypto/prng/X931Rng.cs | 44 +- crypto/src/crypto/prng/X931SecureRandom.cs | 4 + crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs | 579 +++++++++++++++------ crypto/src/crypto/prng/drbg/DrbgUtilities.cs | 106 ++-- crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs | 164 ++++-- crypto/src/crypto/prng/drbg/HashSP800Drbg.cs | 304 ++++++++--- crypto/src/crypto/prng/drbg/ISP80090Drbg.cs | 10 +- .../crypto/prng/test/TestEntropySourceProvider.cs | 12 + 13 files changed, 906 insertions(+), 359 deletions(-) diff --git a/crypto/src/crypto/IEntropySource.cs b/crypto/src/crypto/IEntropySource.cs index 62e3bc76c..2e088ed27 100644 --- a/crypto/src/crypto/IEntropySource.cs +++ b/crypto/src/crypto/IEntropySource.cs @@ -19,6 +19,10 @@ namespace Org.BouncyCastle.Crypto /// The entropy bytes. byte[] GetEntropy(); +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + int GetEntropy(Span output); +#endif + /// /// Return the number of bits of entropy this source can produce. /// diff --git a/crypto/src/crypto/parameters/DesParameters.cs b/crypto/src/crypto/parameters/DesParameters.cs index a1f67e2b1..28881f21c 100644 --- a/crypto/src/crypto/parameters/DesParameters.cs +++ b/crypto/src/crypto/parameters/DesParameters.cs @@ -135,5 +135,15 @@ namespace Org.BouncyCastle.Crypto.Parameters bytes[off + i] = SetOddParity(bytes[off + i]); } } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public static void SetOddParity(Span bytes) + { + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = SetOddParity(bytes[i]); + } + } +#endif } } diff --git a/crypto/src/crypto/prng/BasicEntropySourceProvider.cs b/crypto/src/crypto/prng/BasicEntropySourceProvider.cs index 31a8461f0..485cf25ab 100644 --- a/crypto/src/crypto/prng/BasicEntropySourceProvider.cs +++ b/crypto/src/crypto/prng/BasicEntropySourceProvider.cs @@ -62,6 +62,15 @@ namespace Org.BouncyCastle.Crypto.Prng return SecureRandom.GetNextBytes(mSecureRandom, (mEntropySize + 7) / 8); } +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + int IEntropySource.GetEntropy(Span output) + { + int length = (mEntropySize + 7) / 8; + mSecureRandom.NextBytes(output[..length]); + return length; + } +#endif + int IEntropySource.EntropySize { get { return mEntropySize; } diff --git a/crypto/src/crypto/prng/CryptoApiEntropySourceProvider.cs b/crypto/src/crypto/prng/CryptoApiEntropySourceProvider.cs index 635af2bd9..9a2f6de2c 100644 --- a/crypto/src/crypto/prng/CryptoApiEntropySourceProvider.cs +++ b/crypto/src/crypto/prng/CryptoApiEntropySourceProvider.cs @@ -56,6 +56,15 @@ namespace Org.BouncyCastle.Crypto.Prng return result; } +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + int IEntropySource.GetEntropy(Span output) + { + int length = (mEntropySize + 7) / 8; + mRng.GetBytes(output[..length]); + return length; + } +#endif + int IEntropySource.EntropySize { get { return mEntropySize; } diff --git a/crypto/src/crypto/prng/SP800SecureRandom.cs b/crypto/src/crypto/prng/SP800SecureRandom.cs index 1409df2fa..a18576d03 100644 --- a/crypto/src/crypto/prng/SP800SecureRandom.cs +++ b/crypto/src/crypto/prng/SP800SecureRandom.cs @@ -68,6 +68,9 @@ namespace Org.BouncyCastle.Crypto.Prng public override void NextBytes(byte[] buf, int off, int len) { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + NextBytes(buf.AsSpan(off, len)); +#else lock (this) { if (mDrbg == null) @@ -82,6 +85,7 @@ namespace Org.BouncyCastle.Crypto.Prng mDrbg.Generate(buf, off, len, null, mPredictionResistant); } } +#endif } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER @@ -95,10 +99,10 @@ namespace Org.BouncyCastle.Crypto.Prng } // check if a reseed is required... - if (mDrbg.Generate(buffer, null, mPredictionResistant) < 0) + if (mDrbg.Generate(buffer, mPredictionResistant) < 0) { - mDrbg.Reseed(null); - mDrbg.Generate(buffer, null, mPredictionResistant); + mDrbg.Reseed(ReadOnlySpan.Empty); + mDrbg.Generate(buffer, mPredictionResistant); } } } diff --git a/crypto/src/crypto/prng/X931Rng.cs b/crypto/src/crypto/prng/X931Rng.cs index f0bfdc9f1..25dba89f5 100644 --- a/crypto/src/crypto/prng/X931Rng.cs +++ b/crypto/src/crypto/prng/X931Rng.cs @@ -38,16 +38,11 @@ namespace Org.BouncyCastle.Crypto.Prng this.mR = new byte[engine.GetBlockSize()]; } - /** - * Populate a passed in array with random data. - * - * @param output output array for generated bits. - * @param predictionResistant true if a reseed should be forced, false otherwise. - * - * @return number of bits generated, -1 if a reseed required. - */ - internal int Generate(byte[] output, int outputOff, int outputLen, bool predictionResistant) +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + internal int Generate(Span output, bool predictionResistant) { + int outputLen = output.Length; + if (mR.Length == 8) // 64 bit block size { if (mReseedCounter > BLOCK64_RESEED_MAX) @@ -76,24 +71,24 @@ namespace Org.BouncyCastle.Crypto.Prng for (int i = 0; i < m; i++) { - mEngine.ProcessBlock(mDT, 0, mI, 0); + mEngine.ProcessBlock(mDT, mI); Process(mR, mI, mV); Process(mV, mR, mI); - Array.Copy(mR, 0, output, outputOff + i * mR.Length, mR.Length); + mR.CopyTo(output[(i * mR.Length)..]); Increment(mDT); } - int bytesToCopy = (outputLen - m * mR.Length); + int bytesToCopy = outputLen - m * mR.Length; if (bytesToCopy > 0) { - mEngine.ProcessBlock(mDT, 0, mI, 0); + mEngine.ProcessBlock(mDT, mI); Process(mR, mI, mV); Process(mV, mR, mI); - Array.Copy(mR, 0, output, outputOff + m * mR.Length, bytesToCopy); + mR.AsSpan(0, bytesToCopy).CopyTo(output[(m * mR.Length)..]); Increment(mDT); } @@ -102,12 +97,17 @@ namespace Org.BouncyCastle.Crypto.Prng return outputLen * 8; } - -#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - internal int Generate(Span output, bool predictionResistant) +#else + /** + * Populate a passed in array with random data. + * + * @param output output array for generated bits. + * @param predictionResistant true if a reseed should be forced, false otherwise. + * + * @return number of bits generated, -1 if a reseed required. + */ + internal int Generate(byte[] output, int outputOff, int outputLen, bool predictionResistant) { - int outputLen = output.Length; - if (mR.Length == 8) // 64 bit block size { if (mReseedCounter > BLOCK64_RESEED_MAX) @@ -140,12 +140,12 @@ namespace Org.BouncyCastle.Crypto.Prng Process(mR, mI, mV); Process(mV, mR, mI); - mR.CopyTo(output[(i * mR.Length)..]); + Array.Copy(mR, 0, output, outputOff + i * mR.Length, mR.Length); Increment(mDT); } - int bytesToCopy = (outputLen - m * mR.Length); + int bytesToCopy = outputLen - m * mR.Length; if (bytesToCopy > 0) { @@ -153,7 +153,7 @@ namespace Org.BouncyCastle.Crypto.Prng Process(mR, mI, mV); Process(mV, mR, mI); - mR.AsSpan(0, bytesToCopy).CopyTo(output[(m * mR.Length)..]); + Array.Copy(mR, 0, output, outputOff + m * mR.Length, bytesToCopy); Increment(mDT); } diff --git a/crypto/src/crypto/prng/X931SecureRandom.cs b/crypto/src/crypto/prng/X931SecureRandom.cs index 2ceff238f..d40134851 100644 --- a/crypto/src/crypto/prng/X931SecureRandom.cs +++ b/crypto/src/crypto/prng/X931SecureRandom.cs @@ -62,6 +62,9 @@ namespace Org.BouncyCastle.Crypto.Prng public override void NextBytes(byte[] buf, int off, int len) { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + NextBytes(buf.AsSpan(off, len)); +#else lock (this) { // check if a reseed is required... @@ -71,6 +74,7 @@ namespace Org.BouncyCastle.Crypto.Prng mDrbg.Generate(buf, off, len, mPredictionResistant); } } +#endif } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER diff --git a/crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs b/crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs index ddb503aa0..cf566ff9c 100644 --- a/crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs +++ b/crypto/src/crypto/prng/drbg/CtrSP800Drbg.cs @@ -1,6 +1,7 @@ using System; using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Encoders; @@ -9,7 +10,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg /** * A SP800-90A CTR DRBG. */ - public class CtrSP800Drbg + public sealed class CtrSP800Drbg : ISP80090Drbg { private static readonly long TDEA_RESEED_MAX = 1L << (32 - 1); @@ -59,20 +60,19 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg mSeedLength = keySizeInBits + engine.GetBlockSize() * 8; mIsTdea = IsTdea(engine); - byte[] entropy = GetEntropy(); // Get_entropy_input - - CTR_DRBG_Instantiate_algorithm(entropy, nonce, personalizationString); + CTR_DRBG_Instantiate_algorithm(nonce, personalizationString); } - private void CTR_DRBG_Instantiate_algorithm(byte[] entropy, byte[] nonce, byte[] personalisationString) + private void CTR_DRBG_Instantiate_algorithm(byte[] nonce, byte[] personalisationString) { - byte[] seedMaterial = Arrays.ConcatenateAll(entropy, nonce, personalisationString); - byte[] seed = Block_Cipher_df(seedMaterial, mSeedLength); + byte[] entropy = GetEntropy(); // Get_entropy_input + byte[] seedMaterial = Arrays.ConcatenateAll(entropy, nonce, personalisationString); + byte[] seed = BlockCipherDF(seedMaterial, mSeedLength / 8); - int outlen = mEngine.GetBlockSize(); + int blockSize = mEngine.GetBlockSize(); mKey = new byte[(mKeySizeInBits + 7) / 8]; - mV = new byte[outlen]; + mV = new byte[blockSize]; // mKey & mV are modified by this call CTR_DRBG_Update(seed, mKey, mV); @@ -80,55 +80,130 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg mReseedCounter = 1; } +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void CTR_DRBG_Update(ReadOnlySpan seed, Span key, Span v) + { + int seedLength = seed.Length; + Span temp = seedLength <= 256 + ? stackalloc byte[seedLength] + : new byte[seedLength]; + + int blockSize = mEngine.GetBlockSize(); + Span block = blockSize <= 64 + ? stackalloc byte[blockSize] + : new byte[blockSize]; + + mEngine.Init(true, ExpandToKeyParameter(key)); + for (int i = 0; i * blockSize < seed.Length; ++i) + { + AddOneTo(v); + mEngine.ProcessBlock(v, block); + + int bytesToCopy = System.Math.Min(blockSize, temp.Length - i * blockSize); + block[..bytesToCopy].CopyTo(temp[(i * blockSize)..]); + } + + XorWith(seed, temp); + + key.CopyFrom(temp); + v.CopyFrom(temp[key.Length..]); + } +#else private void CTR_DRBG_Update(byte[] seed, byte[] key, byte[] v) { - byte[] temp = new byte[seed.Length]; + byte[] temp = new byte[seed.Length]; byte[] outputBlock = new byte[mEngine.GetBlockSize()]; int i = 0; int outLen = mEngine.GetBlockSize(); - mEngine.Init(true, new KeyParameter(ExpandKey(key))); - while (i*outLen < seed.Length) + mEngine.Init(true, ExpandToKeyParameter(key)); + while (i * outLen < seed.Length) { AddOneTo(v); mEngine.ProcessBlock(v, 0, outputBlock, 0); - int bytesToCopy = ((temp.Length - i * outLen) > outLen) - ? outLen : (temp.Length - i * outLen); - + int bytesToCopy = System.Math.Min(outLen, temp.Length - i * outLen); Array.Copy(outputBlock, 0, temp, i * outLen, bytesToCopy); ++i; } - XOR(temp, seed, temp, 0); + Xor(temp, seed, temp, 0); Array.Copy(temp, 0, key, 0, key.Length); Array.Copy(temp, key.Length, v, 0, v.Length); - } - - private void CTR_DRBG_Reseed_algorithm(byte[] additionalInput) - { - byte[] seedMaterial = Arrays.Concatenate(GetEntropy(), additionalInput); + } +#endif - seedMaterial = Block_Cipher_df(seedMaterial, mSeedLength); + private void CTR_DRBG_Reseed_algorithm(byte[] additionalInput) + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + CTR_DRBG_Reseed_algorithm(Spans.FromNullableReadOnly(additionalInput)); +#else + byte[] seedMaterial = Arrays.Concatenate(GetEntropy(), additionalInput); + + seedMaterial = BlockCipherDF(seedMaterial, mSeedLength / 8); CTR_DRBG_Update(seedMaterial, mKey, mV); mReseedCounter = 1; - } +#endif + } - private void XOR(byte[] output, byte[] a, byte[] b, int bOff) +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void CTR_DRBG_Reseed_algorithm(ReadOnlySpan additionalInput) + { + int entropyLength = GetEntropyLength(); + int seedLength = entropyLength + additionalInput.Length; + + Span seedMaterial = seedLength <= 256 + ? stackalloc byte[seedLength] + : new byte[seedLength]; + + GetEntropy(seedMaterial[..entropyLength]); + additionalInput.CopyTo(seedMaterial[entropyLength..]); + + seedMaterial = BlockCipherDF(seedMaterial, mSeedLength / 8); + + CTR_DRBG_Update(seedMaterial, mKey, mV); + + mReseedCounter = 1; + } +#endif + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void Xor(ReadOnlySpan x, ReadOnlySpan y, Span z) + { + for (int i = 0; i < z.Length; ++i) + { + z[i] = (byte)(x[i] ^ y[i]); + } + } + + private void XorWith(ReadOnlySpan x, Span z) + { + for (int i = 0; i < z.Length; ++i) + { + z[i] ^= x[i]; + } + } +#else + private void Xor(byte[] output, byte[] a, byte[] b, int bOff) { for (int i = 0; i < output.Length; i++) { output[i] = (byte)(a[i] ^ b[bOff + i]); } } +#endif - private void AddOneTo(byte[] longer) - { - uint carry = 1; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void AddOneTo(Span longer) +#else + private void AddOneTo(byte[] longer) +#endif + { + uint carry = 1; int i = longer.Length; while (--i >= 0) { @@ -146,83 +221,101 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg return entropy; } - // -- Internal state migration --- +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + 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 static readonly byte[] K_BITS = Hex.DecodeStrict("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); + private int GetEntropyLength() + { + return (mEntropySource.EntropySize + 7) / 8; + } +#endif + + // -- Internal state migration --- + + private static readonly byte[] K_BITS = Hex.DecodeStrict( + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); // 1. If (number_of_bits_to_return > max_number_of_bits), then return an - // ERROR_FLAG. - // 2. L = len (input_string)/8. - // 3. N = number_of_bits_to_return/8. - // Comment: L is the bitstring represention of - // the integer resulting from len (input_string)/8. - // L shall be represented as a 32-bit integer. - // - // Comment : N is the bitstring represention of - // the integer resulting from - // number_of_bits_to_return/8. N shall be - // represented as a 32-bit integer. - // - // 4. S = L || N || input_string || 0x80. - // 5. While (len (S) mod outlen) - // Comment : Pad S with zeros, if necessary. - // 0, S = S || 0x00. - // - // Comment : Compute the starting value. - // 6. temp = the Null string. - // 7. i = 0. - // 8. K = Leftmost keylen bits of 0x00010203...1D1E1F. - // 9. While len (temp) < keylen + outlen, do - // - // IV = i || 0outlen - len (i). - // - // 9.1 - // - // temp = temp || BCC (K, (IV || S)). - // - // 9.2 - // - // i = i + 1. - // - // 9.3 - // - // Comment : i shall be represented as a 32-bit - // integer, i.e., len (i) = 32. - // - // Comment: The 32-bit integer represenation of - // i is padded with zeros to outlen bits. - // - // Comment: Compute the requested number of - // bits. - // - // 10. K = Leftmost keylen bits of temp. - // - // 11. X = Next outlen bits of temp. - // - // 12. temp = the Null string. - // - // 13. While len (temp) < number_of_bits_to_return, do - // - // 13.1 X = Block_Encrypt (K, X). - // - // 13.2 temp = temp || X. - // - // 14. requested_bits = Leftmost number_of_bits_to_return of temp. - // - // 15. Return SUCCESS and requested_bits. - private byte[] Block_Cipher_df(byte[] inputString, int bitLength) - { - int outLen = mEngine.GetBlockSize(); - int L = inputString.Length; // already in bytes - int N = bitLength / 8; - // 4 S = L || N || inputstring || 0x80 + // ERROR_FLAG. + // 2. L = len (input_string)/8. + // 3. N = number_of_bits_to_return/8. + // Comment: L is the bitstring represention of + // the integer resulting from len (input_string)/8. + // L shall be represented as a 32-bit integer. + // + // Comment : N is the bitstring represention of + // the integer resulting from + // number_of_bits_to_return/8. N shall be + // represented as a 32-bit integer. + // + // 4. S = L || N || input_string || 0x80. + // 5. While (len (S) mod outlen) + // Comment : Pad S with zeros, if necessary. + // 0, S = S || 0x00. + // + // Comment : Compute the starting value. + // 6. temp = the Null string. + // 7. i = 0. + // 8. K = Leftmost keylen bits of 0x00010203...1D1E1F. + // 9. While len (temp) < keylen + outlen, do + // + // IV = i || 0outlen - len (i). + // + // 9.1 + // + // temp = temp || BCC (K, (IV || S)). + // + // 9.2 + // + // i = i + 1. + // + // 9.3 + // + // Comment : i shall be represented as a 32-bit + // integer, i.e., len (i) = 32. + // + // Comment: The 32-bit integer represenation of + // i is padded with zeros to outlen bits. + // + // Comment: Compute the requested number of + // bits. + // + // 10. K = Leftmost keylen bits of temp. + // + // 11. X = Next outlen bits of temp. + // + // 12. temp = the Null string. + // + // 13. While len (temp) < number_of_bits_to_return, do + // + // 13.1 X = Block_Encrypt (K, X). + // + // 13.2 temp = temp || X. + // + // 14. requested_bits = Leftmost number_of_bits_to_return of temp. + // + // 15. Return SUCCESS and requested_bits. + private byte[] BlockCipherDF(byte[] input, int N) + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return BlockCipherDF(input.AsSpan(), N); +#else + int outLen = mEngine.GetBlockSize(); + int L = input.Length; // already in bytes + // 4 S = L || N || input || 0x80 int sLen = 4 + 4 + L + 1; int blockLen = ((sLen + outLen - 1) / outLen) * outLen; byte[] S = new byte[blockLen]; - copyIntToByteArray(S, L, 0); - copyIntToByteArray(S, N, 4); - Array.Copy(inputString, 0, S, 8, L); - S[8 + L] = (byte)0x80; + Pack.UInt32_To_BE((uint)L, S, 0); + Pack.UInt32_To_BE((uint)N, S, 4); + Array.Copy(input, 0, S, 8, L); + S[8 + L] = 0x80; // S already padded with zeros byte[] temp = new byte[mKeySizeInBits / 8 + outLen]; @@ -233,16 +326,15 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg int i = 0; byte[] K = new byte[mKeySizeInBits / 8]; Array.Copy(K_BITS, 0, K, 0, K.Length); + var K1 = ExpandToKeyParameter(K); + mEngine.Init(true, K1); while (i*outLen*8 < mKeySizeInBits + outLen *8) { - copyIntToByteArray(IV, i, 0); - BCC(bccOut, K, IV, S); + Pack.UInt32_To_BE((uint)i, IV, 0); + BCC(bccOut, IV, S); - int bytesToCopy = ((temp.Length - i * outLen) > outLen) - ? outLen - : (temp.Length - i * outLen); - + int bytesToCopy = System.Math.Min(outLen, temp.Length - i * outLen); Array.Copy(bccOut, 0, temp, i * outLen, bytesToCopy); ++i; } @@ -251,39 +343,120 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg Array.Copy(temp, 0, K, 0, K.Length); Array.Copy(temp, K.Length, X, 0, X.Length); - temp = new byte[bitLength / 2]; + temp = new byte[N]; i = 0; - mEngine.Init(true, new KeyParameter(ExpandKey(K))); + mEngine.Init(true, ExpandToKeyParameter(K)); while (i * outLen < temp.Length) { mEngine.ProcessBlock(X, 0, X, 0); - int bytesToCopy = ((temp.Length - i * outLen) > outLen) - ? outLen - : (temp.Length - i * outLen); - + int bytesToCopy = System.Math.Min(outLen, temp.Length - i * outLen); Array.Copy(X, 0, temp, i * outLen, bytesToCopy); i++; } return temp; - } +#endif + } - /* - * 1. chaining_value = 0^outlen - * . Comment: Set the first chaining value to outlen zeros. - * 2. n = len (data)/outlen. - * 3. Starting with the leftmost bits of data, split the data into n blocks of outlen bits - * each, forming block(1) to block(n). - * 4. For i = 1 to n do - * 4.1 input_block = chaining_value ^ block(i) . - * 4.2 chaining_value = Block_Encrypt (Key, input_block). - * 5. output_block = chaining_value. - * 6. Return output_block. - */ - private void BCC(byte[] bccOut, byte[] k, byte[] iV, byte[] data) +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private byte[] BlockCipherDF(ReadOnlySpan input, int N) + { + int blockSize = mEngine.GetBlockSize(); + int L = input.Length; // already in bytes + // 4 S = L || N || input || 0x80 + int sLen = 4 + 4 + L + 1; + int blockLen = ((sLen + blockSize - 1) / blockSize) * blockSize; + Span S = blockLen <= 256 + ? stackalloc byte[blockLen] + : new byte[blockLen]; + Pack.UInt32_To_BE((uint)L, S); + Pack.UInt32_To_BE((uint)N, S[4..]); + input.CopyTo(S[8..]); + S[8 + L] = 0x80; + // S already padded with zeros + + int keySize = mKeySizeInBits / 8; + int tempSize = keySize + blockSize; + Span temp = tempSize <= 128 + ? stackalloc byte[tempSize] + : new byte[tempSize]; + + Span bccOut = blockSize <= 64 + ? stackalloc byte[blockSize] + : new byte[blockSize]; + + Span IV = blockSize <= 64 + ? stackalloc byte[blockSize] + : new byte[blockSize]; + + var K1 = ExpandToKeyParameter(K_BITS.AsSpan(0, keySize)); + mEngine.Init(true, K1); + + for (int i = 0; i * blockSize < tempSize; ++i) + { + Pack.UInt32_To_BE((uint)i, IV); + BCC(bccOut, IV, S); + + int bytesToCopy = System.Math.Min(blockSize, tempSize - i * blockSize); + bccOut[..bytesToCopy].CopyTo(temp[(i * blockSize)..]); + } + + var K2 = ExpandToKeyParameter(temp[..keySize]); + mEngine.Init(true, K2); + var X = temp[keySize..]; + + byte[] result = new byte[N]; + for (int i = 0; i * blockSize < result.Length; ++i) + { + mEngine.ProcessBlock(X, X); + + int bytesToCopy = System.Math.Min(blockSize, result.Length - i * blockSize); + X[..bytesToCopy].CopyTo(result.AsSpan(i * blockSize)); + } + return result; + } +#endif + + /* + * 1. chaining_value = 0^outlen + * . Comment: Set the first chaining value to outlen zeros. + * 2. n = len (data)/outlen. + * 3. Starting with the leftmost bits of data, split the data into n blocks of outlen bits + * each, forming block(1) to block(n). + * 4. For i = 1 to n do + * 4.1 input_block = chaining_value ^ block(i) . + * 4.2 chaining_value = Block_Encrypt (Key, input_block). + * 5. output_block = chaining_value. + * 6. Return output_block. + */ +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void BCC(Span bccOut, ReadOnlySpan iV, ReadOnlySpan data) + { + int blockSize = mEngine.GetBlockSize(); + + Span chainingValue = blockSize <= 64 + ? stackalloc byte[blockSize] + : new byte[blockSize]; + Span inputBlock = blockSize <= 64 + ? stackalloc byte[blockSize] + : new byte[blockSize]; + + mEngine.ProcessBlock(iV, chainingValue); + + int n = data.Length / blockSize; + for (int i = 0; i < n; i++) + { + Xor(chainingValue, data[(i * blockSize)..], inputBlock); + mEngine.ProcessBlock(inputBlock, chainingValue); + } + + bccOut.CopyFrom(chainingValue); + } +#else + private void BCC(byte[] bccOut, byte[] iV, byte[] data) { int outlen = mEngine.GetBlockSize(); byte[] chainingValue = new byte[outlen]; // initial values = 0 @@ -291,33 +464,24 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg byte[] inputBlock = new byte[outlen]; - mEngine.Init(true, new KeyParameter(ExpandKey(k))); - mEngine.ProcessBlock(iV, 0, chainingValue, 0); for (int i = 0; i < n; i++) { - XOR(inputBlock, chainingValue, data, i*outlen); + Xor(inputBlock, chainingValue, data, i*outlen); mEngine.ProcessBlock(inputBlock, 0, chainingValue, 0); } Array.Copy(chainingValue, 0, bccOut, 0, bccOut.Length); } +#endif - private void copyIntToByteArray(byte[] buf, int value, int offSet) - { - buf[offSet + 0] = ((byte)(value >> 24)); - buf[offSet + 1] = ((byte)(value >> 16)); - buf[offSet + 2] = ((byte)(value >> 8)); - buf[offSet + 3] = ((byte)(value)); - } - - /** + /** * 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 + public int BlockSize { get { return mV.Length * 8; } } @@ -335,7 +499,9 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg bool predictionResistant) { #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - return Generate(output.AsSpan(outputOff, outputLen), additionalInput, predictionResistant); + return additionalInput == null + ? Generate(output.AsSpan(outputOff, outputLen), predictionResistant) + : GenerateWithInput(output.AsSpan(outputOff, outputLen), additionalInput.AsSpan(), predictionResistant); #else if (mIsTdea) { @@ -362,7 +528,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg if (additionalInput != null) { - additionalInput = Block_Cipher_df(additionalInput, mSeedLength); + additionalInput = BlockCipherDF(additionalInput, mSeedLength / 8); CTR_DRBG_Update(additionalInput, mKey, mV); } else @@ -372,7 +538,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg byte[] tmp = new byte[mV.Length]; - mEngine.Init(true, new KeyParameter(ExpandKey(mKey))); + mEngine.Init(true, ExpandToKeyParameter(mKey)); for (int i = 0, limit = outputLen / tmp.Length; i <= limit; i++) { @@ -397,9 +563,9 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - public int Generate(Span output, byte[] additionalInput, bool predictionResistant) - { - int outputLen = output.Length; + public int Generate(Span output, bool predictionResistant) + { + int outputLen = output.Length; if (mIsTdea) { if (mReseedCounter > TDEA_RESEED_MAX) @@ -419,24 +585,57 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg if (predictionResistant) { - CTR_DRBG_Reseed_algorithm(additionalInput); - additionalInput = null; + CTR_DRBG_Reseed_algorithm(ReadOnlySpan.Empty); } - if (additionalInput != null) + byte[] seed = new byte[mSeedLength / 8]; + + return ImplGenerate(seed, output); + } + + public int GenerateWithInput(Span output, ReadOnlySpan additionalInput, bool predictionResistant) + { + int outputLen = output.Length; + if (mIsTdea) { - additionalInput = Block_Cipher_df(additionalInput, mSeedLength); - CTR_DRBG_Update(additionalInput, mKey, mV); + if (mReseedCounter > TDEA_RESEED_MAX) + return -1; + + if (outputLen > TDEA_MAX_BITS_REQUEST / 8) + throw new ArgumentException("Number of bits per request limited to " + TDEA_MAX_BITS_REQUEST, "output"); } else { - additionalInput = new byte[mSeedLength]; + if (mReseedCounter > AES_RESEED_MAX) + return -1; + + if (outputLen > AES_MAX_BITS_REQUEST / 8) + throw new ArgumentException("Number of bits per request limited to " + AES_MAX_BITS_REQUEST, "output"); } + int seedLength = mSeedLength / 8; + byte[] seed; + if (predictionResistant) + { + CTR_DRBG_Reseed_algorithm(additionalInput); + seed = new byte[seedLength]; + } + else + { + seed = BlockCipherDF(additionalInput, seedLength); + CTR_DRBG_Update(seed, mKey, mV); + } + + return ImplGenerate(seed, output); + } + + private int ImplGenerate(ReadOnlySpan seed, Span output) + { byte[] tmp = new byte[mV.Length]; - mEngine.Init(true, new KeyParameter(ExpandKey(mKey))); + mEngine.Init(true, ExpandToKeyParameter(mKey)); + int outputLen = output.Length; for (int i = 0, limit = outputLen / tmp.Length; i <= limit; i++) { int bytesToCopy = System.Math.Min(tmp.Length, outputLen - i * tmp.Length); @@ -447,11 +646,11 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg mEngine.ProcessBlock(mV, 0, tmp, 0); - tmp[..bytesToCopy].CopyTo(output[(i * tmp.Length)..]); + tmp[..bytesToCopy].CopyTo(output[(i * tmp.Length)..]); } } - CTR_DRBG_Update(additionalInput, mKey, mV); + CTR_DRBG_Update(seed, mKey, mV); mReseedCounter++; @@ -466,9 +665,20 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg */ public void Reseed(byte[] additionalInput) { - CTR_DRBG_Reseed_algorithm(additionalInput); +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Reseed(Spans.FromNullableReadOnly(additionalInput)); +#else + CTR_DRBG_Reseed_algorithm(additionalInput); +#endif } +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public void Reseed(ReadOnlySpan additionalInput) + { + CTR_DRBG_Reseed_algorithm(additionalInput); + } +#endif + private bool IsTdea(IBlockCipher cipher) { return cipher.AlgorithmName.Equals("DESede") || cipher.AlgorithmName.Equals("TDEA"); @@ -488,26 +698,39 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg return -1; } - private byte[] ExpandKey(byte[] key) + private KeyParameter ExpandToKeyParameter(byte[] key) { - if (mIsTdea) - { - // expand key to 192 bits. - byte[] tmp = new byte[24]; + if (!mIsTdea) + return new KeyParameter(key); - PadKey(key, 0, tmp, 0); - PadKey(key, 7, tmp, 8); - PadKey(key, 14, tmp, 16); + // expand key to 192 bits. + byte[] tmp = new byte[24]; - return tmp; - } - else - { - return key; - } - } + PadKey(key, 0, tmp, 0); + PadKey(key, 7, tmp, 8); + PadKey(key, 14, tmp, 16); - /** + return new KeyParameter(tmp); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private KeyParameter ExpandToKeyParameter(ReadOnlySpan key) + { + if (!mIsTdea) + return new KeyParameter(key); + + // expand key to 192 bits. + Span tmp = stackalloc byte[24]; + + PadKey(key, tmp); + PadKey(key[7..], tmp[8..]); + PadKey(key[14..], tmp[16..]); + + return new KeyParameter(tmp); + } +#endif + + /** * Pad out a key for TDEA, setting odd parity for each byte. * * @param keyMaster @@ -528,5 +751,21 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg DesParameters.SetOddParity(tmp, tmpOff, 8); } - } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void PadKey(ReadOnlySpan keyMaster, Span tmp) + { + tmp[0] = (byte)(keyMaster[0] & 0xFE); + tmp[1] = (byte)((keyMaster[0] << 7) | ((keyMaster[1] & 0xfc) >> 1)); + tmp[2] = (byte)((keyMaster[1] << 6) | ((keyMaster[2] & 0xf8) >> 2)); + tmp[3] = (byte)((keyMaster[2] << 5) | ((keyMaster[3] & 0xf0) >> 3)); + tmp[4] = (byte)((keyMaster[3] << 4) | ((keyMaster[4] & 0xe0) >> 4)); + tmp[5] = (byte)((keyMaster[4] << 3) | ((keyMaster[5] & 0xc0) >> 5)); + tmp[6] = (byte)((keyMaster[5] << 2) | ((keyMaster[6] & 0x80) >> 6)); + tmp[7] = (byte)(keyMaster[6] << 1); + + DesParameters.SetOddParity(tmp[..8]); + } +#endif + } } diff --git a/crypto/src/crypto/prng/drbg/DrbgUtilities.cs b/crypto/src/crypto/prng/drbg/DrbgUtilities.cs index 58baaf5d9..24037b3f1 100644 --- a/crypto/src/crypto/prng/drbg/DrbgUtilities.cs +++ b/crypto/src/crypto/prng/drbg/DrbgUtilities.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Crypto.Utilities; namespace Org.BouncyCastle.Crypto.Prng.Drbg { @@ -35,65 +35,77 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg return MaxSecurityStrengths[name.Substring(0, name.IndexOf("/"))]; } - /** + /** * Used by both Dual EC and Hash. */ - internal static byte[] HashDF(IDigest digest, byte[] seedMaterial, int seedLength) - { - // 1. temp = the Null string. - // 2. . - // 3. counter = an 8-bit binary value representing the integer "1". - // 4. For i = 1 to len do - // Comment : In step 4.1, no_of_bits_to_return - // is used as a 32-bit string. - // 4.1 temp = temp || Hash (counter || no_of_bits_to_return || - // input_string). - // 4.2 counter = counter + 1. - // 5. requested_bits = Leftmost (no_of_bits_to_return) of temp. - // 6. Return SUCCESS and requested_bits. - byte[] temp = new byte[(seedLength + 7) / 8]; - - int len = temp.Length / digest.GetDigestSize(); +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + internal static void HashDF(IDigest digest, ReadOnlySpan seedMaterial, int seedLength, Span output) +#else + internal static void HashDF(IDigest digest, byte[] seedMaterial, int seedLength, byte[] output) +#endif + { + // 1. temp = the Null string. + // 2. . + // 3. counter = an 8-bit binary value representing the integer "1". + // 4. For i = 1 to len do + // Comment : In step 4.1, no_of_bits_to_return + // is used as a 32-bit string. + // 4.1 temp = temp || Hash (counter || no_of_bits_to_return || + // input_string). + // 4.2 counter = counter + 1. + // 5. requested_bits = Leftmost (no_of_bits_to_return) of temp. + // 6. Return SUCCESS and requested_bits. + int outputLength = (seedLength + 7) / 8; + + int digestSize = digest.GetDigestSize(); + int len = outputLength / digestSize; int counter = 1; - byte[] dig = new byte[digest.GetDigestSize()]; - - for (int i = 0; i <= len; i++) +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Span dig = digestSize <= 128 + ? stackalloc byte[digestSize] + : new byte[digestSize]; + Span header = stackalloc byte[5]; + Pack.UInt32_To_BE((uint)seedLength, header[1..]); +#else + byte[] dig = new byte[digestSize]; + byte[] header = new byte[5]; + Pack.UInt32_To_BE((uint)seedLength, header, 1); +#endif + + for (int i = 0; i <= len; i++, counter++) { - digest.Update((byte)counter); - - digest.Update((byte)(seedLength >> 24)); - digest.Update((byte)(seedLength >> 16)); - digest.Update((byte)(seedLength >> 8)); - digest.Update((byte)seedLength); - - digest.BlockUpdate(seedMaterial, 0, seedMaterial.Length); - - digest.DoFinal(dig, 0); - - int bytesToCopy = ((temp.Length - i * dig.Length) > dig.Length) - ? dig.Length - : (temp.Length - i * dig.Length); - Array.Copy(dig, 0, temp, i * dig.Length, bytesToCopy); - - counter++; - } - - // do a left shift to get rid of excess bits. - if (seedLength % 8 != 0) + header[0] = (byte)counter; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + digest.BlockUpdate(header); + digest.BlockUpdate(seedMaterial); + digest.DoFinal(dig); + + int bytesToCopy = System.Math.Min(digestSize, outputLength - i * digestSize); + dig[..bytesToCopy].CopyTo(output[(i * digestSize)..]); +#else + digest.BlockUpdate(header, 0, header.Length); + digest.BlockUpdate(seedMaterial, 0, seedMaterial.Length); + digest.DoFinal(dig, 0); + + int bytesToCopy = System.Math.Min(digestSize, outputLength - i * digestSize); + Array.Copy(dig, 0, output, i * digestSize, bytesToCopy); +#endif + } + + // do a left shift to get rid of excess bits. + if (seedLength % 8 != 0) { int shift = 8 - (seedLength % 8); uint carry = 0; - for (int i = 0; i != temp.Length; i++) + for (int i = 0; i != outputLength; i++) { - uint b = temp[i]; - temp[i] = (byte)((b >> shift) | (carry << (8 - shift))); + uint b = output[i]; + output[i] = (byte)((b >> shift) | (carry << (8 - shift))); carry = b; } } - - return temp; } } } diff --git a/crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs b/crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs index 5a684a89d..47e0191dd 100644 --- a/crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs +++ b/crypto/src/crypto/prng/drbg/HMacSP800Drbg.cs @@ -8,7 +8,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg /** * A SP800-90A HMAC DRBG. */ - public class HMacSP800Drbg + public sealed class HMacSP800Drbg : ISP80090Drbg { private readonly static long RESEED_MAX = 1L << (48 - 1); @@ -33,7 +33,8 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg * @param personalizationString personalization string to distinguish this DRBG (may be null). * @param nonce nonce to further distinguish this DRBG (may be null). */ - public HMacSP800Drbg(IMac hMac, int securityStrength, IEntropySource entropySource, byte[] personalizationString, byte[] nonce) + public HMacSP800Drbg(IMac hMac, int securityStrength, IEntropySource entropySource, + byte[] personalizationString, byte[] nonce) { if (securityStrength > DrbgUtilities.GetMaxSecurityStrength(hMac)) throw new ArgumentException("Requested security strength is not supported by the derivation function"); @@ -56,12 +57,41 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg mReseedCounter = 1; } - private void hmac_DRBG_Update(byte[] seedMaterial) +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void hmac_DRBG_Update() + { + hmac_DRBG_Update_Func(ReadOnlySpan.Empty, 0x00); + } + + private void hmac_DRBG_Update(ReadOnlySpan seedMaterial) + { + hmac_DRBG_Update_Func(seedMaterial, 0x00); + hmac_DRBG_Update_Func(seedMaterial, 0x01); + } + + private void hmac_DRBG_Update_Func(ReadOnlySpan seedMaterial, byte vValue) + { + mHMac.Init(new KeyParameter(mK)); + + mHMac.BlockUpdate(mV); + mHMac.Update(vValue); + if (!seedMaterial.IsEmpty) + { + mHMac.BlockUpdate(seedMaterial); + } + mHMac.DoFinal(mK); + + mHMac.Init(new KeyParameter(mK)); + mHMac.BlockUpdate(mV); + mHMac.DoFinal(mV); + } +#else + private void hmac_DRBG_Update(byte[] seedMaterial) { - hmac_DRBG_Update_Func(seedMaterial, (byte)0x00); + hmac_DRBG_Update_Func(seedMaterial, 0x00); if (seedMaterial != null) { - hmac_DRBG_Update_Func(seedMaterial, (byte)0x01); + hmac_DRBG_Update_Func(seedMaterial, 0x01); } } @@ -84,13 +114,14 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg mHMac.DoFinal(mV, 0); } +#endif - /** + /** * Return the block size (in bits) of the DRBG. * * @return the number of bits produced on each round of the DRBG. */ - public int BlockSize + public int BlockSize { get { return mV.Length * 8; } } @@ -108,7 +139,9 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg bool predictionResistant) { #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - return Generate(output.AsSpan(outputOff, outputLen), additionalInput, predictionResistant); + return additionalInput == null + ? Generate(output.AsSpan(outputOff, outputLen), predictionResistant) + : GenerateWithInput(output.AsSpan(outputOff, outputLen), additionalInput.AsSpan(), predictionResistant); #else int numberOfBits = outputLen * 8; @@ -164,10 +197,9 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - public int Generate(Span output, byte[] additionalInput, bool predictionResistant) + public int Generate(Span output, bool predictionResistant) { - int outputLen = output.Length; - int numberOfBits = outputLen * 8; + int numberOfBits = output.Length * 8; if (numberOfBits > MAX_BITS_REQUEST) throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output"); @@ -177,43 +209,79 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg if (predictionResistant) { - Reseed(additionalInput); - additionalInput = null; + Reseed(ReadOnlySpan.Empty); } - // 2. - if (additionalInput != null) + // 3. + ImplGenerate(output); + + hmac_DRBG_Update(); + + mReseedCounter++; + + return numberOfBits; + } + + public int GenerateWithInput(Span output, ReadOnlySpan additionalInput, bool predictionResistant) + { + 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. hmac_DRBG_Update(additionalInput); } // 3. + ImplGenerate(output); + + if (predictionResistant) + { + hmac_DRBG_Update(); + } + else + { + hmac_DRBG_Update(additionalInput); + } + + mReseedCounter++; + + return numberOfBits; + } + + private void ImplGenerate(Span output) + { + int outputLen = output.Length; int m = outputLen / mV.Length; mHMac.Init(new KeyParameter(mK)); for (int i = 0; i < m; i++) { - mHMac.BlockUpdate(mV, 0, mV.Length); - mHMac.DoFinal(mV, 0); + mHMac.BlockUpdate(mV); + mHMac.DoFinal(mV); - mV.CopyTo(output[(i * mV.Length)..]); + mV.CopyTo(output[(i * mV.Length)..]); } - int remaining = outputLen - m * mV.Length; + int remaining = outputLen - m * mV.Length; if (remaining > 0) { - mHMac.BlockUpdate(mV, 0, mV.Length); - mHMac.DoFinal(mV, 0); + mHMac.BlockUpdate(mV); + mHMac.DoFinal(mV); - mV[..remaining].CopyTo(output[(m * mV.Length)..]); + mV[..remaining].CopyTo(output[(m * mV.Length)..]); } - - hmac_DRBG_Update(additionalInput); - - mReseedCounter++; - - return numberOfBits; } #endif @@ -224,14 +292,35 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg */ public void Reseed(byte[] additionalInput) { - byte[] entropy = GetEntropy(); +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Reseed(Spans.FromNullableReadOnly(additionalInput)); +#else + byte[] entropy = GetEntropy(); byte[] seedMaterial = Arrays.Concatenate(entropy, additionalInput); hmac_DRBG_Update(seedMaterial); mReseedCounter = 1; +#endif } +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public void Reseed(ReadOnlySpan additionalInput) + { + int entropyLength = GetEntropyLength(); + int seedMaterialLength = entropyLength + additionalInput.Length; + Span seedMaterial = seedMaterialLength <= 256 + ? stackalloc byte[seedMaterialLength] + : new byte[seedMaterialLength]; + GetEntropy(seedMaterial); + additionalInput.CopyTo(seedMaterial[entropyLength..]); + + hmac_DRBG_Update(seedMaterial); + + mReseedCounter = 1; + } +#endif + private byte[] GetEntropy() { byte[] entropy = mEntropySource.GetEntropy(); @@ -239,5 +328,20 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg throw new InvalidOperationException("Insufficient entropy provided by entropy source"); return entropy; } - } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + 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 + } } diff --git a/crypto/src/crypto/prng/drbg/HashSP800Drbg.cs b/crypto/src/crypto/prng/drbg/HashSP800Drbg.cs index 2f73b1f4e..3f9cffbcd 100644 --- a/crypto/src/crypto/prng/drbg/HashSP800Drbg.cs +++ b/crypto/src/crypto/prng/drbg/HashSP800Drbg.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; +using Org.BouncyCastle.Crypto.Utilities; using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Prng.Drbg @@ -8,7 +9,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg /** * A SP800-90A Hash DRBG. */ - public class HashSP800Drbg + public sealed class HashSP800Drbg : ISP80090Drbg { private readonly static byte[] ONE = { 0x01 }; @@ -72,12 +73,13 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg byte[] entropy = GetEntropy(); byte[] seedMaterial = Arrays.ConcatenateAll(entropy, nonce, personalizationString); - byte[] seed = DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength); + mV = new byte[(mSeedLength + 7) / 8]; + DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength, mV); - mV = seed; byte[] subV = new byte[mV.Length + 1]; Array.Copy(mV, 0, subV, 1, mV.Length); - mC = DrbgUtilities.HashDF(mDigest, subV, mSeedLength); + mC = new byte[(mSeedLength + 7) / 8]; + DrbgUtilities.HashDF(mDigest, subV, mSeedLength, mC); mReseedCounter = 1; } @@ -89,7 +91,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg */ public int BlockSize { - get { return mDigest.GetDigestSize () * 8; } + get { return mDigest.GetDigestSize() * 8; } } /** @@ -105,7 +107,9 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg bool predictionResistant) { #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - return Generate(output.AsSpan(outputOff, outputLen), additionalInput, predictionResistant); + 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. @@ -147,7 +151,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg } // 3. - byte[] rv = hashgen(mV, numberOfBits); + byte[] rv = Hashgen(mV, outputLen); // 4. byte[] subH = new byte[mV.Length + 1]; @@ -159,11 +163,9 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg // 5. AddTo(mV, H); AddTo(mV, mC); + byte[] c = new byte[4]; - c[0] = (byte)(mReseedCounter >> 24); - c[1] = (byte)(mReseedCounter >> 16); - c[2] = (byte)(mReseedCounter >> 8); - c[3] = (byte)mReseedCounter; + Pack.UInt32_To_BE((uint)mReseedCounter, c); AddTo(mV, c); @@ -176,7 +178,38 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - public int Generate(Span output, byte[] additionalInput, bool predictionResistant) + 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. @@ -191,8 +224,7 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg // 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 outputLen = output.Length; - int numberOfBits = outputLen * 8; + int numberOfBits = output.Length * 8; if (numberOfBits > MAX_BITS_REQUEST) throw new ArgumentException("Number of bits per request limited to " + MAX_BITS_REQUEST, "output"); @@ -203,47 +235,53 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg 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); + 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. - byte[] rv = hashgen(mV, numberOfBits); + Hashgen(mV, output); // 4. - byte[] subH = new byte[mV.Length + 1]; - Array.Copy(mV, 0, subH, 1, mV.Length); - subH[0] = 0x03; + mDigest.Update(0x03); + mDigest.BlockUpdate(mV); - byte[] H = Hash(subH); + int digestSize = mDigest.GetDigestSize(); + Span H = digestSize <= 128 + ? stackalloc byte[digestSize] + : new byte[digestSize]; + mDigest.DoFinal(H); // 5. AddTo(mV, H); AddTo(mV, mC); - byte[] c = new byte[4]; - c[0] = (byte)(mReseedCounter >> 24); - c[1] = (byte)(mReseedCounter >> 16); - c[2] = (byte)(mReseedCounter >> 8); - c[3] = (byte)mReseedCounter; + + Span c = stackalloc byte[4]; + Pack.UInt32_To_BE((uint)mReseedCounter, c); AddTo(mV, c); mReseedCounter++; - rv.CopyTo(output); - - return numberOfBits; + return output.Length * 8; } #endif @@ -255,18 +293,37 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg return entropy; } - // this will always add the shorter length byte array mathematically to the - // longer length byte array. - // be careful.... - private void AddTo(byte[] longer, byte[] shorter) - { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + 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 + 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] + (uint)shorter[i]; + carry += (uint)longer[off + i] + shorter[i]; longer[off + i] = (byte)carry; carry >>= 8; } @@ -287,78 +344,155 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg */ public void Reseed(byte[] 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. - byte[] entropy = GetEntropy(); +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + 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); - byte[] seed = DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength); + DrbgUtilities.HashDF(mDigest, seedMaterial, mSeedLength, mV); - mV = seed; byte[] subV = new byte[mV.Length + 1]; subV[0] = 0x00; Array.Copy(mV, 0, subV, 1, mV.Length); - mC = DrbgUtilities.HashDF(mDigest, subV, mSeedLength); + DrbgUtilities.HashDF(mDigest, subV, mSeedLength, mC); mReseedCounter = 1; +#endif } - private byte[] Hash(byte[] input) +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public void Reseed(ReadOnlySpan additionalInput) { - byte[] hash = new byte[mDigest.GetDigestSize()]; - DoHash(input, hash); - return hash; + // 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 - private void DoHash(byte[] input, byte[] output) +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + 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. - private byte[] hashgen(byte[] input, int lengthInBits) + // 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 + private void Hashgen(ReadOnlySpan input, Span output) { int digestSize = mDigest.GetDigestSize(); - int m = (lengthInBits / 8) / digestSize; + int m = output.Length / digestSize; - byte[] data = new byte[input.Length]; - Array.Copy(input, 0, data, 0, input.Length); + int dataSize = input.Length; + Span data = dataSize <= 256 + ? stackalloc byte[input.Length] + : new byte[input.Length]; + input.CopyTo(data); - byte[] W = new byte[lengthInBits / 8]; + Span dig = digestSize <= 128 + ? stackalloc byte[digestSize] + : new byte[digestSize]; - byte[] dig = new byte[mDigest.GetDigestSize()]; for (int i = 0; i <= m; i++) { DoHash(data, dig); - int bytesToCopy = ((W.Length - i * dig.Length) > dig.Length) - ? dig.Length - : (W.Length - i * dig.Length); - Array.Copy(dig, 0, W, i * dig.Length, bytesToCopy); + 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 + } } diff --git a/crypto/src/crypto/prng/drbg/ISP80090Drbg.cs b/crypto/src/crypto/prng/drbg/ISP80090Drbg.cs index e067f20a3..ab7252a8d 100644 --- a/crypto/src/crypto/prng/drbg/ISP80090Drbg.cs +++ b/crypto/src/crypto/prng/drbg/ISP80090Drbg.cs @@ -26,7 +26,9 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg int Generate(byte[] output, int outputOff, int outputLen, byte[] additionalInput, bool predictionResistant); #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - int Generate(Span output, byte[] additionalInput, bool predictionResistant); + int Generate(Span output, bool predictionResistant); + + int GenerateWithInput(Span output, ReadOnlySpan additionalInput, bool predictionResistant); #endif /** @@ -35,5 +37,9 @@ namespace Org.BouncyCastle.Crypto.Prng.Drbg * @param additionalInput additional input to be added to the DRBG in this step. */ void Reseed(byte[] additionalInput); - } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + void Reseed(ReadOnlySpan additionalInput); +#endif + } } diff --git a/crypto/test/src/crypto/prng/test/TestEntropySourceProvider.cs b/crypto/test/src/crypto/prng/test/TestEntropySourceProvider.cs index 647799630..9ebbb7ccd 100644 --- a/crypto/test/src/crypto/prng/test/TestEntropySourceProvider.cs +++ b/crypto/test/src/crypto/prng/test/TestEntropySourceProvider.cs @@ -48,6 +48,18 @@ namespace Org.BouncyCastle.Crypto.Prng.Test return rv; } + // NOTE: .NET Core 2.1 has Span, but is tested against our .NET Standard 2.0 assembly. +//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + int IEntropySource.GetEntropy(Span output) + { + int length = bitsRequired / 8; + data.AsSpan(index, length).CopyTo(output); + index += length; + return length; + } +#endif + public int EntropySize { get { return bitsRequired; } -- cgit 1.4.1