Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CSharpNotesForProfessionals.pdf
Скачиваний:
57
Добавлен:
20.05.2023
Размер:
6.12 Mб
Скачать

throw new ArgumentException(String.Format("Must have a password of at least {0} characters!", MinPasswordLength), "password");

if (encryptedMessage == null || encryptedMessage.Length == 0)

throw new ArgumentException("Encrypted Message Required!", "encryptedMessage");

var generator = new Pkcs5S2ParametersGenerator();

//Grab Salt from Payload

var salt = new byte[SaltBitSize / 8];

Array.Copy(encryptedMessage, nonSecretPayloadLength, salt, 0, salt.Length);

generator.Init( PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()), salt,

Iterations);

//Generate Key

var key = (KeyParameter)generator.GenerateDerivedMacParameters(KeyBitSize);

return SimpleDecrypt(encryptedMessage, key.GetKey(), salt.Length + nonSecretPayloadLength);

}

}

}

Section 150.2: Introduction to Symmetric and Asymmetric Encryption

You can improve the security for data transit or storing by implementing encrypting techniques. Basically there are two approaches when using System.Security.Cryptography: symmetric and asymmetric.

Symmetric Encryption

This method uses a private key in order to perform the data transformation.

Pros:

Symmetric algorithms consume less resources and are faster than asymmetric ones.

The amount of data you can encrypt is unlimited.

Cons:

Encryption and decryption use the same key. Someone will be able to decrypt your data if the key is compromised.

You could end up with many di erent secret keys to manage if you choose to use a di erent secret key for di erent data.

Under System.Security.Cryptography you have di erent classes that perform symmetric encryption, they are known as block ciphers:

AesManaged (AES algorithm).

AesCryptoServiceProvider (AES algorithm FIPS 140-2 complaint).

DESCryptoServiceProvider (DES algorithm).

RC2CryptoServiceProvider (Rivest Cipher 2 algorithm).

RijndaelManaged (AES algorithm). Note: RijndaelManaged is not FIPS-197 complaint.

TripleDES (TripleDES algorithm).

GoalKicker.com – C# Notes for Professionals

728

Asymmetric Encryption

This method uses a combination of public and private keys in order to perform the data transformation.

Pros:

It uses larger keys than symmetric algorithms, thus they are less susceptible to being cracked by using brute force.

It is easier to guarantee who is able to encrypt and decrypt the data because it relies on two keys (public and private).

Cons:

There is a limit on the amount of data that you can encrypt. The limit is di erent for each algorithm and is typically proportional with the key size of the algorithm. For example, an RSACryptoServiceProvider object with a key length of 1,024 bits can only encrypt a message that is smaller than 128 bytes.

Asymmetric algorithms are very slow in comparison to symmetric algorithms.

Under System.Security.Cryptography you have access to di erent classes that perform asymmetric encryption:

DSACryptoServiceProvider (Digital Signature Algorithm algorithm)

RSACryptoServiceProvider (RSA Algorithm algorithm)

Section 150.3: Simple Symmetric File Encryption

The following code sample demonstrates a quick and easy means of encrypting and decrypting files using the AES symmetric encryption algorithm.

The code randomly generates the Salt and Initialization Vectors each time a file is encrypted, meaning that encrypting the same file with the same password will always lead to di erent output. The salt and IV are written to the output file so that only the password is required to decrypt it.

public static void ProcessFile(string inputPath, string password, bool encryptMode, string outputPath)

{

using (var cypher = new AesManaged())

using (var fsIn = new FileStream(inputPath, FileMode.Open)) using (var fsOut = new FileStream(outputPath, FileMode.Create))

{

const int saltLength = 256;

var salt = new byte[saltLength];

var iv = new byte[cypher.BlockSize / 8];

if (encryptMode)

{

// Generate random salt and IV, then write them to file using (var rng = new RNGCryptoServiceProvider())

{

rng.GetBytes(salt); rng.GetBytes(iv);

}

fsOut.Write(salt, 0, salt.Length); fsOut.Write(iv, 0, iv.Length);

}

else

{

// Read the salt and IV from the file fsIn.Read(salt, 0, saltLength);

GoalKicker.com – C# Notes for Professionals

729

fsIn.Read(iv, 0, iv.Length);

}

//Generate a secure password, based on the password and salt provided var pdb = new Rfc2898DeriveBytes(password, salt);

var key = pdb.GetBytes(cypher.KeySize / 8);

//Encrypt or decrypt the file

using (var cryptoTransform = encryptMode ? cypher.CreateEncryptor(key, iv)

: cypher.CreateDecryptor(key, iv))

using (var cs = new CryptoStream(fsOut, cryptoTransform, CryptoStreamMode.Write))

{

fsIn.CopyTo(cs);

}

}

}

