This is a brief introduction into Cryptography in .NET.
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 SHAs 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 bruteforce 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, arises the problem of storing that key.
public static byte[] ComputeHmacsha1(byte[] data, byte[] key)
{
using (var hmac = new HMACSHA1(key))
{
return hmac.ComputeHash(data);
}
}
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:
string salt = GenSalt(32);
string GenSalt(int length)
{
RNGCryptoServiceProvider p = new RNGCryptoServiceProvider();
var salt = new byte[length];
p.GetBytes(salt);
return Convert.ToBase64String(salt);
}
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 are 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:
string Encrypt()
{
RijndaelManaged cipher = new RijndaelManaged();
cipher.KeySize = 256;
cipher.BlockSize = 256;
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);
}
.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:
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;
}
}
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:
- Alice encrypts her message.
- Alice calculates a hash of her message.
- Alice signs her message by her signing private key.
- Alice sends encrypted data, hash and her digital sign to Bob.
- Bob calculates a hash of encrypted data.
- Bob verifies a digital sign, applying a public key.
Here is a code snippet:
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);
}
}
}
Summary
We discussed the basics of cryptography in .NET along with a couple of useful code examples.
Subscribe to my blog!
Filed under: .NET, CodeProject, Security Tagged: Cryptography