This is a brief introduction into .NET cryptography.

Hashes

Hashing is a transformation process of some input data of an arbitrary length into an array of bytes of fixed size.
Hash is a one-side transformation function, the result of which cannot be reversed for receiving original input data. Very often it is used to store passwords. Even if an attacker gets a hash, he can’t retrieve the password from it. The length of a hash is determined by a hashing algorithm. In .NET you can find the following hashing algorithms (all of which derive from the HashAlgorithm base class):

  • MD5 — 128 bits in length
  • SHA (Secure Hash Algorithm) — there is no such a class, but there are SHA1 (160 bits), SHA256, SHA384, SHA512
  • KeydHashAlgorithm (also known as Message Authentication Code). Represented by the following classes of  algorithms: HMAC and MACTripleDES

If you need a hash based on a key, then (if all other conditions being equal) it’s better to rely on SHA512. MD5 is getting old and can be compromised. SHA512 works very fast (only SHA1 is faster). As a matter of fact, the whole class of SHA’s works fast. The slowest one is MACTripleDES. HMAC algorithms are roughly 2-4 times slower than SHA.

Hashes based on a key can be used to protect data from modifications. Requests sent to a server from a client could be verified for the facts of modifications. If server receives modified data, then hashes will not be equal. In case of protecting string requests you shouldn’t rely on a hash which is not based on a key. Because an attacker can brute-force all possible hash algorithms, find the sought one, modify arguments in the request string, attach the correct hash-value and after that server has to accept the request. In order to protect a server from this, you can use HMACSHA1 as a key-based hash algorithm. In this case, obviously, arise the problem of storing that key.

[code lang=”csharp”]
public static byte[] ComputeHmacsha1(byte[] data, byte[] key)
{
using (var hmac = new HMACSHA1(key))
{
return hmac.ComputeHash(data);
}
}
[/code]

You can add salt, in order to reduce the abilities of an attacker in collecting of input-data with corresponding hash-values for subsequent brute-force attack.
Salt is a randomized additional “key”, which adds some entropy for encryption. Salt can be passed in clear text. It can be generated and associated with the current session of a user. Salt will be updated every new session, thus hash-values for the same requests will be different in different sessions. Also, in order to complicate the brute-force by a dictionary attack, salt can be added to hashed passwords. It’s better to use a cryptographically strong generator of RNGCryptoServiceProvider type.

Salt can be generated by the following code:

[code lang=”csharp”]
string salt = GenSalt(32);
string GenSalt(int length)
{
RNGCryptoServiceProvider p = new RNGCryptoServiceProvider();
var salt = new byte[length];
p.GetBytes(salt);
return Convert.ToBase64String(salt);
}
[/code]

Symmetric Algorithms

You can find the following algorithms in .NET which derive from the base class SymmetricAlgorithm:

  • Rijndael
  • DES
  • TripleDES
  • RC2

The algorithm recommended to use by default is Rijndael. Mars, RC6, Serpent, TwoFish are also good, but you’ll not find them in the standard .NET “package” (look for them in third-party libraries). Rijndael is a block sypher. It means that the input data will be sliced into blocks and the algorithm will be applied to each block consecutively.

Padding is a process of adjusting (adding some additional data) the results produced by a block cypher in order to get the result of the required size.

Rijndael supports several padding modes: zeroing, padding by random numbers and there is also a couple of modes. The safest mode which adds up some entropy is padding by random numbers. This is ISO10126 mode.

Rijndael also supports several modes of blocks processing. CBC mode is the best choice by default. This mode means that each next block takes as the input the results of the previous block encryption, what increases entropy again.

IV stands for initialization vector. It is passed as the input for the first block. In case of Rijndael IV will be generated automatically. IV is necessary for decrypting, so it should be passed to a decrypting method. By the way, it can be passed in clear text.

Consider the following simple example:

[code lang=”csharp”]
string Encrypt()
{
RijndaelManaged cipher = new RijndaelManaged();
cipher.KeySize = 256;
cipher.BlockSize = 256; //use 128 for compatibility with AES
cipher.Padding = PaddingMode.ISO10126;
cipher.Mode = CipherMode.CBC;
cipher.Key = "some_super_secret_key";

ICryptoTransform t = cipher.CreateEncryptor();
string text = "some_text_to_encrypt";
byte[] textInBytes = Encoding.UTF8.GetBytes(text);
byte[] result = t.TransformFinalBlock(textInBytes, 0, textInBytes.Length);
return Convert.ToBase64String(result);
}
[/code]