Section 150.4: Cryptographically Secure Random Data

There are times when the framework's Random() class may not be considered random enough, given that it is based on a psuedo-random number generator. The framework's Crypto classes do, however, provide something more robust in the form of RNGCryptoServiceProvider.

The following code samples demonstrate how to generate Cryptographically Secure byte arrays, strings and numbers.

Random Byte Array

public static byte[] GenerateRandomData(int length)

{

var rnd = new byte[length];

using (var rng = new RNGCryptoServiceProvider()) rng.GetBytes(rnd);

return rnd;

}

Random Integer (with even distribution)

public static int GenerateRandomInt(int minVal=0, int maxVal=100)

{

var rnd = new byte[4];

using (var rng = new RNGCryptoServiceProvider()) rng.GetBytes(rnd);

var i = Math.Abs(BitConverter.ToInt32(rnd, 0));

return Convert.ToInt32(i % (maxVal - minVal + 1) + minVal);

}

Random String

public static string GenerateRandomString(int length, string allowableChars=null)

{

if (string.IsNullOrEmpty(allowableChars)) allowableChars = @"ABCDEFGHIJKLMNOPQRSTUVWXYZ";

// Generate random data

var rnd = new byte[length];

using (var rng = new RNGCryptoServiceProvider()) rng.GetBytes(rnd);

GoalKicker.com – C# Notes for Professionals

730

// Generate the output string

var allowable = allowableChars.ToCharArray(); var l = allowable.Length;

var chars = new char[length]; for (var i = 0; i < length; i++)

chars[i] = allowable[rnd[i] % l];

return new string(chars);

}

Section 150.5: Password Hashing

Passwords should never be stored as plain text! They should be hashed with a randomly generated salt (to defend against rainbow table attacks) using a slow password hashing algorithm. A high number of iterations (> 10k) can be used to slow down brute force attacks. A delay of ~100ms is acceptable to a user logging in, but makes breaking a long password di cult. When choosing a number of iterations you should use the maximum tolerable value for your application and increase it as computer performance improves. You will also need to consider stopping repeated requests which could be used as a DoS attack.

When hashing for the first time a salt can be generated for you, the resulting hash and salt can then be stored to a file.

private void firstHash(string userName, string userPassword, int numberOfItterations)

{

Rfc2898DeriveBytes PBKDF2 = new Rfc2898DeriveBytes(userPassword, 8, numberOfItterations);

//Hash the password with a 8 byte salt

 

byte[] hashedPassword = PBKDF2.GetBytes(20);

//Returns a 20 byte hash

byte[] salt = PBKDF2.Salt;

writeHashToFile(userName, hashedPassword, salt, numberOfItterations); //Store the hashed password with the salt and number of itterations to check against future password entries

}

Checking an existing users password, read their hash and salt from a file and compare to the hash of the entered password

private bool checkPassword(string userName, string userPassword, int numberOfItterations)

{

byte[] usersHash = getUserHashFromFile(userName); byte[] userSalt = getUserSaltFromFile(userName);

Rfc2898DeriveBytes PBKDF2 = new Rfc2898DeriveBytes(userPassword, userSalt,

numberOfItterations);

//Hash the password with the users salt

 

byte[] hashedPassword = PBKDF2.GetBytes(20);

//Returns a 20 byte hash

bool passwordsMach = comparePasswords(usersHash, hashedPassword);

//Compares byte arrays

return passwordsMach;

 

 

 

}

 

 

 

Section 150.6: Fast Asymmetric File Encryption

Asymmetric encryption is often regarded as preferable to Symmetric encryption for transferring messages to other parties. This is mainly because it negates many of the risks related to the exchange of a shared key and ensures that whilst anyone with the public key can encrypt a message for the intended recipient, only that recipient can decrypt it. Unfortunately the major down-side of asymmetric encryption algorithms is that they are significantly slower than their symmetric cousins. As such the asymmetric encryption of files, especially large ones, can often be a very computationally intensive process.

