User Tools

Site Tools


notes:csharp:cryptography

Cryptography in C# and .NET

  • Symmetric key algorithms use the same key (a shared key) for encryption and decryption of data. The algorithms using symmetric keys are much faster than those using asymmetric keys and are well-suited for encrypting larger data sets. The downside is that the key must be exchanged in a safe manner. The shared key is also called a private key, a secret key, or a symmetric key.
  • Asymmetric key algorithms use a pair of keys: a public and a private key. With asymmetric encryption, a public key is used to encrypt data and a private key is used to decrypt data. The public key is known to everyone who must be able to encrypt data. The private key is known only to those who should decrypt data.
  • The purpose of hash algorithms is to create a fixed-length hash value from binary strings. The hash algorithms are used with digital signatures and for data integrity. If the same binary string is hashed again, the same hash value is returned. Hashing is a one-way process and hashed data cannot be reversed. For example, a hash code can be calculated for a paragraph of text. When this paragraph is changed in any way, the hash code will be different. It implies that two objects that are equal have the same hash codes. However, the reverse is not true: equal hash codes do not imply object equality, because different (unequal) objects can have identical hash codes.
Symmetric Key Algorithm Description
Advanced Encryption Standard Offers a key size of 128, 192, or 256 bits.
Rijndael Similar to AES but offers more key size options.
Triple-DES The successor to DES. Its key length is 168 bits, but the effective security it provides is 112-bit.
Data Encryption Standard Considered insecure because it uses only the 56 bits key size which can be broken in a few hours.
Asymmetric Key Algorithm Description
Rivest Shamir Adleman Used for digital signatures as well as encryption in e-commerce protocols;
incorporated into all major Web browsers; the basis for Secure Socket Layer (SSL).
Digital Signature Algorithm Used for digital signatures.
Elliptic Curve DSA More secure, with shorter key sizes, for example: ECDSA 160 bits is as secure as DSA 1024 bits.
EC Diffie-Hellman Used to exchange private keys in a secure way over a public channel.
Hash Algorithm Description
Secure Hash Algorithm stronger than MD5 against brute force attacks
- SHA1 uses a 160-bit hash size
- SHA256 uses 256 bits
- SHA384 uses 384 bits
- SHA512 uses 512 bits; it is the strongest and the slowest from the SHA algorithms
Message Digest Algorithm 5 faster than SHA; MD5 uses 128-bit hash size
RACE Integrity Primitives Evaluation MD RIPEDM160 uses a 160-bit hash size

The .NET Framework contains classes for encryption in the namespace System.Security.Cryptography. The classes with a Cng (Cryptography Next Generation) prefix or suffix are newer versions of the native Crypto API. There are a few abstract classes such as MD5, SHA1, or DES. The classes with the suffix CryptoServiceProvider implement the abstract base classes.

Category Abstract Base Class Concrete Classes
Symmetric DES DESCryptoServiceProvider
TripleDES TripleDESCryptoServiceProvider
Aes AesCryptoServiceProvider, AesManaged
RC2 RC2CryptoServiceProvider
Rijandel RijandelManaged
Asymmetric DSA DSACryptoServiceProvider
ECDsa ECDsaCng, ECDiffieHellman, ECDiffieHellmanCng
RSA RSACryptoServiceProvider
Hash MD5 MD5Cng
SHA1 SHA1Managed, SHA1Cng
SHA256 SHA256Managed, SHA256Cng
SHA384 SHA384Managed, SHA384Cng
SHA512 SHA512Managed, SHA512Cng
RIPEMD160 RIPEMD160Managed

Digital certificates can be used to verify the authenticity of an author. Digital certificates use hashing as well as asymmetric encryption. A digital certificate authenticates the identity of an object signed by the certificate. It also helps with protecting the integrity of data.

An example of communication using a digital certificate. A is sending a message to B:

  • A hashes the message to generate a hash code
  • A encrypts the hash code with its private key to create a personal signature
  • A sends the message and the personal signature to B
  • B receives the message and the signature
  • B decrypts the signature using A's public key to obtain the message hash code
  • B hashes the message and compares this hash code with the one obtained from the signature
  • If the hash codes match, it means the message has not been tampered

A digital certificate is part of a Public Key Infrastructure (PKI). A PKI is a system of digital certificates, certificate authorities, and other registration authorities that authenticate and verify the validity of each involved party.

A Certificate Authority (CA) is a third-party issuer of certificates that is considered trustworthy by all parties. The CA issues certificates (certs) that contain a public key, a subject to which the certificate is issued, and the details of the CA.

One use of digital certificates is to secure Internet communication. The HTTPS communication protocol is used to secure communication between a web server and a client.

The makecert.exe utility can be used to create X.509 certificates for test purposes. Before the certificate can be used it has to be installed in a certificate store.

