Skip to content

Commit ef0756c

Browse files
committed
Version bump from 1.0 to 1.1.
CryptoRandom: major rewrite and perf improvements (~50% faster). AesCryptoTransform: micro perf improvements. HKDF: micro perf improvements. PBKDF2: perf improvements (fast xor). SP800_108_Ctr: micro code cleanup and perf improvements. TOTP: micro perf improvements. EtM_CBC: micro code cleanup and perf improvements.
1 parent cfba069 commit ef0756c

File tree

8 files changed

+206
-89
lines changed

8 files changed

+206
-89
lines changed

Cipher/AesCtrCryptoTransform.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public AesCtrCryptoTransform(byte[] key, ArraySegment<byte> counterBufferSegment
4343
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
4444
{
4545
int partialBlockSize = inputCount % AesConstants.AES_BLOCK_SIZE;
46-
int fullBlockSize = inputCount - partialBlockSize;
46+
int fullBlockSize = inputCount & (-AesConstants.AES_BLOCK_SIZE);//inputCount - partialBlockSize;
4747
int i, j;
4848
byte[] counterBuffer = _counterBuffer.Value; // looks dumb, but local-access is faster than field-access
4949

CryptoRandom.cs

Lines changed: 179 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System;
2+
using System.Diagnostics;
3+
using System.Runtime.InteropServices;
24
using System.Security.Cryptography;
5+
using System.Threading;
36

47
namespace SecurityDriven.Inferno
58
{
@@ -13,12 +16,47 @@ namespace SecurityDriven.Inferno
1316
/// </summary>
1417
public class CryptoRandom : Random
1518
{
16-
static RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider();
17-
const int BUFFER_SIZE = 1024 * 4; // 4k buffer seems to work best (empirical experimentation)
18-
const int BUFFERED_THRESHOLD = 100; // non-buffered approach seems faster beyond this point (empirical experimentation)
19-
byte[] _buffer = new byte[BUFFER_SIZE];
20-
int _bufferPosition = BUFFER_SIZE;
21-
object lockObj = new Object();
19+
static readonly int CACHE_THRESHOLD; // non-buffered approach seems faster beyond this threshold (empirical experimentation).
20+
const int BYTE_CACHE_SIZE = 4096; // 4k buffer seems to work best (empirical experimentation). Buffer must be larger than CACHE_THRESHOLD.
21+
readonly byte[] _byteCache = new byte[BYTE_CACHE_SIZE];
22+
volatile int _byteCachePosition = BYTE_CACHE_SIZE;
23+
24+
static readonly Action<byte[]> _fillBufferWithRandomBytes;
25+
static readonly BCrypt.BCryptAlgorithmHandle _bcryptAgorithm;
26+
27+
static CryptoRandom()
28+
{
29+
try { _bcryptAgorithm = BCrypt.OpenAlgorithm(BCrypt.BCRYPT_RNG_ALGORITHM, BCrypt.MS_PRIMITIVE_PROVIDER); }
30+
catch { _bcryptAgorithm = null; }
31+
32+
if (_bcryptAgorithm == null)
33+
{
34+
_fillBufferWithRandomBytes = new RNGCryptoServiceProvider().GetBytes;
35+
CACHE_THRESHOLD = 104;
36+
}
37+
else
38+
{
39+
_fillBufferWithRandomBytes = _bCryptGetBytes;
40+
CACHE_THRESHOLD = 64;
41+
}
42+
}// static ctor
43+
44+
public CryptoRandom() : base(Seed: 0)
45+
{
46+
// Minimize the wasted time of calling default System.Random base ctor.
47+
// We can't avoid calling at least some base ctor, ie. 2~3 milliseconds are wasted anyway.
48+
// That's the price of inheriting from System.Random (doesn't implement an interface).
49+
}// ctor
50+
51+
static void _bCryptGetBytes(byte[] buffer)
52+
{
53+
Debug.Assert(_bcryptAgorithm != null, "algorithm != null");
54+
Debug.Assert(!_bcryptAgorithm.IsClosed && !_bcryptAgorithm.IsInvalid, "!algorithm.IsClosed && !algorithm.IsInvalid");
55+
Debug.Assert(buffer != null, "buffer != null");
56+
57+
BCrypt.ErrorCode errorCode = BCrypt.DllImportedNativeMethods.BCryptGenRandom(_bcryptAgorithm, buffer, buffer.Length, 0);
58+
if (errorCode != BCrypt.ErrorCode.Success) throw new CryptographicException((int)errorCode);
59+
}// _bCryptGetBytes()
2260

2361
#region NextLong()
2462
/// <summary>
@@ -71,13 +109,13 @@ public long NextLong(long minValue, long maxValue)
71109
throw new ArgumentOutOfRangeException("minValue");
72110

73111
ulong diff = decimal.ToUInt64((decimal)maxValue - minValue);
74-
ulong upperBound = ulong.MaxValue / diff * diff - 1;
112+
ulong upperBound = ulong.MaxValue / diff * diff;
75113

76114
ulong ul;
77115
do
78116
{
79117
ul = GetRandomULong();
80-
} while (ul > upperBound);
118+
} while (ul >= upperBound);
81119
return decimal.ToInt64((decimal)minValue + ul % diff);
82120
}//NextLong()
83121
#endregion
@@ -133,13 +171,13 @@ public override int Next(int minValue, int maxValue)
133171
throw new ArgumentOutOfRangeException("minValue");
134172

135173
long diff = (long)maxValue - minValue;
136-
long upperBound = uint.MaxValue / diff * diff - 1;
174+
long upperBound = uint.MaxValue / diff * diff;
137175

138176
uint ui;
139177
do
140178
{
141179
ui = GetRandomUInt();
142-
} while (ui > upperBound);
180+
} while (ui >= upperBound);
143181
return (int)(minValue + (ui % diff));
144182
}//Next()
145183
#endregion
@@ -156,6 +194,18 @@ public override double NextDouble()
156194
return GetRandomUInt() / max;
157195
}//NextDouble()
158196