In order to provide both security AND performance, a hybrid approach can be taken. This entails the

GoalKicker.com – C# Notes for Professionals

731

cryptographically random generation of a key and initialization vector for Symmetric encryption. These values are then encrypted using an Asymmetric algorithm and written to the output file, before being used to encrypt the source data Symmetrically and appending it to the output.

This approach provides a high degree of both performance and security, in that the data is encrypted using a symmetric algorithm (fast) and the key and iv, both randomly generated (secure) are encrypted by an asymmetric algorithm (secure). It also has the added advantage that the same payload encrypted on di erent occasions will have very di erent cyphertext, because the symmetric keys are randomly generated each time.

The following class demonstrates asymmetric encryption of strings and byte arrays, as well as hybrid file encryption.

public static class AsymmetricProvider

{

#region Key Generation public class KeyPair

{

public string PublicKey { get; set; } public string PrivateKey { get; set; }

}

public static KeyPair GenerateNewKeyPair(int keySize = 4096)

{

// KeySize is measured in bits. 1024 is the default, 2048 is better, 4096 is more robust but takes a fair bit longer to generate.

using (var rsa = new RSACryptoServiceProvider(keySize))

{

return new KeyPair {PublicKey = rsa.ToXmlString(false), PrivateKey = rsa.ToXmlString(true)};

}

}

#endregion

#region Asymmetric Data Encryption and Decryption

public static byte[] EncryptData(byte[] data, string publicKey)

{

using (var asymmetricProvider = new RSACryptoServiceProvider())

{

asymmetricProvider.FromXmlString(publicKey); return asymmetricProvider.Encrypt(data, true);

}

}

public static byte[] DecryptData(byte[] data, string publicKey)

{

using (var asymmetricProvider = new RSACryptoServiceProvider())

{

asymmetricProvider.FromXmlString(publicKey); if (asymmetricProvider.PublicOnly)

throw new Exception("The key provided is a public key and does not contain the private key elements required for decryption");

return asymmetricProvider.Decrypt(data, true);

}

}

public static string EncryptString(string value, string publicKey)

{

return Convert.ToBase64String(EncryptData(Encoding.UTF8.GetBytes(value), publicKey));

GoalKicker.com – C# Notes for Professionals

732

}

public static string DecryptString(string value, string privateKey)

{

return Encoding.UTF8.GetString(EncryptData(Convert.FromBase64String(value), privateKey));

}

#endregion

#region Hybrid File Encryption and Decription

public static void EncryptFile(string inputFilePath, string outputFilePath, string publicKey)

{

using (var symmetricCypher = new AesManaged())

{

//Generate random key and IV for symmetric encryption var key = new byte[symmetricCypher.KeySize / 8];

var iv = new byte[symmetricCypher.BlockSize / 8]; using (var rng = new RNGCryptoServiceProvider())

{

rng.GetBytes(key); rng.GetBytes(iv);

}

//Encrypt the symmetric key and IV

var buf = new byte[key.Length + iv.Length]; Array.Copy(key, buf, key.Length); Array.Copy(iv, 0, buf, key.Length, iv.Length); buf = EncryptData(buf, publicKey);

var bufLen = BitConverter.GetBytes(buf.Length);

// Symmetrically encrypt the data and write it to the file, along with the encrypted key

and iv

using (var cypherKey = symmetricCypher.CreateEncryptor(key, iv)) using (var fsIn = new FileStream(inputFilePath, FileMode.Open)) using (var fsOut = new FileStream(outputFilePath, FileMode.Create))

using (var cs = new CryptoStream(fsOut, cypherKey, CryptoStreamMode.Write))

{

fsOut.Write(bufLen,0, bufLen.Length); fsOut.Write(buf, 0, buf.Length); fsIn.CopyTo(cs);

}

}

}

public static void DecryptFile(string inputFilePath, string outputFilePath, string privateKey)

{

using (var symmetricCypher = new AesManaged())

using (var fsIn = new FileStream(inputFilePath, FileMode.Open))

{

//Determine the length of the encrypted key and IV var buf = new byte[sizeof(int)];

fsIn.Read(buf, 0, buf.Length);

var bufLen = BitConverter.ToInt32(buf, 0);

//Read the encrypted key and IV data from the file and decrypt using the asymmetric

algorithm

buf = new byte[bufLen]; fsIn.Read(buf, 0, buf.Length);

buf = DecryptData(buf, privateKey);

GoalKicker.com – C# Notes for Professionals

733

var key = new byte[symmetricCypher.KeySize / 8]; var iv = new byte[symmetricCypher.BlockSize / 8]; Array.Copy(buf, key, key.Length); Array.Copy(buf, key.Length, iv, 0, iv.Length);

// Decript the file data using the symmetric algorithm

using (var cypherKey = symmetricCypher.CreateDecryptor(key, iv)) using (var fsOut = new FileStream(outputFilePath, FileMode.Create))

using (var cs = new CryptoStream(fsOut, cypherKey, CryptoStreamMode.Write))

{

fsIn.CopyTo(cs);

}

}

}

#endregion

#region Key Storage

public static void WritePublicKey(string publicKeyFilePath, string publicKey)

{

File.WriteAllText(publicKeyFilePath, publicKey);

}

public static string ReadPublicKey(string publicKeyFilePath)

{

return File.ReadAllText(publicKeyFilePath);

}

private const string SymmetricSalt = "Stack_Overflow!"; // Change me!

public static string ReadPrivateKey(string privateKeyFilePath, string password)

{

var salt = Encoding.UTF8.GetBytes(SymmetricSalt);

var cypherText = File.ReadAllBytes(privateKeyFilePath);

using (var cypher = new AesManaged())

{

var pdb = new Rfc2898DeriveBytes(password, salt); var key = pdb.GetBytes(cypher.KeySize / 8);

var iv = pdb.GetBytes(cypher.BlockSize / 8);

using (var decryptor = cypher.CreateDecryptor(key, iv)) using (var msDecrypt = new MemoryStream(cypherText))

using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) using (var srDecrypt = new StreamReader(csDecrypt))

{

return srDecrypt.ReadToEnd();

}

}

}