.NET also provides an interesting class named CryptoStream. It is used for integration of encryption and streams like FileStream and so on. For example, one CryptoStream which performs the encryption can be passed in another CryptoStream which wraps FileStream. After that you can call Write on the first CryptoStream with some text and as a result it will be encrypted and written to a file.

Asymmetric Algorithms

In case of asymmetric algorithms public key is used for encryption and private key for decryption. If there is only one part which decrypts encrypted messages, then a private key can be stored in one place. In case of symmetric encryption, a private key should be shared between all participants.

Asymmetric algorithms are slower than symmetric roughly 100-1000 times. That’s why it is somewhat problematic to encrypt huge data chunks. Asymmetric encryption can be used side by side with symmetric. Asymmetric alg generates a session key, which is used as a key for symmetric encryption.

.NET provides the following main asymmetric algorithms:

  • RSA
  • DSA (only for digital signs)
  • ECDiffieHellman

Usually, people use RSA 2048 or 4096. RSA 1024 is not strong and this fact has been proven. So, you should rely on 2048 bits and greater length. Here is an example of using RSA:

[code lang=”csharp”]
public class RsaWithCspKey
{
const string ContainerName = "MyContainer";
public void AssignNewKey()
{
CspParameters cspParams = new CspParameters(1);
cspParams.KeyContainerName = ContainerName;
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
cspParams.ProviderName = "Microsoft Strong Cryptographic Provider";
var rsa = new RSACryptoServiceProvider(cspParams) { PersistKeyInCsp = true };
}
public void DeleteKeyInCsp()
{
var cspParams = new CspParameters { KeyContainerName = ContainerName };
var rsa = new RSACryptoServiceProvider(cspParams) { PersistKeyInCsp = false };
rsa.Clear();
}
public byte[] EncryptData(byte[] dataToEncrypt)
{
byte[] cipherbytes;
var cspParams = new CspParameters { KeyContainerName = ContainerName };
using (var rsa = new RSACryptoServiceProvider(2048, cspParams))
{
cipherbytes = rsa.Encrypt(dataToEncrypt, false);
}
return cipherbytes;
}
public byte[] DecryptData(byte[] dataToDecrypt)
{
byte[] plain;
var cspParams = new CspParameters { KeyContainerName = ContainerName };
using (var rsa = new RSACryptoServiceProvider(2048, cspParams))
{
plain = rsa.Decrypt(dataToDecrypt, false);
}
return plain;
}
}
[/code]

Digital signatures are based on asymmetric encryption. Digital signature guarantees the privacy of a message and proves the author’s identity.
Here is an example of how it works:

  1. Alice encrypts her message.
  2. Alice calculates a hash of her message.
  3. Alice signs her message by her signing private key.
  4. Alice sends encrypted data, hash and her digital sign to Bob.
  5. Bob calculates a hash of encrypted data.
  6. Bob verifies a digital sign, applying a public key.

Here is a code snippet:

[code lang=”csharp”]
public class DigitalSignature
{
private RSAParameters _publicKey;
private RSAParameters _privateKey;
public void AssignNewKey()
{
using (var rsa = new RSACryptoServiceProvider(2048))
{
rsa.PersistKeyInCsp = false;
_publicKey = rsa.ExportParameters(false);
_privateKey = rsa.ExportParameters(true);
}
}
public byte[] SignData(byte[] hashOfDataToSign)
{
using (var rsa = new RSACryptoServiceProvider(2048))
{
rsa.PersistKeyInCsp = false;
rsa.ImportParameters(_privateKey);

var rsaFormatter = new RSAPKCS1SignatureFormatter(rsa);
rsaFormatter.SetHashAlgorithm("SHA256");
return rsaFormatter.CreateSignature(hashOfDataToSign);
}
}
public bool VerifySignature(byte[] hashOfDataToSign, byte[] signature)
{
using (var rsa = new RSACryptoServiceProvider(2048))
{
rsa.ImportParameters(_publicKey);
var rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
rsaDeformatter.SetHashAlgorithm("SHA256");
return rsaDeformatter.VerifySignature(hashOfDataToSign, signature);
}
}
}
[/code]

Summary

We discussed the basics of cryptography in .NET along with a couple of useful code examples.

Subscribe to my blog!