197+
/// <summary>
198+
/// Returns a new count-sized byte array filled with random bytes.
199+
/// </summary>
200+
/// <param name="count">Array length.</param>
201+
/// <returns>Random byte array.</returns>
202+
public byte[] NextBytes(int count)
203+
{
204+
byte[] bytes = new byte[count];
205+
this.NextBytes(bytes);
206+
return bytes;
207+
}//NextBytes()
208+
159209
/// <summary>
160210
/// Fills the elements of a specified array of bytes with random numbers.
161211
/// </summary>
@@ -166,76 +216,148 @@ public override double NextDouble()
166216
public override void NextBytes(byte[] buffer)
167217
{
168218
var bufferLength = buffer.Length;
169-
if (bufferLength > BUFFERED_THRESHOLD)
170-
{
171-
_rng.GetBytes(buffer);
172-
return;
173-
}
219+
if (bufferLength == 0) return;
220+
if (bufferLength > CACHE_THRESHOLD) { _fillBufferWithRandomBytes(buffer); return; }
174221

175-
lock (lockObj)
222+
while (true)
176223
{
177-
if ((BUFFER_SIZE - _bufferPosition) < bufferLength)
224+
int currentByteCachePosition = Interlocked.Add(ref _byteCachePosition, bufferLength);
225+
if (currentByteCachePosition <= BYTE_CACHE_SIZE && currentByteCachePosition > 0)
178226
{
179-
_rng.GetBytes(_buffer);
180-
_bufferPosition = 0;
227+
Utils.BlockCopy(_byteCache, currentByteCachePosition - bufferLength, buffer, 0, bufferLength); return;
181228
}
182229

183-
Utils.BlockCopy(_buffer, _bufferPosition, buffer, 0, bufferLength);
184-
_bufferPosition += bufferLength;
185-
}
186-
}//NextBytes()
187-
188-
/// <summary>
189-
/// Returns a new count-sized byte array filled with random bytes.
190-
/// </summary>
191-
/// <param name="count">Array length.</param>
192-
/// <returns>Random byte array.</returns>
193-
public byte[] NextBytes(int count)
194-
{
195-
if (count < 0) throw new ArgumentOutOfRangeException("count", "count must be non-negative.");
196-
byte[] bytes = new byte[count];
197-
this.NextBytes(bytes);
198-
return bytes;
230+
lock (_byteCache)
231+
{
232+
currentByteCachePosition = _byteCachePosition; // atomic read
233+
if (currentByteCachePosition > (BYTE_CACHE_SIZE - bufferLength) || currentByteCachePosition <= 0)
234+
{
235+
_fillBufferWithRandomBytes(_byteCache);
236+
_byteCachePosition = bufferLength; // atomic write
237+
Utils.BlockCopy(_byteCache, 0, buffer, 0, bufferLength);
238+
return;
239+
}
240+
}// lock
241+
}// while(true)
199242
}//NextBytes()
200243