public static void WritePrivateKey(string privateKeyFilePath, string privateKey, string password)

{

var salt = Encoding.UTF8.GetBytes(SymmetricSalt); using (var cypher = new AesManaged())

{

var pdb = new Rfc2898DeriveBytes(password, salt); var key = pdb.GetBytes(cypher.KeySize / 8);

var iv = pdb.GetBytes(cypher.BlockSize / 8);

using (var encryptor = cypher.CreateEncryptor(key, iv))

GoalKicker.com – C# Notes for Professionals

734

using (var fsEncrypt = new FileStream(privateKeyFilePath, FileMode.Create))

using (var csEncrypt = new CryptoStream(fsEncrypt, encryptor, CryptoStreamMode.Write)) using (var swEncrypt = new StreamWriter(csEncrypt))

{

swEncrypt.Write(privateKey);

}

}

}

#endregion

}

Example of use:

private static void HybridCryptoTest(string privateKeyPath, string privateKeyPassword, string inputPath)

{

// Setup the test

var publicKeyPath = Path.ChangeExtension(privateKeyPath, ".public"); var outputPath = Path.Combine(Path.ChangeExtension(inputPath, ".enc")); var testPath = Path.Combine(Path.ChangeExtension(inputPath, ".test"));

if (!File.Exists(privateKeyPath))

{

var keys = AsymmetricProvider.GenerateNewKeyPair(2048); AsymmetricProvider.WritePublicKey(publicKeyPath, keys.PublicKey); AsymmetricProvider.WritePrivateKey(privateKeyPath, keys.PrivateKey, privateKeyPassword);

}

// Encrypt the file

var publicKey = AsymmetricProvider.ReadPublicKey(publicKeyPath); AsymmetricProvider.EncryptFile(inputPath, outputPath, publicKey);

// Decrypt it again to compare against the source file

var privateKey = AsymmetricProvider.ReadPrivateKey(privateKeyPath, privateKeyPassword); AsymmetricProvider.DecryptFile(outputPath, testPath, privateKey);

// Check that the two files match

var source = File.ReadAllBytes(inputPath); var dest = File.ReadAllBytes(testPath);

if (source.Length != dest.Length)

throw new Exception("Length does not match");

if (source.Where((t, i) => t != dest[i]).Any()) throw new Exception("Data mismatch");

}

GoalKicker.com – C# Notes for Professionals

735