namespace Util
{
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
public enum CryptoAlgorithm { Rijndael };
///////////////////////////////////////////////////////////////////////////////
// SAMPLE: Illustrates symmetric key encryption and decryption using Rijndael
// algorithm. In addition to performing encryption, this sample
// explains how to add a salt value (randomly generated bytes) to
// plain text before the value is encrypted. This can help reduce the
// risk of dictionary attacks.
//
// To run this sample, create a new Visual C# project using the Console
// Application template and replace the contents of the Module1.vb file with
// the code below.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
//
// Copyright (C) 2003 Obviex(TM). All rights reserved.
// http://www.obviex.com/Resources/Samples.aspx
//
///
/// This class uses a symmetric key algorithm (Rijndael/AES) to encrypt and
/// decrypt data. As long as it is initialized with the same constructor
/// parameters, the class will use the same key. Before performing encryption,
/// the class can prepend random bytes to plain text and generate different
/// encrypted values from the same plain text, encryption key, initialization
/// vector, and other parameters. This class is thread-safe.
///
///
/// Be careful when performing encryption and decryption. There is a bug
/// ("feature"?) in .NET Framework, which causes corruption of encryptor/
/// decryptor if a cryptographic exception occurs during encryption/
/// decryption operation. To correct the problem, re-initialize the class
/// instance when a cryptographic exception occurs.
///
public class Crypto
{
#region Public Property
private CryptoAlgorithm _cryptomethod = CryptoAlgorithm.Rijndael;
public CryptoAlgorithm CryptoMethod
{
set { _cryptomethod = value; }
get { return _cryptomethod; }
}
#endregion
#region Private members
// If hashing algorithm is not specified, use SHA-1.
private static string DEFAULT_HASH_ALGORITHM = "SHA1";
// If key size is not specified, use the longest 256-bit key.
private static int DEFAULT_KEY_SIZE = 256;
// Do not allow salt to be longer than 255 bytes, because we have only
// 1 byte to store its length.
private static int MAX_ALLOWED_SALT_LEN = 255;
// Do not allow salt to be smaller than 4 bytes, because we use the first
// 4 bytes of salt to store its length.
private static int MIN_ALLOWED_SALT_LEN = 4;
// Random salt value will be between 4 and 8 bytes long.
private static int DEFAULT_MIN_SALT_LEN = MIN_ALLOWED_SALT_LEN;
private static int DEFAULT_MAX_SALT_LEN = 8;
// Use these members to save min and max salt lengths.
private int minSaltLen = -1;
private int maxSaltLen = -1;
// These members will be used to perform encryption and decryption.
private ICryptoTransform encryptor = null;
private ICryptoTransform decryptor = null;
#endregion
#region static
///
/// calcul le hash SHA256 d'une clé
///
///
///
public static string getHashSHA256(string phrase)
{
byte[] data = Encoding.UTF8.GetBytes(phrase);
using (HashAlgorithm sha = new SHA256Managed())
{
byte[] encryptedBytes = sha.TransformFinalBlock(data, 0, data.Length);
return Convert.ToBase64String(sha.Hash);
}
}
///
/// calcul le hash MD5 d'une clé
///
///
///
public static string getHashMD5(string phrase)
{
using (MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider())
{
byte[] result = md5.ComputeHash(Encoding.ASCII.GetBytes(phrase));
return Convert.ToBase64String(md5.Hash);
}
}
#endregion
#region Constructors
///
/// Use this constructor if you are planning to perform encryption/
/// decryption with 256-bit key, derived using 1 password iteration,
/// hashing without salt, no initialization vector, electronic codebook
/// (ECB) mode, SHA-1 hashing algorithm, and 4-to-8 byte long salt.
///
///
/// Passphrase from which a pseudo-random password will be derived.
/// The derived password will be used to generate the encryption key.
/// Passphrase can be any string. In this example we assume that the
/// passphrase is an ASCII string. Passphrase value must be kept in
/// secret.
///
///
/// This constructor is not recommended because it does not use
/// initialization vector and uses the ECB cipher mode, which is less
/// secure than the CBC mode.
///
public Crypto(string passPhrase)
:
this(passPhrase, null)
{
}
///
/// Use this constructor if you are planning to perform encryption/
/// decryption with 256-bit key, derived using 1 password iteration,
/// hashing without salt, cipher block chaining (CBC) mode, SHA-1
/// hashing algorithm, and 4-to-8 byte long salt.
///
///
/// Passphrase from which a pseudo-random password will be derived.
/// The derived password will be used to generate the encryption key.
/// Passphrase can be any string. In this example we assume that the
/// passphrase is an ASCII string. Passphrase value must be kept in
/// secret.
///
///
/// Initialization vector (IV). This value is required to encrypt the
/// first block of plaintext data. For RijndaelManaged class IV must be
/// exactly 16 ASCII characters long. IV value does not have to be kept
/// in secret.
///
public Crypto(string passPhrase,
string initVector)
:
this(passPhrase, initVector, -1)
{
}
///
/// Use this constructor if you are planning to perform encryption/
/// decryption with 256-bit key, derived using 1 password iteration,
/// hashing without salt, cipher block chaining (CBC) mode, SHA-1
/// hashing algorithm, and 0-to-8 byte long salt.
///
///
/// Passphrase from which a pseudo-random password will be derived.
/// The derived password will be used to generate the encryption key
/// Passphrase can be any string. In this example we assume that the
/// passphrase is an ASCII string. Passphrase value must be kept in
/// secret.
///
///
/// Initialization vector (IV). This value is required to encrypt the
/// first block of plaintext data. For RijndaelManaged class IV must be
/// exactly 16 ASCII characters long. IV value does not have to be kept
/// in secret.
///
///
/// Min size (in bytes) of randomly generated salt which will be added at
/// the beginning of plain text before encryption is performed. When this
/// value is less than 4, the default min value will be used (currently 4
/// bytes).
///
public Crypto(string passPhrase,
string initVector,
int minSaltLen)
:
this(passPhrase, initVector, minSaltLen, -1)
{
}
///
/// Use this constructor if you are planning to perform encryption/
/// decryption with 256-bit key, derived using 1 password iteration,
/// hashing without salt, cipher block chaining (CBC) mode, SHA-1
/// hashing algorithm. Use the minSaltLen and maxSaltLen parameters to
/// specify the size of randomly generated salt.
///
///
/// Passphrase from which a pseudo-random password will be derived.
/// The derived password will be used to generate the encryption key.
/// Passphrase can be any string. In this example we assume that the
/// passphrase is an ASCII string. Passphrase value must be kept in
/// secret.
///
///
/// Initialization vector (IV). This value is required to encrypt the
/// first block of plaintext data. For RijndaelManaged class IV must be
/// exactly 16 ASCII characters long. IV value does not have to be kept
/// in secret.
///
///
/// Min size (in bytes) of randomly generated salt which will be added at
/// the beginning of plain text before encryption is performed. When this
/// value is less than 4, the default min value will be used (currently 4
/// bytes).
///
///
/// Max size (in bytes) of randomly generated salt which will be added at
/// the beginning of plain text before encryption is performed. When this
/// value is negative or greater than 255, the default max value will be
/// used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
/// than the specified min value (which can be adjusted to default value),
/// salt will not be used and plain text value will be encrypted as is.
/// In this case, salt will not be processed during decryption either.
///
public Crypto(string passPhrase,
string initVector,
int minSaltLen,
int maxSaltLen)
:
this(passPhrase, initVector, minSaltLen, maxSaltLen, -1)
{
}
///
/// Use this constructor if you are planning to perform encryption/
/// decryption using the key derived from 1 password iteration,
/// hashing without salt, cipher block chaining (CBC) mode, and
/// SHA-1 hashing algorithm.
///
///
/// Passphrase from which a pseudo-random password will be derived.
/// The derived password will be used to generate the encryption key.
/// Passphrase can be any string. In this example we assume that the
/// passphrase is an ASCII string. Passphrase value must be kept in
/// secret.
///
///
/// Initialization vector (IV). This value is required to encrypt the
/// first block of plaintext data. For RijndaelManaged class IV must be
/// exactly 16 ASCII characters long. IV value does not have to be kept
/// in secret.
///
///
/// Min size (in bytes) of randomly generated salt which will be added at
/// the beginning of plain text before encryption is performed. When this
/// value is less than 4, the default min value will be used (currently 4
/// bytes).
///
///
/// Max size (in bytes) of randomly generated salt which will be added at
/// the beginning of plain text before encryption is performed. When this
/// value is negative or greater than 255, the default max value will be
/// used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
/// than the specified min value (which can be adjusted to default value),
/// salt will not be used and plain text value will be encrypted as is.
/// In this case, salt will not be processed during decryption either.
///
///
/// Size of symmetric key (in bits): 128, 192, or 256.
///
public Crypto(string passPhrase,
string initVector,
int minSaltLen,
int maxSaltLen,
int keySize)
:
this(passPhrase, initVector, minSaltLen, maxSaltLen, keySize, null)
{
}
///
/// Use this constructor if you are planning to perform encryption/
/// decryption using the key derived from 1 password iteration, hashing
/// without salt, and cipher block chaining (CBC) mode.
///
///
/// Passphrase from which a pseudo-random password will be derived.
/// The derived password will be used to generate the encryption key.
/// Passphrase can be any string. In this example we assume that the
/// passphrase is an ASCII string. Passphrase value must be kept in
/// secret.
///
///
/// Initialization vector (IV). This value is required to encrypt the
/// first block of plaintext data. For RijndaelManaged class IV must be
/// exactly 16 ASCII characters long. IV value does not have to be kept
/// in secret.
///
///
/// Min size (in bytes) of randomly generated salt which will be added at
/// the beginning of plain text before encryption is performed. When this
/// value is less than 4, the default min value will be used (currently 4
/// bytes).
///
///
/// Max size (in bytes) of randomly generated salt which will be added at
/// the beginning of plain text before encryption is performed. When this
/// value is negative or greater than 255, the default max value will be
/// used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
/// than the specified min value (which can be adjusted to default value),
/// salt will not be used and plain text value will be encrypted as is.
/// In this case, salt will not be processed during decryption either.
///
///
/// Size of symmetric key (in bits): 128, 192, or 256.
///
///
/// Hashing algorithm: "MD5" or "SHA1". SHA1 is recommended.
///
public Crypto(string passPhrase,
string initVector,
int minSaltLen,
int maxSaltLen,
int keySize,
string hashAlgorithm)
:
this(passPhrase, initVector, minSaltLen, maxSaltLen, keySize,
hashAlgorithm, null)
{
}
///
/// Use this constructor if you are planning to perform encryption/
/// decryption using the key derived from 1 password iteration, and
/// cipher block chaining (CBC) mode.
///
///
/// Passphrase from which a pseudo-random password will be derived.
/// The derived password will be used to generate the encryption key.
/// Passphrase can be any string. In this example we assume that the
/// passphrase is an ASCII string. Passphrase value must be kept in
/// secret.
///
///
/// Initialization vector (IV). This value is required to encrypt the
/// first block of plaintext data. For RijndaelManaged class IV must be
/// exactly 16 ASCII characters long. IV value does not have to be kept
/// in secret.
///
///
/// Min size (in bytes) of randomly generated salt which will be added at
/// the beginning of plain text before encryption is performed. When this
/// value is less than 4, the default min value will be used (currently 4
/// bytes).
///
///
/// Max size (in bytes) of randomly generated salt which will be added at
/// the beginning of plain text before encryption is performed. When this
/// value is negative or greater than 255, the default max value will be
/// used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
/// than the specified min value (which can be adjusted to default value),
/// salt will not be used and plain text value will be encrypted as is.
/// In this case, salt will not be processed during decryption either.
///
///
/// Size of symmetric key (in bits): 128, 192, or 256.
///
///
/// Hashing algorithm: "MD5" or "SHA1". SHA1 is recommended.
///
///
/// Salt value used for password hashing during key generation. This is
/// not the same as the salt we will use during encryption. This parameter
/// can be any string.
///
public Crypto(string passPhrase,
string initVector,
int minSaltLen,
int maxSaltLen,
int keySize,
string hashAlgorithm,
string saltValue)
:
this(passPhrase, initVector, minSaltLen, maxSaltLen, keySize,
hashAlgorithm, saltValue, 1)
{
}
///
/// Use this constructor if you are planning to perform encryption/
/// decryption with the key derived from the explicitly specified
/// parameters.
///
///
/// Passphrase from which a pseudo-random password will be derived.
/// The derived password will be used to generate the encryption key
/// Passphrase can be any string. In this example we assume that the
/// passphrase is an ASCII string. Passphrase value must be kept in
/// secret.
///
///
/// Initialization vector (IV). This value is required to encrypt the
/// first block of plaintext data. For RijndaelManaged class IV must be
/// exactly 16 ASCII characters long. IV value does not have to be kept
/// in secret.
///
///
/// Min size (in bytes) of randomly generated salt which will be added at
/// the beginning of plain text before encryption is performed. When this
/// value is less than 4, the default min value will be used (currently 4
/// bytes).
///
///
/// Max size (in bytes) of randomly generated salt which will be added at
/// the beginning of plain text before encryption is performed. When this
/// value is negative or greater than 255, the default max value will be
/// used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
/// than the specified min value (which can be adjusted to default value),
/// salt will not be used and plain text value will be encrypted as is.
/// In this case, salt will not be processed during decryption either.
///
///
/// Size of symmetric key (in bits): 128, 192, or 256.
///
///
/// Hashing algorithm: "MD5" or "SHA1". SHA1 is recommended.
///
///
/// Salt value used for password hashing during key generation. This is
/// not the same as the salt we will use during encryption. This parameter
/// can be any string.
///
///
/// Number of iterations used to hash password. More iterations are
/// considered more secure but may take longer.
///
public Crypto(string passPhrase,
string initVector,
int minSaltLen,
int maxSaltLen,
int keySize,
string hashAlgorithm,
string saltValue,
int passwordIterations)
{
// Save min salt length; set it to default if invalid value is passed.
if (minSaltLen < MIN_ALLOWED_SALT_LEN)
this.minSaltLen = DEFAULT_MIN_SALT_LEN;
else
this.minSaltLen = minSaltLen;
// Save max salt length; set it to default if invalid value is passed.
if (maxSaltLen < 0 || maxSaltLen > MAX_ALLOWED_SALT_LEN)
this.maxSaltLen = DEFAULT_MAX_SALT_LEN;
else
this.maxSaltLen = maxSaltLen;
// Set the size of cryptographic key.
if (keySize <= 0)
keySize = DEFAULT_KEY_SIZE;
// Set the name of algorithm. Make sure it is in UPPER CASE and does
// not use dashes, e.g. change "sha-1" to "SHA1".
if (hashAlgorithm == null)
hashAlgorithm = DEFAULT_HASH_ALGORITHM;
else
hashAlgorithm = hashAlgorithm.ToUpper().Replace("-", "");
// Initialization vector converted to a byte array.
byte[] initVectorBytes = null;
// Salt used for password hashing (to generate the key, not during
// encryption) converted to a byte array.
byte[] saltValueBytes = null;
// Get bytes of initialization vector.
if (initVector == null)
initVectorBytes = new byte[0];
else
initVectorBytes = Encoding.ASCII.GetBytes(initVector);
// Get bytes of salt (used in hashing).
if (saltValue == null)
saltValueBytes = new byte[0];
else
saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
// Generate password, which will be used to derive the key.
PasswordDeriveBytes password = new PasswordDeriveBytes(
passPhrase,
saltValueBytes,
hashAlgorithm,
passwordIterations);
// Convert key to a byte array adjusting the size from bits to bytes.
byte[] keyBytes = password.GetBytes(keySize / 8);
// Initialize Rijndael key object.
RijndaelManaged symmetricKey = new RijndaelManaged();
// If we do not have initialization vector, we cannot use the CBC mode.
// The only alternative is the ECB mode (which is not as good).
if (initVectorBytes.Length == 0)
symmetricKey.Mode = CipherMode.ECB;
else
symmetricKey.Mode = CipherMode.CBC;
// Create encryptor and decryptor, which we will use for cryptographic
// operations.
encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);
}
#endregion
#region Encryption routines
///
/// Encrypts a string value generating a base64-encoded string.
///
///
/// Plain text string to be encrypted.
///
///
/// Cipher text formatted as a base64-encoded string.
///
public string Encrypt(string plainText)
{
return Encrypt(Encoding.UTF8.GetBytes(plainText));
}
///
/// Encrypts a byte array generating a base64-encoded string.
///
///
/// Plain text bytes to be encrypted.
///
///
/// Cipher text formatted as a base64-encoded string.
///
public string Encrypt(byte[] plainTextBytes)
{
return Convert.ToBase64String(EncryptToBytes(plainTextBytes));
}
///
/// Encrypts a string value generating a byte array of cipher text.
///
///
/// Plain text string to be encrypted.
///
///
/// Cipher text formatted as a byte array.
///
public byte[] EncryptToBytes(string plainText)
{
return EncryptToBytes(Encoding.UTF8.GetBytes(plainText));
}
///
/// Encrypts a byte array generating a byte array of cipher text.
///
///
/// Plain text bytes to be encrypted.
///
///
/// Cipher text formatted as a byte array.
///
public byte[] EncryptToBytes(byte[] plainTextBytes)
{
// Add salt at the beginning of the plain text bytes (if needed).
byte[] plainTextBytesWithSalt = AddSalt(plainTextBytes);
// Encryption will be performed using memory stream.
MemoryStream memoryStream = new MemoryStream();
// Let's make cryptographic operations thread-safe.
lock (this)
{
// To perform encryption, we must use the Write mode.
CryptoStream cryptoStream = new CryptoStream(
memoryStream,
encryptor,
CryptoStreamMode.Write);
// Start encrypting data.
cryptoStream.Write(plainTextBytesWithSalt,
0,
plainTextBytesWithSalt.Length);
// Finish the encryption operation.
cryptoStream.FlushFinalBlock();
// Move encrypted data from memory into a byte array.
byte[] cipherTextBytes = memoryStream.ToArray();
// Close memory streams.
memoryStream.Close();
cryptoStream.Close();
// Return encrypted data.
return cipherTextBytes;
}
}
#endregion
#region Decryption routines
///
/// Decrypts a base64-encoded cipher text value generating a string result.
///
///
/// Base64-encoded cipher text string to be decrypted.
///
///
/// Decrypted string value.
///
public string Decrypt(string cipherText)
{
return Decrypt(Convert.FromBase64String(cipherText));
}
///
/// Decrypts a byte array containing cipher text value and generates a
/// string result.
///
///
/// Byte array containing encrypted data.
///
///
/// Decrypted string value.
///
public string Decrypt(byte[] cipherTextBytes)
{
return Encoding.UTF8.GetString(DecryptToBytes(cipherTextBytes));
}
///
/// Decrypts a base64-encoded cipher text value and generates a byte array
/// of plain text data.
///
///
/// Base64-encoded cipher text string to be decrypted.
///
///
/// Byte array containing decrypted value.
///
public byte[] DecryptToBytes(string cipherText)
{
return DecryptToBytes(Convert.FromBase64String(cipherText));
}
///
/// Decrypts a base64-encoded cipher text value and generates a byte array
/// of plain text data.
///
///
/// Byte array containing encrypted data.
///
///
/// Byte array containing decrypted value.
///
public byte[] DecryptToBytes(byte[] cipherTextBytes)
{
byte[] decryptedBytes = null;
byte[] plainTextBytes = null;
int decryptedByteCount = 0;
int saltLen = 0;
MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
// Since we do not know how big decrypted value will be, use the same
// size as cipher text. Cipher text is always longer than plain text
// (in block cipher encryption), so we will just use the number of
// decrypted data byte after we know how big it is.
decryptedBytes = new byte[cipherTextBytes.Length];
// Let's make cryptographic operations thread-safe.
lock (this)
{
// To perform decryption, we must use the Read mode.
CryptoStream cryptoStream = new CryptoStream(
memoryStream,
decryptor,
CryptoStreamMode.Read);
// Decrypting data and get the count of plain text bytes.
decryptedByteCount = cryptoStream.Read(decryptedBytes,
0,
decryptedBytes.Length);
// Release memory.
memoryStream.Close();
cryptoStream.Close();
}
// If we are using salt, get its length from the first 4 bytes of plain
// text data.
if (maxSaltLen > 0 && maxSaltLen >= minSaltLen)
{
saltLen = (decryptedBytes[0] & 0x03) |
(decryptedBytes[1] & 0x0c) |
(decryptedBytes[2] & 0x30) |
(decryptedBytes[3] & 0xc0);
}
// Allocate the byte array to hold the original plain text (without salt).
plainTextBytes = new byte[decryptedByteCount - saltLen];
// Copy original plain text discarding the salt value if needed.
Array.Copy(decryptedBytes, saltLen, plainTextBytes,
0, decryptedByteCount - saltLen);
// Return original plain text value.
return plainTextBytes;
}
#endregion
#region Helper functions
///
/// Adds an array of randomly generated bytes at the beginning of the
/// array holding original plain text value.
///
///
/// Byte array containing original plain text value.
///
///
/// Either original array of plain text bytes (if salt is not used) or a
/// modified array containing a randomly generated salt added at the
/// beginning of the plain text bytes.
///
private byte[] AddSalt(byte[] plainTextBytes)
{
// The max salt value of 0 (zero) indicates that we should not use
// salt. Also do not use salt if the max salt value is smaller than
// the min value.
if (maxSaltLen == 0 || maxSaltLen < minSaltLen)
return plainTextBytes;
// Generate the salt.
byte[] saltBytes = GenerateSalt();
// Allocate array which will hold salt and plain text bytes.
byte[] plainTextBytesWithSalt = new byte[plainTextBytes.Length +
saltBytes.Length];
// First, copy salt bytes.
Array.Copy(saltBytes, plainTextBytesWithSalt, saltBytes.Length);
// Append plain text bytes to the salt value.
Array.Copy(plainTextBytes, 0,
plainTextBytesWithSalt, saltBytes.Length,
plainTextBytes.Length);
return plainTextBytesWithSalt;
}
///
/// Generates an array holding cryptographically strong bytes.
///
///
/// Array of randomly generated bytes.
///
///
/// Salt size will be defined at random or exactly as specified by the
/// minSlatLen and maxSaltLen parameters passed to the object constructor.
/// The first four bytes of the salt array will contain the salt length
/// split into four two-bit pieces.
///
private byte[] GenerateSalt()
{
// We don't have the length, yet.
int saltLen = 0;
// If min and max salt values are the same, it should not be random.
if (minSaltLen == maxSaltLen)
saltLen = minSaltLen;
// Use random number generator to calculate salt length.
else
saltLen = GenerateRandomNumber(minSaltLen, maxSaltLen);
// Allocate byte array to hold our salt.
byte[] salt = new byte[saltLen];
// Populate salt with cryptographically strong bytes.
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetNonZeroBytes(salt);
// Split salt length (always one byte) into four two-bit pieces and
// store these pieces in the first four bytes of the salt array.
salt[0] = (byte)((salt[0] & 0xfc) | (saltLen & 0x03));
salt[1] = (byte)((salt[1] & 0xf3) | (saltLen & 0x0c));
salt[2] = (byte)((salt[2] & 0xcf) | (saltLen & 0x30));
salt[3] = (byte)((salt[3] & 0x3f) | (saltLen & 0xc0));
return salt;
}
///
/// Generates random integer.
///
///
/// Min value (inclusive).
///
///
/// Max value (inclusive).
///
///
/// Random integer value between the min and max values (inclusive).
///
///
/// This methods overcomes the limitations of .NET Framework's Random
/// class, which - when initialized multiple times within a very short
/// period of time - can generate the same "random" number.
///
private int GenerateRandomNumber(int minValue, int maxValue)
{
// We will make up an integer seed from 4 bytes of this array.
byte[] randomBytes = new byte[4];
// Generate 4 random bytes.
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(randomBytes);
// Convert four random bytes into a positive integer value.
int seed = ((randomBytes[0] & 0x7f) << 24) |
(randomBytes[1] << 16) |
(randomBytes[2] << 8) |
(randomBytes[3]);
// Now, this looks more like real randomization.
Random random = new Random(seed);
// Calculate a random number.
return random.Next(minValue, maxValue + 1);
}
#endregion
}
}