201244
/// <summary>
202245
/// Gets one random unsigned 32bit integer in a thread safe manner.
203246
/// </summary>
204247
uint GetRandomUInt()
205248
{
206-
uint rand;
207-
lock (lockObj)
249+
while (true)
208250
{
209-
if ((BUFFER_SIZE - _bufferPosition) < sizeof(uint))
210-
{
211-
_rng.GetBytes(_buffer);
212-
_bufferPosition = 0;
213-
}
251+
int currentByteCachePosition = Interlocked.Add(ref _byteCachePosition, sizeof(uint));
252+
if (currentByteCachePosition <= BYTE_CACHE_SIZE && currentByteCachePosition > 0)
253+
return BitConverter.ToUInt32(_byteCache, currentByteCachePosition - sizeof(uint));
214254

215-
rand = BitConverter.ToUInt32(_buffer, _bufferPosition);
216-
_bufferPosition += sizeof(uint);
217-
}
218-
return rand;
255+
lock (_byteCache)
256+
{
257+
currentByteCachePosition = _byteCachePosition; // atomic read
258+
if (currentByteCachePosition > (BYTE_CACHE_SIZE - sizeof(uint)) || currentByteCachePosition <= 0)
259+
{
260+
_fillBufferWithRandomBytes(_byteCache);
261+
_byteCachePosition = sizeof(uint); // atomic write
262+
return BitConverter.ToUInt32(_byteCache, 0);
263+
}
264+
}// lock
265+
}// while(true)
219266
}//GetRandomUInt()
220267

221268
/// <summary>
222269
/// Gets one random unsigned 64bit integer in a thread safe manner.
223270
/// </summary>
224271
ulong GetRandomULong()
225272
{
226-
ulong rand;
227-
lock (lockObj)
273+
while (true)
228274
{
229-
if ((BUFFER_SIZE - _bufferPosition) < sizeof(ulong))
230-
{
231-
_rng.GetBytes(_buffer);
232-
_bufferPosition = 0;
233-
}
275+
int currentByteCachePosition = Interlocked.Add(ref _byteCachePosition, sizeof(ulong));
276+
if (currentByteCachePosition <= BYTE_CACHE_SIZE && currentByteCachePosition > 0)
277+
return BitConverter.ToUInt64(_byteCache, currentByteCachePosition - sizeof(ulong));
234278

235-
rand = BitConverter.ToUInt64(_buffer, _bufferPosition);
236-
_bufferPosition += sizeof(ulong);
237-
}
238-
return rand;
279+
lock (_byteCache)
280+
{
281+
currentByteCachePosition = _byteCachePosition; // atomic read
282+
if (currentByteCachePosition > (BYTE_CACHE_SIZE - sizeof(ulong)) || currentByteCachePosition <= 0)
283+
{
284+
_fillBufferWithRandomBytes(_byteCache);
285+
_byteCachePosition = sizeof(ulong); // atomic write
286+
return BitConverter.ToUInt64(_byteCache, 0);
287+
}
288+
}// lock
289+
}// while(true)
239290
}//GetRandomULong()
240291
}//class CryptoRandom
292+
293+
#region BCrypt
294+
internal static class BCrypt
295+
{
296+
internal const string MS_PRIMITIVE_PROVIDER = "Microsoft Primitive Provider"; // MS_PRIMITIVE_PROVIDER -- https://msdn.microsoft.com/en-us/library/windows/desktop/aa375479(v=vs.85).aspx
297+
internal const string BCRYPT_RNG_ALGORITHM = "RNG"; // BCRYPT_RNG_ALGORITHM -- https://msdn.microsoft.com/en-us/library/windows/desktop/aa375534(v=vs.85).aspx
298+
299+
/// <summary>
300+
/// Open a handle to a BCrypt algorithm provider.
301+
/// </summary>
302+
internal static BCryptAlgorithmHandle OpenAlgorithm(string algorithm, string implementation)
303+
{
304+
Debug.Assert(!string.IsNullOrEmpty(algorithm), "!String.IsNullOrEmpty(algorithm)");
305+
Debug.Assert(!string.IsNullOrEmpty(implementation), "!String.IsNullOrEmpty(implementation)");
306+
307+
BCryptAlgorithmHandle algorithmHandle = null;
308+
ErrorCode error = DllImportedNativeMethods.BCryptOpenAlgorithmProvider(out algorithmHandle, algorithm, implementation, AlgorithmProviderOptions.None);
309+
if (error != ErrorCode.Success) throw new CryptographicException(error.ToString());
310+
return algorithmHandle;
311+
}
312+
313+
internal sealed class BCryptAlgorithmHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid
314+
{
315+
BCryptAlgorithmHandle() : base(ownsHandle: true) { }
316+
protected override bool ReleaseHandle()
317+
{
318+
return DllImportedNativeMethods.BCryptCloseAlgorithmProvider(handle, AlgorithmProviderOptions.None) == ErrorCode.Success;
319+
}
320+
}// class BCryptAlgorithmHandle
321+
322+
/// <summary>
323+
/// Result codes from BCrypt APIs.
324+
/// </summary>
325+
internal enum ErrorCode
326+
{
327+
Success = 0x00000000, // STATUS_SUCCESS
328+
AuthenticationTagMismatch = unchecked((int)0xC000A002), // STATUS_AUTH_TAG_MISMATCH
329+
BufferToSmall = unchecked((int)0xC0000023) // STATUS_BUFFER_TOO_SMALL
330+
}// enum ErrorCode
331+
332+
/// <summary>
333+
/// Flags for BCryptOpenAlgorithmProvider.
334+
/// </summary>
335+
[Flags] internal enum AlgorithmProviderOptions { None = 0x00000000 }
336+
337+
internal static class DllImportedNativeMethods
338+
{
339+
const string bcrypt_dll = "bcrypt.dll";
340+
341+
[DllImport(bcrypt_dll)]
342+
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa375458(v=vs.85).aspx
343+
internal static extern ErrorCode BCryptGenRandom(
344+
BCryptAlgorithmHandle hAlgorithm,
345+
[In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbBuffer,
346+
int cbBuffer,
347+
int dwFlags);
348+
349+
[DllImport(bcrypt_dll)]
350+
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa375479(v=vs.85).aspx
351+
internal static extern ErrorCode BCryptOpenAlgorithmProvider(
352+
[Out] out BCryptAlgorithmHandle phAlgorithm,
353+
[MarshalAs(UnmanagedType.LPWStr)] string pszAlgId,
354+
[MarshalAs(UnmanagedType.LPWStr)] string pszImplementation,
355+
AlgorithmProviderOptions dwFlags);
356+
357+
[DllImport(bcrypt_dll)]
358+
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa375377(v=vs.85).aspx
359+
internal static extern ErrorCode BCryptCloseAlgorithmProvider(IntPtr hAlgorithm, AlgorithmProviderOptions dwFlags);
360+
}// class DllImportedNativeMathods
361+
}// class BCrypt
362+
#endregion
241363
}//ns

