📜 ⬆️ ⬇️

Some practical cryptography for .NET for dummies

image

This is a brief introduction to cryptography under .NET for dummies, as the title suggests. There will be simple things and no in-depth knowledge.


A bit about hashes


Hashing is the process of converting some input data of any length into a fixed-length byte array.
A hash is a one-way conversion function, the result of which can not be reversed to obtain the original input data. Very often used for storing passwords. Even if the attacker gets a hash, then get the password itself will not work. The hash length is determined by the hashing algorithm.
In .NET, you can find the following hash algorithms (inheriting the abstract HashAlgorithm class):

In .NET, if you do not need a hash based on the key, then, other things being equal, today it is best to use the SHA512 algorithm. MD5 is somewhat outdated and may be compromised. SHA512 works very fast (second only to SHA1). In fact, the whole SHA family works fast. The slowest MACTripleDES. The HMAC family is about two to four times slower than SHA.
')
Key-based hashes can be used to protect data from modifications. Requests sent to the server from the client can be checked for modifications. If the data transmitted to the server has been modified, the hashes will not match. When protecting string queries, you cannot use a simple hash, without a key. Because, then the attacker simply iterates through all possible hashing algorithms, finds the right one, modifies the arguments in the query string and substitutes the desired hash, and then the server will have nothing left to do to respond to a correctly formed request. For such protection, HMACSHA1 may well be suitable as a key-based hashing algorithm. Of course, the problem of storing a secret key arises in full growth.

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

So that the attacker has less opportunity to collect the input data and the corresponding hashes for the next search in order to find the key, you can add salt.
Salt is a kind of randomized additional “key” that adds entropy to encryption. Salt can be transmitted in the clear. Salt can be generated and associated with the current user session. Thus, the salt will change every session and the hashing results for the same requests will be different. Salt is also added when hashing passwords to complicate dictionary searching, especially if, for example, an intruder has access to hash values. To generate salt, it is better to use a crypto-resistant generator like RNGCryptoServiceProvider.
Salt can be generated as follows:
 string salt = GenSalt(32); string GenSalt(int length) { RNGCryptoServiceProvider p = new RNGCryptoServiceProvider(); var salt = new byte[length]; p.GetBytes(salt); return Convert.ToBase64String(salt); } 


A little about symmetric encryption algorithms


In .NET, several algorithms are introduced that inherit the SymmetricAlgorithm base class:

Currently the recommended symmetric algorithm in .NET is Rijndael. Mars, RC6, Serpent, TwoFish are also good, but they are not implemented in .NET. If only in third-party libraries. Rijndael is a block encryption algorithm.
Block means that the input data will be sliced ​​into blocks and the encryption algorithm will be applied sequentially to each of these blocks.
Padding means the process of “finishing off” the results of block encryption in order to obtain the desired size as a result.
Rijndael supports several padding modes - finishing with zeros, finishing with random values ​​and a couple more modes. Perhaps the most reliable way to add entropy is to pursue random values ​​- this is ISO10126 mode.
Rijndael also supports several ways to work on blocks. By default, it is better to select the CBC mode, which means that the encryption results of the previous block will also be fed to the encryption of the next block, which again increases the entropy.
IV - initialization vector (initialization vector). Required for submission to the input encryption of the first block. When using Rijndael is generated automatically. To decrypt the initialization vector, you need to know it and you can send it in open form.
A small example:
 string Encrypt() { RijndaelManaged cipher = new RijndaelManaged(); cipher.KeySize = 256; cipher.BlockSize = 256; //  128    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); } 


In .NET there is also an interesting CryptoStream class. It is designed to integrate encryption with filestream streams and so on. For example, in one CryptoStream that performs encryption, you can attach another CryptoStream, which contains a FileStream. Then in the first CryptoStream do Write, transferring the text, and as a result receive the cipher text recorded in a certain file.

A little about asymmetric encryption algorithms


In the case of asymmetric algorithms, the public key is used for encryption and private for decryption. If encrypted messages are decrypted by only one of the participants, then the private key can only be stored in one place. In the case of symmetric encryption, the private key has to be made known to all participants in the exchange of messages.
Asymmetric encryption algorithms are approximately 100-1000 times slower than symmetric ones. Therefore, large amounts of data to encrypt them can be expensive. Asymmetric encryption can be used along with symmetric. Using asymmetric encryption, a session key is generated, which is used to encrypt data.
The following basic algorithms are implemented in .NET:

Usually for RSA, keys of 1024, 2048, 4096 bits are used. It has been proven that RSA on a 1024 key is unreliable, so longer keys should be used.
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. A digital signature ensures the privacy of a message and proves that a message belongs to one or another author. This usually works as follows:
1. Alice encrypts her message.
2. Alice takes the hash of her message.
3. Alice signs the message with her private key for signing.
4. Alice sends the encrypted data, hash, and signature to Bob.
5. Bob calculates the hash of the encrypted data.
6. Bob verifies the signature using the public key.

Example of using a signature:

 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); } } } 

The article is a very brief summary of two courses with Pluralsight: “Introduction to Cryptography in .NET” and “Practical Cryptography in .NET” .

Translation here.

Source: https://habr.com/ru/post/254909/


All Articles