Generate a certificate to the MyCert.cer file:

makecert MyCert.cer 

Create and install a certificate in a custom certificate store MyCertStore:

makecert -n "CN = WBS" -sr currentuser -ss MyCertStore 

The ProtectedData and ProtectedMemory classes are managed wrappers around Data Protection Application Programming Interface (DPAPI):

  • ProtectedData - Provides methods for encrypting and decrypting data.
  • ProtectedMemory - Provides methods for protecting and unprotecting memory.

The ProtectedData.Protect method does not modify the input data. The ProtectedMemory.Protect method modifies the input data. The length of the input byte array has to be a multiple of 16.

Example: Encrypt and decrypt a string using the symmetric key algorithm AES:

using System;
using System.IO;
using System.Security.Cryptography;
 
namespace WBS.Test
{
    public class TestApp
    {
        public static int Main(string[] args)
        {
            string original = "A secret message";
 
            using (SymmetricAlgorithm sa = new AesManaged())
            {
                byte[] encrypted = Encrypt(sa, original);
                string roundtrip = Decrypt(sa, encrypted);
 
                // Displays: A secret message
                Console.WriteLine("Original: {0}", original);
                Console.WriteLine("Round Trip: {0}", roundtrip);
            }
 
            return 0;
        }
 
        static byte[] Encrypt(SymmetricAlgorithm aes, string plainText)
        {
            // Use the SymmetricAlgorithm.CreateEncryptor method to create an encryptor.
            ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); 
            using (MemoryStream memStream = new MemoryStream())
            {
                // Encrypt a byte sequence using the CryptoStream class.
                using (CryptoStream crStream = new CryptoStream(memStream, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter writer = new StreamWriter(crStream))
                    {
                        writer.Write(plainText);
                    }
                    return memStream.ToArray();
                }
            }
        }
 
        static string Decrypt(SymmetricAlgorithm aes, byte[] cipherText)
        {
            // Use the SymmetricAlgorithm.CreateDecryptor method to create a decryptor.
            ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
            using (MemoryStream memStream = new MemoryStream(cipherText))
            {
                // Decrypt a byte sequence using the CryptoStream class.
                using (CryptoStream crStream = new CryptoStream(memStream, decryptor, CryptoStreamMode.Read)) 
                {
                    using (StreamReader reader = new StreamReader(crStream)) 
                    {
                        return reader.ReadToEnd(); 
                    } 
                } 
            } 
        }
    }
}

Example: Use asymmetric encryption with RSACryptoServiceProvider to encrypt data using a public key and decrypt it with a private key:

using System;
using System.Security.Cryptography;
using System.Text;
 
namespace WBS.Test
{
    public class TestApp
    {
        public static int Main(string[] args)
        {
            // Create a public/private key pair.
            string publicKeyXML, privateKeyXML;
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                publicKeyXML = rsa.ToXmlString(false); // export the public key
                privateKeyXML = rsa.ToXmlString(true); // export both the public and the private keys
            }
 
            // Obtain byte representation of data for encryption.
            UnicodeEncoding converter = new UnicodeEncoding(); 
            byte[] dataToEncrypt = converter.GetBytes("A secret message");
 
            // Use the public key to encrypt data.
            byte[] encryptedData; 
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) 
            { 
                rsa.FromXmlString(publicKeyXML); // we need only the public key to encrypt data
                encryptedData = rsa.Encrypt(dataToEncrypt, false);
            } 
 
            // Use the private key to decrypt data.
            byte[] decryptedData; 
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            { 
                rsa.FromXmlString(privateKeyXML); 
                decryptedData = rsa.Decrypt(encryptedData, false);
            } 
 
            // Obtain text representation of the byte sequence.
            string decryptedString = converter.GetString(decryptedData);
            Console.WriteLine(decryptedString); // displays: A secret message
 
            return 0;
        }
    }
}

Example: Configure RSACryptoServiceProvider to use a key container for saving and loading the asymmetric key. The key container can be specific to a user or to the whole machine:

using System.Security.Cryptography;
using System.Text;
 
namespace WBS.Test
{
    public class TestApp
    {
        public static int Main(string[] args)
        {
            // Obtain byte representation of data for encryption.
            UnicodeEncoding converter = new UnicodeEncoding(); 
            byte[] dataToEncrypt = converter.GetBytes("A secret message");
 
            CspParameters csp = new CspParameters() 
            { 
                KeyContainerName = "MyContainer"
            }; 
 
            byte[] encryptedData; 
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp))
            {
                encryptedData = rsa.Encrypt(dataToEncrypt, false); 
            }
 
            return 0;
        }
    }
}

Example: Export both the public and the private keys to XML:

using System;
using System.Security.Cryptography;
 