EtM_CBC.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public static partial class EtM_CBC
2727

2828
public static int CalculateCiphertextLength(ArraySegment<byte> plaintext)
2929
{
30-
int finalBlockLength = plaintext.Count - plaintext.Count / AES_IV_LENGTH * AES_IV_LENGTH; ;
30+
int finalBlockLength = plaintext.Count - (plaintext.Count & (-Cipher.AesConstants.AES_BLOCK_SIZE));
3131
int paddingLength = AES_IV_LENGTH - finalBlockLength;
3232
return CONTEXT_BUFFER_LENGTH + plaintext.Count + paddingLength + MAC_LENGTH;
3333
}
@@ -48,9 +48,9 @@ static void ClearKeyMaterial()
4848

4949
public static void Encrypt(byte[] masterKey, ArraySegment<byte> plaintext, byte[] output, int outputOffset, ArraySegment<byte>? salt = null, uint counter = 1)
5050
{
51-
int fullBlockLength = plaintext.Count / AES_IV_LENGTH * AES_IV_LENGTH;
52-
int finalBlockLength = plaintext.Count - fullBlockLength;
53-
int paddingLength = AES_IV_LENGTH - finalBlockLength;
51+
int fullBlockLength = plaintext.Count & (-Cipher.AesConstants.AES_BLOCK_SIZE);
52+
int finalBlockLength = plaintext.Count % Cipher.AesConstants.AES_BLOCK_SIZE;
53+
int paddingLength = Cipher.AesConstants.AES_BLOCK_SIZE - finalBlockLength;
5454
int ciphertextLength = CONTEXT_BUFFER_LENGTH + plaintext.Count + paddingLength + MAC_LENGTH;
5555
if (output.Length - outputOffset < ciphertextLength) throw new ArgumentOutOfRangeException("output", "'output' array segment is not big enough for the ciphertext");
5656

@@ -90,10 +90,9 @@ public static void Encrypt(byte[] masterKey, ArraySegment<byte> plaintext, byte[
9090

9191
public static byte[] Encrypt(byte[] masterKey, ArraySegment<byte> plaintext, ArraySegment<byte>? salt = null, uint counter = 1)
9292
{
93-
int fullBlockLength = plaintext.Count / AES_IV_LENGTH * AES_IV_LENGTH;
94-
int finalBlockLength = plaintext.Count - fullBlockLength;
95-
int paddingLength = AES_IV_LENGTH - finalBlockLength;
96-
93+
int fullBlockLength = plaintext.Count & (-Cipher.AesConstants.AES_BLOCK_SIZE);
94+
int finalBlockLength = plaintext.Count % Cipher.AesConstants.AES_BLOCK_SIZE;
95+
int paddingLength = Cipher.AesConstants.AES_BLOCK_SIZE - finalBlockLength;
9796
int ciphertextLength = CONTEXT_BUFFER_LENGTH + plaintext.Count + paddingLength + MAC_LENGTH;
9897
byte[] buffer = new byte[ciphertextLength];
9998
EtM_CBC.Encrypt(masterKey: masterKey, plaintext: plaintext, output: buffer, outputOffset: 0, salt: salt, counter: counter);
@@ -104,7 +103,7 @@ public static byte[] Encrypt(byte[] masterKey, ArraySegment<byte> plaintext, Arr
104103
public static void Decrypt(byte[] masterKey, ArraySegment<byte> ciphertext, ref ArraySegment<byte>? outputSegment, ArraySegment<byte>? salt = null, uint counter = 1)
105104
{
106105
int cipherLength = ciphertext.Count - CONTEXT_BUFFER_LENGTH - MAC_LENGTH;
107-
if (cipherLength < AES_IV_LENGTH) { outputSegment = null; return; }
106+
if (cipherLength < Cipher.AesConstants.AES_BLOCK_SIZE) { outputSegment = null; return; }
108107
int fullBlockLength = cipherLength - AES_IV_LENGTH;
109108
byte[] finalBlock = null;
110109
try
@@ -148,7 +147,7 @@ public static void Decrypt(byte[] masterKey, ArraySegment<byte> ciphertext, ref
148147
public static byte[] Decrypt(byte[] masterKey, ArraySegment<byte> ciphertext, ArraySegment<byte>? salt = null, uint counter = 1)
149148
{
150149
int cipherLength = ciphertext.Count - CONTEXT_BUFFER_LENGTH - MAC_LENGTH;
151-
if (cipherLength < AES_IV_LENGTH) return null;
150+
if (cipherLength < Cipher.AesConstants.AES_BLOCK_SIZE) return null;
152151
try
153152
{
154153
Kdf.SP800_108_Ctr.DeriveKey(hmacFactory: _hmacFactory, key: masterKey, label: salt, context: new ArraySegment<byte>(ciphertext.Array, ciphertext.Offset, CONTEXT_BUFFER_LENGTH), derivedOutput: _sessionKey.Value.AsArraySegment(), counter: counter);
@@ -180,7 +179,7 @@ public static byte[] Decrypt(byte[] masterKey, ArraySegment<byte> ciphertext, Ar
180179
public static bool Authenticate(byte[] masterKey, ArraySegment<byte> ciphertext, ArraySegment<byte>? salt = null, uint counter = 1)
181180
{
182181
int cipherLength = ciphertext.Count - CONTEXT_BUFFER_LENGTH - MAC_LENGTH;
183-
if (cipherLength < AES_IV_LENGTH) return false;
182+
if (cipherLength < Cipher.AesConstants.AES_BLOCK_SIZE) return false;
184183
try
185184
{
186185
Kdf.SP800_108_Ctr.DeriveKey(hmacFactory: _hmacFactory, key: masterKey, label: salt, context: new ArraySegment<byte>(ciphertext.Array, ciphertext.Offset, CONTEXT_BUFFER_LENGTH), derivedOutput: _sessionKey.Value.AsArraySegment(), counter: counter);

Kdf/HKDF.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public override byte[] GetBytes(int countBytes)
4646
var okm = new byte[countBytes];
4747
if (k_unused > 0)
4848
{
49-
var min = Math.Min(k_unused, countBytes);
49+
var min = k_unused > countBytes ? countBytes : k_unused;//Math.Min(k_unused, countBytes);
5050
Utils.BlockCopy(k, hashLength - k_unused, okm, 0, min);
5151
countBytes -= min;
5252
k_unused -= min;

0 commit comments

Comments
 (0)