namespace WBS.Test
{
    public class TestApp
    {
        public static int Main(string[] args)
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); 
 
            string publicKeyXML = rsa.ToXmlString(false); // export the public key
            string privateKeyXML = rsa.ToXmlString(true); // export both the public and the private keys
 
            Console.WriteLine(publicKeyXML); 
            Console.WriteLine(privateKeyXML);
 
            return 0;
        }
    }
}

Example: Calculate the hash code for a piece of text using the SHA256Managed algorithm. The hashing algorithm SHA256Managed outputs a significantly different hash code for a small change in data:

using System;
using System.Linq; // SequenceEqual
using System.Security.Cryptography;
using System.Text;
 
namespace WBS.Test
{
    public class TestApp
    {
        public static int Main(string[] args)
        {
            UnicodeEncoding byteConverter = new UnicodeEncoding();
            SHA256 sha = SHA256.Create();
 
            string str = "Sample text";
            byte[] hash1 = sha.ComputeHash(byteConverter.GetBytes(str));
 
            // hash2 is defferent than hash1
            str = "Modified sample text";
            byte[] hash2 = sha.ComputeHash(byteConverter.GetBytes(str));
 
            // hash3 is the same as hash1
            str = "Sample text";
            byte[] hash3 = sha.ComputeHash(byteConverter.GetBytes(str));
 
            // Check whether the string str has been altered by comparing the hash codes.
            Console.WriteLine(hash1.SequenceEqual(hash2)); // Displays: false
            Console.WriteLine(hash1.SequenceEqual(hash3)); // Displays: true
 
            return 0;
        }
    }
}

Example: Use a digital certificate to sign and verify some text. The data is hashed and then signed. When verifying, the same hash algorithm is used to make sure the data has not changed:

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
 
namespace WBS.Test
{
    public class TestApp
    {
        public static int Main(string[] args)
        {
            // Sign and verify data with a certificate
            string textToSign = "Test paragraph";
            byte[] signature = Sign(textToSign, "CN = WBS");
 
            // Uncomment this line to make the verification step fail
            //signature[0] = 0; 
 
            Console.WriteLine(Verify(textToSign, signature)); // displays: True
 
            return 0;
        }
 
        // The Sign method uses the private key of the certificate to 
        // create a signature for the data. 
        static byte[] Sign(string text, string certSubject)
        { 
            X509Certificate2 cert = GetCertificate();
            var csp = (RSACryptoServiceProvider)cert.PrivateKey;
            byte[] hash = HashData(text); 
            return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
        } 
 
        // The Verify method uses the public key of the certificate to 
        // see whether the data has changed.
        static bool Verify(string text, byte[] signature)
        { 
            X509Certificate2 cert = GetCertificate(); 
            var csp = (RSACryptoServiceProvider)cert.PublicKey.Key; 
            byte[] hash = HashData(text); 
            return csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), signature);
        } 
 
        private static byte[] HashData(string text)
        { 
            HashAlgorithm hashAlgorithm = new SHA1Managed(); 
            UnicodeEncoding encoding = new UnicodeEncoding(); 
            byte[] data = encoding.GetBytes(text); 
            byte[] hash = hashAlgorithm.ComputeHash(data);
            return hash;
        } 
 
        private static X509Certificate2 GetCertificate() 
        { 
            X509Store my = new X509Store("MyCertStore", StoreLocation.CurrentUser);
            my.Open(OpenFlags.ReadOnly);
 
            var certificate = my.Certificates[0]; 
 
            return certificate;
        }
    }
}

Example: Generate an encrypted representation of a byte array using DPAPI:

using System;
using System.Security.Cryptography;
 
namespace CSharpTest
{
    class EntryPoint
    {
        public static void Main()
        {
            byte[] secretData = new byte[] { 120, 34, 56, 88, 230, 23, 45, 89,
                                             34, 234, 106, 78, 46, 203, 109, 3};
 
            // Only the current user can decrypt the data.
            // The input byte array is not modified.
            byte[] data1 = ProtectedData.Protect(secretData, null, DataProtectionScope.CurrentUser);
 
            // Any user on the current computer can decrypt the data. It is useful when data is 
            // stored on a network share and there are multiple users on the same computer who 
            // need access the data.
            // The input byte array is not modified.
            byte[] data2 = ProtectedData.Protect(secretData, null, DataProtectionScope.LocalMachine);
 
            // Only the user who encypted data can decrypt it.
            // The input byte array is modified. 
            ProtectedMemory.Protect(secretData, MemoryProtectionScope.SameLogon);
 
            // All threads in the same process can decrypt the data.
            // The input byte array is modified. 
            ProtectedMemory.Protect(secretData, MemoryProtectionScope.SameProcess);
        }
    }
}

Links

notes/csharp/cryptography.txt · Last modified: 2018/01/09 by leszek