© Marius Iulian Mihailescu and Stefania Loredana Nita 2021
M. I. Mihailescu, S. L. NitaPro Cryptography and Cryptanalysis https://doi.org/10.1007/978-1-4842-6367-9_7

7. .NET Cryptography Services

Marius Iulian Mihailescu1   and Stefania Loredana Nita1
(1)
Bucharest, Romania
 

In this chapter, we will discuss the main services and cryptographic primitives that .NET Framework and .NET Core offer to professionals. Knowing what services and cryptographic primitives a development technology has to offer is very important, especially if you don’t want to develop the cryptographic algorithms and security schemes from scratch.

The following topics will be covered:
  • Cryptographic primitives

  • Encryption using a secret key

  • Encryption using a public key

  • Digital signatures

  • Hash values

  • Random number generation

  • Support for Suite B

  • Cryptography Next Generation (CHG) classes

Communication through the Internet is not inherently secure, and in this case encryption needs to be used with the goal of guaranteeing the security of such communication. The communication between these network entities is likely to be read by unauthorized third parties, or even altered. As you have seen before, the purpose of cryptography is to protect the data from being viewed or modified by parties who are not authorized to do so. With the help of the .NET Framework, we have cryptographic classes that are designed to be used within System.Security.Cryptography (see Chapter 9), a namespace that handles cryptography functions and their operations. Also, we are dealing with wrappers for the unmanaged CryptoAPI (Microsoft Cryptography API). Meanwhile, others are fully implemented and tested accordingly. The nice thing is that once we create a new instance of a specific encryption algorithm class, the keys are automatically generated in order to be used as easy as possible, using default properties that are very secure.

This being said, in the following sections we will provide a quick synopsis of the most important encryption algorithms that are supported by the .NET Framework (ClickOnce, Suite B, and CNG), features that were introduced starting with .NET Framework 3.5.

Encryption Using a Secret Key

The encryption algorithms based on a secret key use a single secret key for the encryption and decryption processes. It is very important to guarantee the security of the key from unauthorized parties or services. Any unauthorized party that has access to the key can use it to decrypt data or encrypt other data, claiming and impersonating the real authorized party.

Encryption using a secret key is also known as symmetric encryption because the same key is used for both processes: encryption and decryption operations (see Figure 7-1). The encryption algorithms based on the secret key are very fast compared to the algorithms based on a public key. At the same time, they are most suitable for doing cryptographic encoding for large data streams. On the other hand, we have algorithms that are based on asymmetric encryption such as RSA, and their mathematical limitation is based on how much data has to be encrypted.
../images/493660_1_En_7_Chapter/493660_1_En_7_Fig1_HTML.jpg
Figure 7-1

Symmetric encryption

The .NET Framework contains the following classes for helping professionals implement encryption and decryption operations using the same secret key:
  • AesManaged [38] (starting with .NET Framework 3.5). See an implementation in Listing 7-1 and Figure 7-2.

  • RijndaelManaged [41]. See an implementation in Listing 7-2.

  • DESCryptoServiceProvider [39]. See an implementation in Listing 7-3.

  • RC2CryptoServiceProvider [40]. The implementation is similar with the one from Listing 7-3.

  • TripleDESCryptoServiceProvider [42].

../images/493660_1_En_7_Chapter/493660_1_En_7_Fig2_HTML.jpg
Figure 7-2

AES execution

using System;
using System.IO;
using System.Security.Cryptography;
namespace AESExampleOfImplementation
{
    class AESExampleOfImplementation
    {
        public static void Main()
        {
            string genuineMessage = "Welcome to Apress!";
            //** Declare a new instance
            //** of the class AESManaged.
            //** With its help a new key
            //** and initialization vector is generated
            using (AesManaged aes_encryption =
new AesManaged())
            {
                //** the string is encrypted and
                //** stored as an array of bytes
                byte[] message_encrypted =
EncryptStringToBytes_Aes(genuineMessage,
aes_encryption.Key,
aes_encryption.IV);
                //** the decryption will take place as
                //** decrypting the bytes into a string
                string tripRound =
DecryptStringFromBytes_Aes(
message_encrypted,
aes_encryption.Key,
aes_encryption.IV);
                //** Shows in the console the original
                //** message and the data decrypted
Console.WriteLine("The original message is:
{0}", genuineMessage);
Console.WriteLine("The trip round is: {0}",
tripRound);
Console.WriteLine("The encrypted message is
(byte-by-byte view): {0}", PrintByteArray(message_encrypted));
                Console.WriteLine("The encrypted message is
(default view): {0}", Encoding.Default.GetString
(message_encrypted));
                Console.WriteLine("The encrypted message is
(UTF8 view): {0}", Encoding.UTF8.GetString(message_encrypted));
                Console.WriteLine("The encrypted message is
(UTF32 view): {0}",
Encoding.UTF32.GetString
(message_encrypted));
                Console.ReadKey();
            }
        }
       //** processing byte values to display them
     static string PrintByteArray(byte[] encrypted_message)
        {
            var build_string =
new StringBuilder("new byte[] { ");
            foreach (var each_byte in encrypted_message)
            {
                build_string.Append(each_byte + " ");
            }
            build_string.Append("}");
            return build_string.ToString();
        }
        static byte[] EncryptStringToBytes_Aes
(string genuineText,
 byte[] crypto_key,
 byte[] initializationVector)
        {
            //** verify the arguments
            if (genuineText == null ||
genuineText.Length <= 0)
               throw new ArgumentNullException("genuineText");
            if (crypto_key == null || crypto_key.Length <= 0)
                throw new ArgumentNullException("crypto_key");
 if (initializationVector == null ||
initializationVector.Length <= 0)
                throw new ArgumentNullException("IV");
            byte[] encryptionRepresentation;
            //** declare an AesManaged instance
            //** Create an AesManaged object
           //** the declaration should include the specified
           //** key and initialization vector.
            using (Aes aes_algorithm = Aes.Create())
            {
                aes_algorithm.Key = crypto_key;
                aes_algorithm.IV = initializationVector;
                //** do the stream transformation
               //** for this declare an ecnryptor
              //** using ICryptoTransform
                ICryptoTransform crypto_transformation =
aes_algorithm.CreateEncryptor
(aes_algorithm.Key, aes_algorithm.IV);
                //** use the streams and work with the
                //** encryption Create the streams
                //** used for encryption
                using (MemoryStream memoryStreamForEncryption
= new MemoryStream())
                {
                    using (CryptoStream cryptoStreamEncryption
 = new CryptoStream(memoryStreamForEncryption,
 crypto_transformation, CryptoStreamMode.Write))
                {
                        using (StreamWriter
  streamWriterForEncryption = new
                        StreamWriter(cryptoStreamEncryption))
                        {
                            //** write the entire volume of
                           //** data with the stream
streamWriterForEncryption.
Write(genuineText);
                        }
                        encryptionRepresentation =
    memoryStreamForEncryption.ToArray();
                    }
                }
            }
            //** Return the encrypted bytes from
 //** the memory stream.
            return encryptionRepresentation;
        }
        static string DecryptStringToBytes_Aes
(byte[] encryptedText,
 byte[] encryption_key,
 byte[] initialization_vector)
        {
            //** verify the arguments
            if (encryptedText == null ||
encryptedText.Length <= 0)
              throw new
ArgumentNullException("encryptedText");
            if (encryption_key == null ||
encryption_key.Length <= 0)
                throw new ArgumentNullException("Key");
 if (initialization_vector == null ||
initialization_vector.Length <= 0)
                throw new ArgumentNullException("IV");
            //** the string used to store
            //** the original decrypted text
            string original_text = null;
            //** declare an AesManaged instance
            //** using the encryption key
            //** and initialization vector
            using (Aes aes_algorithm = Aes.Create())
            {
                aes_algorithm.Key = encryption_key;
                aes_algorithm.IV = initialization_vector;
                //** do the stream transformation
               //** for this declare an
               //** encryptor using ICryptoTransform
                ICryptoTransform decrypt_transformation =
aes_algorithm.CreateDecryptor(
aes_algorithm.Key,
aes_algorithm.IV);
                //** use the streams and work
                //** with the encryption
                //** Create the streams used for encryption
                using (MemoryStream memoryStreamDecryption =
                       new MemoryStream(encryptedText))
                {
                    using (CryptoStream cryptoStreamDecryption
= new CryptoStream(
memoryStreamDecryption,
decrypt_transformation,   CryptoStreamMode.Read))
                    {
                        using (StreamReader
streamReaderDecryption =
                         new StreamReader(
cryptoStreamDecryption))
                        {
                  //** read the decrypted bytes from
                  //** the stream reader
                  //** and save it in
                 //** original_text variable
                           original_text =
streamReaderDecryption.
ReadToEnd();
                        }
                    }
                }
            }
            return original_text;
        }
    }
}
Listing 7-1

AES Implementation Using AesManaged

In Listing 7-2 and Figure 7-3 you can observe how Rijndael can be implemented.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace RijndaelManagedImplementationExample
{
    class RijndaelManagedImplementationExample
    {
        public static void Main()
        {
            try
            {
                string genuineMessage = "Folks, Welcome to Apress!";
                //** declare a new instance of the
                //** RijndaelManaged class with this
                //** instance a new key and
                //** initialization vector (IV)
                using (RijndaelManaged rijndeal_crypto = new RijndaelManaged())
                {
                    rijndeal_crypto.GenerateKey();
                    rijndeal_crypto.GenerateIV();
                    //** encrypt the message (string)
                    //** and store the content to an
                    //** array of bytes
                    byte[] encrypted = EncryptStringToBytes(genuineMessage, rijndeal_crypto.Key, rijndeal_crypto.IV);
                    //** Decrypt the bytes to a string
                    string tripRound = DecryptStringFromBytes(encrypted, rijndeal_crypto.Key, rijndeal_crypto.IV);
                    //** Display the original data
                    //** and the decrypted data
                    Console.WriteLine("Original Message:{0}", genuineMessage);
                    Console.WriteLine("Round Trip: {0}",tripRound);
                    Console.WriteLine(" The encrypted message is (byte - byt - byte view): {0}", PrintByteArray(encrypted));
                    Console.WriteLine(" The encrypted message is (default view): {0}", Encoding.Default.GetString(encrypted));
                    Console.WriteLine(" The encrypted message is (UTF8 view): {0}", Encoding.UTF8.GetString(encrypted));
                    Console.WriteLine(" The encrypted message is (UTF32 view): {0}", Encoding.UTF32.GetString(encrypted));
                    Console.ReadKey();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("There is an error:{0}", e.Message);
            }
        }
        //** processing byte values to display them
        static string PrintByteArray(byte[] encrypted_message)
        {
            var build_string = new StringBuilder("new byte[] { ");
            foreach (var each_byte in encrypted_message)
            {
                build_string.Append(each_byte + " ");
            }
            build_string.Append("}");
            return build_string.ToString();
        }
        static byte[] EncryptStringToBytes(string genuineText, byte[] encryption_key, byte[] initialization_vector)
        {
            //** verify the arguments
            if (genuineText == null ||
            genuineText.Length <= 0)
                throw new ArgumentNullException("genuineText");
            if (encryption_key == null ||
                encryption_key.Length <= 0)
                throw new ArgumentNullException("encryption_key");
            if (initialization_vector == null ||
                initialization_vector.Length <= 0)
                throw new ArgumentNullException("IV");
            byte[] encryption_content;
            //** Create an RijndaelManaged object
            //**  with the specified key and IV.
            using (RijndaelManaged rijndaelAlgorithm = new RijndaelManaged())
            {
                rijndaelAlgorithm.Key = encryption_key;
                rijndaelAlgorithm.IV = initialization_vector;
                //** Create an encryptor to perform
                //** the stream transform.
                ICryptoTransform encryptorTransformation = rijndaelAlgorithm.CreateEncryptor(rijndaelAlgorithm.Key, rijndaelAlgorithm.IV);
                //** Create the streams used for encryption
                using (MemoryStream memoryStreamEncrypt = new MemoryStream())
                {
                    using (CryptoStream cryptoStreamEncrypt = new CryptoStream(memoryStreamEncrypt, encryptorTransformation, CryptoStreamMode.Write))
                    {
                        using (StreamWriter streamWriterEncrypt = new StreamWriter(cryptoStreamEncrypt))
                        {
                            //** write the entire volume of
                            //** data to the stream
                            streamWriterEncrypt.Write(genuineText);
                        }
                        encryption_content = memoryStreamEncrypt.ToArray();
                    }
                }
            }
            //** get the encrypted bytes
            //** from the memory stream.
            return encryption_content;
        }
        static string DecryptStringFromBytes(byte[] encrypted_text, byte[] encryption_key, byte[] initialization_vector)
        {
            //** verify the arguments
            if (encrypted_text == null ||
                encrypted_text.Length <= 0)
                throw new ArgumentNullException("encrypted_text");
            if (encryption_key == null ||
                encryption_key.Length <= 0)
                throw new ArgumentNullException("encryption_key");
            if (initialization_vector == null ||
                initialization_vector.Length <= 0)
                throw new ArgumentNullException("initialization_vector");
            //** Declare the string used to hold
            //** the decrypted text.
            string original_text = null;
            //** Create an RijndaelManaged object
            //** with the specified key and IV.
            using (RijndaelManaged rijndael_algorithm = new RijndaelManaged())
            {
                rijndael_algorithm.Key = encryption_key;
                rijndael_algorithm.IV = initialization_vector;
                //** Create a decryptor to
                //** perform the stream transform.
                ICryptoTransform decryptionTransformation = rijndael_algorithm.CreateDecryptor(rijndael_algorithm.Key, rijndael_algorithm.IV);
                //** Create the streams used for decryption.
                using (MemoryStream memoryStreamDecryption = new MemoryStream(encrypted_text))
                {
                    using (CryptoStream cryptoStreamDecrypt = new CryptoStream(memoryStreamDecryption, decryptionTransformation, CryptoStreamMode.Read))
                    {
                        using (StreamReader streamReaderDecryption = new StreamReader(cryptoStreamDecrypt))
                        {
                            //** Read the decrypted bytes
                            //** from the decrypting stream
                            //** and place them in a string.
                            original_text = streamReaderDecryption.ReadToEnd();
                        }
                    }
                }
            }
            return original_text;
        }
    }
}
Listing 7-2

RijndaelManaged Implementation

../images/493660_1_En_7_Chapter/493660_1_En_7_Fig3_HTML.jpg
Figure 7-3

Example of RijndaelManaged

  • In the following example, we will do two cases of implementation for the DES algorithm. Despite the fact that is overcomed, it is a good example of how cryptographic algorithms and primitives should be treated practically. What makes these two implementations different from the rest of the implementations that exist in real practice is that the first example (see Figure 7-4, Figure 7-5, and Listing 7-3) is doing the encryption/decryption within a file and the second example (see Figure 7-6 and Listing 7-4) is doing the encryption/decryption in memory. When we are dealing with complex systems that use files (such as clear JSON files for Hadoop systems or big data environments), one of the solutions is to design the cryptographic primitives in such way that we are doing the encryption/decryption properly of the files or in the memory without exposing something that could impact the business.

../images/493660_1_En_7_Chapter/493660_1_En_7_Fig4_HTML.jpg
Figure 7-4

DES algorithm with encryption/decryption in a file

../images/493660_1_En_7_Chapter/493660_1_En_7_Fig5_HTML.jpg
Figure 7-5

DES encryption result

../images/493660_1_En_7_Chapter/493660_1_En_7_Fig6_HTML.jpg
Figure 7-6

DES encryption and decryption in memory

using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;
class DESSample
{
    static void Main()
    {
        try
        {
            //** instance of the DES algorithm and
            //** with help of Create() method
            //** an initialization vector is generated
            DES des_algorithm = DES.Create();
            //** declare a string for being encrypted
            string plaintext = "Welcome to Apress. Enjoy reading.";
            string file_name = "encrypted_file.txt";
            //** do the encryption to the file based on
            //** the file name, key, and initialization vector
            DesEncryptionToFile(plaintext, file_name, des_algorithm.Key, des_algorithm.IV);
            //** Decrypt the text from a file using the file name, key, and IV.
            string ciphertext = DesDecryptionFromFile(file_name, des_algorithm.Key, des_algorithm.IV);
            //** Show in the console the results
            Console.WriteLine("The message for encryption is: {0}", plaintext);
            Console.WriteLine("The message has been encrypted with success.");
            Console.WriteLine(" Check your file "{0}" at the following location: {1}", file_name, Path.GetFullPath("encrypted_file.txt"));
            Console.ReadKey();
        }
        catch (Exception exception)
        {
            Console.WriteLine(exception.Message);
        }
    }
    public static void DesEncryptionToFile(String text_to_encrypt, String file_name, byte[] encryption_key, byte[] initialization_vector)
    {
        try
        {
            //** create the file or open the file if exist
            FileStream file_stream = File.Open(file_name, FileMode.OpenOrCreate);
            //** declare a DES object
            DES des_algorithm = DES.Create();
            //** use the key and initialization vector.
            //** pass them to the CryptoStream together with the file stream
            CryptoStream crypto_stream = new CryptoStream(file_stream,
                des_algorithm.CreateEncryptor(encryption_key, initialization_vector),
                CryptoStreamMode.Write);
            //** based on a crypto_stream create an instance of stream writer
            StreamWriter stream_writer = new StreamWriter(crypto_stream);
            //** take data and write it within the stream writer
            stream_writer.WriteLine(text_to_encrypt);
            //** make sure that the file stream, crypto stream and stream writer are closed
            stream_writer.Close();
            crypto_stream.Close();
            file_stream.Close();
        }
        catch (CryptographicException exception){
            Console.WriteLine("There is an error regarding your encryption cryptographic process: {0}", exception.Message);
        }
        catch (UnauthorizedAccessException exception){
            Console.WriteLine("There is an error regarding creating/accessing the file: {0}", exception.Message);
        }
    }
    public static string DesDecryptionFromFile(String file_name, byte[] decryption_key, byte[] initialization_vector)
    {
        try
        {
            //** create the file or open the file if exist
            FileStream file_stream = File.Open(file_name, FileMode.OpenOrCreate);
            //** declare a DES object
            DES des_algorithm = DES.Create();
            //** use the key and initialization vector.
            //** pass them to the CryptoStream together with the file stream
            CryptoStream crypto_stream = new CryptoStream(file_stream,
                des_algorithm.CreateDecryptor(decryption_key, initialization_vector),
                CryptoStreamMode.Read);
            //** based on a crypto_stream create an instance of stream reader
            StreamReader stream_reader = new StreamReader(crypto_stream);
            //** before decryption take place
            //** we need to read the data from the stream
            string val = stream_reader.ReadLine();
            //** make sure that the file stream, crypto stream and stream writer are closed
            stream_reader.Close();
            crypto_stream.Close();
            file_stream.Close();
            //** return the decryption value
            return val;
        }
        catch (CryptographicException cryptoException)
        {
            Console.WriteLine("There was a cryptographic error. Please see: {0}. Correct the error and try again.", cryptoException.Message);
            return null;
        }
        catch (UnauthorizedAccessException unauthorizedException)
        {
            Console.WriteLine("There was an error with the file (unauthorized/existance). Please, check: {0}", unauthorizedException.Message);
            return null;
        }
    }
}
Listing 7-3

Example of Implementation of the DES Algorithm Using DESCryptoServiceProvider

using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;
class DESMemoryImplementationExample
{
    static void Main()
    {
        try
        {
            //** declare a DES instance and with Create()
            //** generate the key and initialization vector
            DES des_algorithm = DES.Create();
            //** declare a variable that contains the string to encrypt
            string data_to_encrypt = "Welcome to Apress. Enjoy your adventure.";
            //** use a memory buffer and proceed encrypting the text
            byte[] encrypted_data = EncryptionOfTextInMemory(data_to_encrypt, des_algorithm.Key, des_algorithm.IV);
            //** use the buffer and proceed with decryption and obtain the plaintext
            string decrypted_data = DecryptionOfTheTextFromMemory(encrypted_data, des_algorithm.Key, des_algorithm.IV);
            //** show in the console the encrypted and decrypted values
            Console.WriteLine("The original message is: {0}", data_to_encrypt);
            Console.WriteLine(" The encrypted message is (byte-by-byte view): {0}", PrintByteArray(encrypted_data));
            Console.WriteLine(" The encrypted message is (default view): {0}", Encoding.Default.GetString(encrypted_data));
            Console.WriteLine(" The encrypted message is (UTF8 view): {0}", Encoding.UTF8.GetString(encrypted_data));
            Console.WriteLine(" The encrypted message is (UTF32 view): {0}", Encoding.UTF32.GetString(encrypted_data));
            Console.WriteLine(" The original text is: {0}.", decrypted_data);
            Console.ReadKey();
        }
        catch (Exception general_exception)
        {
            Console.WriteLine(general_exception.Message);
        }
    }
    static string PrintByteArray(byte[] encrypted_message)
    {
        var build_string = new StringBuilder("new byte[] { ");
        foreach (var each_byte in encrypted_message)
        {
            build_string.Append(each_byte + " ");
        }
        build_string.Append("}");
        return build_string.ToString();
    }
    public static byte[] EncryptionOfTextInMemory(string data_to_encrypt, byte[] key_for_encryption, byte[] initialization_vector)
    {
        try
        {
            //** declare an instance for the memory buffer
            MemoryStream memory_stream = new MemoryStream();
            //** declare an instance of DES
            DES DESalg = DES.Create();
            //** declare an crypto stream instance and use
            //** the key used for encryption and the initialization vector
            CryptoStream crypto_stream = new CryptoStream(memory_stream,
                DESalg.CreateEncryptor(key_for_encryption, initialization_vector),
                CryptoStreamMode.Write);
            //** the string that has been passed will be converted to a byte array
            byte[] for_encryption = new ASCIIEncoding().GetBytes(data_to_encrypt);
            //** take the byte array and write it in the crypto stream
            crypto_stream.Write(for_encryption, 0, for_encryption.Length);
            //** don't forget to flush it
            crypto_stream.FlushFinalBlock();
            //** take the memory stream and write
            //** it as an array of bytes and store it accordingly
            byte[] stream_content_byteArray = memory_stream.ToArray();
            //** make sure to have the streams closed
            crypto_stream.Close();
            memory_stream.Close();
            //** the buffer with the encrypted content
            return stream_content_byteArray;
        }
        catch (CryptographicException cryptoException)
        {
            Console.WriteLine("There was an cryptographic error expected. Please see: {0}", cryptoException.Message);
            return null;
        }
    }
    public static string DecryptionOfTheTextFromMemory(byte[] data_for_decryption, byte[] decryption_key, byte[] initialization_vector)
    {
        try
        {
            //** declare an memory stream instance used for decryption
            //** and as parameter pass the encrypted data
            MemoryStream memory_stream_decryption = new MemoryStream(data_for_decryption);
            //** create an instance of DES object
            DES DESalg = DES.Create();
            //** declare an crypto stream instance based on
            //** the memory stream instance and pass as
            //** parameters the decryption and initialization vector
            CryptoStream crypto_stream_decryption = new CryptoStream(memory_stream_decryption,
                DESalg.CreateDecryptor(decryption_key, initialization_vector),
                CryptoStreamMode.Read);
            //** declare a buffer and we will use it
            //** to store the decrypted data
            byte[] from_encryption = new byte[data_for_decryption.Length];
            //** proceed reading the decrypted data from the crypto stream
            //** and store its content in a temporary buffer
            crypto_stream_decryption.Read(from_encryption, 0, from_encryption.Length);
            //** do the conversion of the buffer in a string
            //** and return its value
            return new ASCIIEncoding().GetString(from_encryption);
        }
        catch (CryptographicException cryptoException)
        {
            Console.WriteLine("There was an cryptographic error. Please see: {0}", cryptoException.Message);
            return null;
        }
    }
}
Listing 7-4

Implementation of DES Encryption and Decryption in Memory

Encryption Using a Public Key

Public key encryption uses a private key that must be stored secretly in such way that unauthorized parties or malicious users cannot access it. Also, it is uses a public key which can be made available to anyone. The relation between the public key and the private key is mathematically based. The data that is encrypted using the public key can only be decrypted using the private key. In this situation, the data that has been signed using the private key can be verified using only the public key.

Let’s consider two parties (classical noted as Alice and Bob, as shown in Figure 7-7) who will use a public-key encryption as described: Alice will generate a pair formed of a public key and private key. When Bob decides to send Alice an encrypted message, he will need the public key from Alice, so he will ask for it. Alice will send Bob her public key over a nonsecure network communication channel. Bob will use it to encrypt the message. Bob will send the encrypted message to Alice, who will decrypt it using her private key.
../images/493660_1_En_7_Chapter/493660_1_En_7_Fig7_HTML.jpg
Figure 7-7

Public-key encryption

The following list shows a comparison between public-key and private-key algorithms:
  • Algorithms based on a public key use a fixed buffer size. It is very important to mention that the secret key and algorithms based on it use a buffer that is represented as a variable and the size varies.

  • Regarding the chain of data, there is a difference between public-key algorithms and symmetric-key algorithms. Symmetric-key algorithms chain data together into streams, and public-key algorithms don’t have this ability. The streaming model is not the same for the asymmetric operations as for symmetric operations.

  • Encryption using a public key uses a larger space for the key representation. The space used by symmetric-key encryptions is much smaller compared with the public key encryptions.

  • Public keys are very easy to distribute because there is no need to have a security mechanism. If necessary, there are mechanisms to verify and check the identity of the sender.

  • To create a digital signature with the goal of verifying and checking the identity of the sender, public-key algorithms such as RSA and DSA can be used with success.

  • Regarding speed, public-key algorithms are very slow if we compare them with symmetric algorithms. They are designed in such way that no large amount of data can be encrypted. Thus, public-key algorithms are recommended only for a small amount of data.

The .NET Framework contains the following classes, which already contain implementations for algorithms and operations for public-key encryption :
  • DSACryptoServiceProvider [1]. The implementations are similar and they can be done in the same way as the ones from Listing 7-1, 7-2, or 7-3.

  • RSACryptoServiceProvider [2]. For an example of an implementation, see Listing 7-5 and Figure 7-8.

  • ECDiffieHellman [3]. The implementations are similar and they can be done in the same way as the ones from Listing 7-1, 7-2, or 7-3.

  • ECDiffieHellmanCng [4]. The implementations are similar and they can be done in the same way as the ones from Listing 7-1, 7-2, or 7-3.

  • ECDiffieHellamnCngPublicKey [5]. The implementations are similar and they can be done in the same way as the ones from Listing 7-1, 7-2, or 7-3.

  • ECDiffieHellmanKeyDerivationFunction [6]. The implementations are similar and they can be done in the same way as the ones from Listing 7-1, 7-2, or 7-3.

  • ECDsaCng [7]. For an example of an implementation of ECDSA (Elliptic Curve Digital Signature Algorithm), see Listing 7-5 .

../images/493660_1_En_7_Chapter/493660_1_En_7_Fig8_HTML.jpg
Figure 7-8

RSA example encryption

using System;
using System.ComponentModel;
using System.Security.Cryptography;
using System.Text;
class RSAExampleOfImplementation
{
    static void Main()
    {
        try
        {
            //** the object will be used for
            //** conversion between the bytes
            //** of the array and string
            UnicodeEncoding byte_converter = new UnicodeEncoding();
            //** Create byte arrays to hold original,
            //** encrypted, and decrypted data.
            string plaintext = "Hi, Apress!";
            byte[] data_to_be_encrypted = byte_converter.GetBytes(plaintext);
            byte[] encrypted_data;
            byte[] decrypted_data;
            //** declare a new object of
            //** RSACryptoServiceProvider
            //** to generate the public
            //** and private key data.
            using (RSACryptoServiceProvider rsa_algorithm = new RSACryptoServiceProvider())
            {
                //** the data to be encrypted
                //** will be passed
                //** to EncryptionWithRSA along with the
                //** informations related to public key
                encrypted_data = EncryptionWithRSA(data_to_be_encrypted, rsa_algorithm.ExportParameters(false), false);
                //** the data to be encrypted
                //** will be passed to DecryptionWithRSA
                //** along with the informations related //** to public key
                decrypted_data = DecryptionWithRSA(encrypted_data, rsa_algorithm.ExportParameters(true), false);
                //** shows in the console the
                //** decryption of the plaintext
                Console.WriteLine("The plaintext for encryption is: {0}", plaintext);
                Console.WriteLine(" Plaintext for encryption is: {0}", PrintByteArray(data_to_be_encrypted));
                Console.WriteLine(" Decrypted plaintext: {0}", byte_converter.GetString(decrypted_data));
                Console.ReadKey();
            }
        }
        catch (ArgumentNullException)
        {
            //** in case that something is going wrong with
            //** the encryption, catch the exception
            Console.WriteLine("Encryption has failed.");
        }
    }
    static string PrintByteArray(byte[] encrypted_message)
    {
        var build_string = new StringBuilder("new byte[] { ");
        foreach (var each_byte in encrypted_message)
        {
            build_string.Append(each_byte + " ");
        }
        build_string.Append("}");
        return build_string.ToString();
    }
    public static byte[] EncryptionWithRSA(byte[] data_to_be_encrypted, RSAParameters rsa_key_info, bool oaep_padding)
    {
        try
        {
            byte[] encrypted_data;
            //** declare a new instance of
            //** RSACryptoServiceProvider.
            using (RSACryptoServiceProvider rsa_csp = new RSACryptoServiceProvider())
            {
                //** do the import for RSA Key information
                rsa_csp.ImportParameters(rsa_key_info);
                //** the byte passed array
                //** will be encrypted
                //** and mention OAEP padding
                encrypted_data = rsa_csp.Encrypt(data_to_be_encrypted, oaep_padding);
            }
            return encrypted_data;
        }
        //** in case that something is going wrong with
        //** the encryption, catch the exception
        catch (CryptographicException exception)
        {
            Console.WriteLine(exception.Message);
            return null;
        }
    }
    public static byte[] DecryptionWithRSA(
        byte[] data_to_be_decrypted,
        RSAParameters rsa_key_info,
        bool oaep_padding)
    {
        try
        {
            byte[] decrypted_data;
            //** declare a new instance of
            //** RSACryptoServiceProvider.
            using (RSACryptoServiceProvider rsa_csp = new RSACryptoServiceProvider())
            {
                //** do the import for RSA Key information
                rsa_csp.ImportParameters(rsa_key_info);
                //** the byte passed array will be decrypted
                //** and mention OAEP padding
                decrypted_data = rsa_csp.Decrypt(data_to_be_decrypted,     oaep_padding);
            }
            return decrypted_data;
        }
        //** in case that something is going wrong with
        //** the encryption, catch the exception
        catch (CryptographicException exception)
        {
            Console.WriteLine(exception.ToString());
            return null;
        }
    }
}
Listing 7-5

Implementation of RSA Using RSACryptoServiceProvider

Digital Signature

Public-key algorithms are used to form digital signatures. The role of digital signatures is to authenticate the real identity of the sender and to offer protection regarding the integrity of the data. See Figure 7-9 for the digital signature path.
../images/493660_1_En_7_Chapter/493660_1_En_7_Fig9_HTML.jpg
Figure 7-9

ECDsaCng example

.NET Framework offers the following classes for implementing digital signature algorithms :
  • DSACryptoServiceProvider [8]

  • RSACryptoServiceProvider [9]

  • ECDsa [10]. See Listing 7-6 and Figure 7-10 for details about implementation.

  • ECDsaCng [11]. See Listing 7-6 and Figure 7-10 for details about implementation.

../images/493660_1_En_7_Chapter/493660_1_En_7_Fig10_HTML.jpg
Figure 7-10

Digital signature

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
class AliceUser
{
    public static void Main(string[] args)
    {
        BobUser bob = new BobUser();
        using (ECDsaCng dsaAlgorithm = new ECDsaCng())
        {
            dsaAlgorithm.HashAlgorithm = CngAlgorithm.Sha256;
            bob.theKey = dsaAlgorithm.Key.Export(CngKeyBlobFormat.EccPublicBlob);
            byte[] dataStructure = new byte[] { 21, 5, 8, 12, 207 };
            byte[] the_signature = dsaAlgorithm.SignData(dataStructure);
            bob.Receive(dataStructure, the_signature);
        }
    }
}
public class BobUser
{
    public byte[] theKey;
    public void Receive(byte[] data, byte[] the_signature)
    {
        using (ECDsaCng ecsdKey = new ECDsaCng(CngKey.Import(theKey, CngKeyBlobFormat.EccPublicBlob)))
        {
            if (ecsdKey.VerifyData(data, the_signature))
                Console.WriteLine("Data is good");
            else
                Console.WriteLine("Data is bad");
        }
    }
}
Listing 7-6

Implementation of ECDsaCng

Hash Values

The purpose of a hash algorithm is to map binary values that are represented as having an arbitrary length to smaller binary values that have a fixed length. A hash value is represented as a numerical value that represents a specific piece of data.

The .NET Framework has the following classes already implemented, which help us to get a quick implementation of the hashing algorithms:
  • HMACSHA1 [12]

  • MACTripleDES [13]

  • MD5CryptoServiceProvider [14]

  • RIPEMD160 [15]

  • SHA1Managed [16]

  • SHA256Managed [17]

  • SHA384Managed [18]

  • SHA512Managed [19]

For examples of implementations, let’s look at RIPEMD160 (see Listing 7-7 and Figure 7-11) and SHA256Managed (see Listing 7-8 and Figure 7-12).
using System;
using System.IO;
using System.Security.Cryptography;
using System.Windows.Forms;
public class RIPEMD160Example
{
    [STAThreadAttribute]
    public static void Main(String[] args)
    {
        string directory_path = "";
        if (args.Length < 1)
        {
            FolderBrowserDialog folder_browser_dialog = new FolderBrowserDialog();
            DialogResult dialog_result = folder_browser_dialog.ShowDialog();
            if (dialog_result == DialogResult.OK)
            {
                directory_path = folder_browser_dialog.SelectedPath;
            }
            else
            {
                Console.WriteLine("There is no directory selected.");
                return;
            }
        }
        else
        {
            directory_path = args[0];
        }
        try
        {
            //** store the selected directory
            DirectoryInfo directoryInfo = new DirectoryInfo(directory_path);
            //** select the informations of
            //** each file from the folder
            FileInfo[] files = directoryInfo.GetFiles();
            //** declare a RIPEMD160 object
            RIPEMD160 myRIPEMD160 = RIPEMD160Managed.Create();
            byte[] hash_value;
            //** get the hash value of each file
            //** from the selected directory
            foreach (FileInfo infoOfTheFile in files)
            {
                //** declare a file stream for the file
                FileStream stream_file = infoOfTheFile.Open(FileMode.Open);
                //** put its position at the
                //** beginning of the stream
                stream_file.Position = 0;
                //** for the declared stream
                //** calculate the hash value
                hash_value = myRIPEMD160.ComputeHash(stream_file);
                //** show the name of the value in the console
                Console.Write(infoOfTheFile.Name + ": ");
                //** show the hash value in the console
                ShowReadableByteArray(hash_value);
                //** make sure that the file will
                //** closed properly
                stream_file.Close();
            }
            return;
            Console.ReadKey();
        }
        catch (DirectoryNotFoundException)
        {
            Console.WriteLine("Error: The selected directory could not be found.Try again!.");
        }
        catch (IOException)
        {
            Console.WriteLine("Error: There is a problem with the access of the file.");
        }
    }
    //** show the byte array in a possible readable format
    public static void ShowReadableByteArray(byte[] byteArray)
    {
        for (int k = 0; k < byteArray.Length; k++)
        {
            Console.Write(String.Format("{0:X2}",byteArray[k]));
            if ((k % 4) == 3)
                Console.Write(" ");
        }
        Console.WriteLine();
    }
}
Listing 7-7

RIPEMD160 Implementation

../images/493660_1_En_7_Chapter/493660_1_En_7_Fig11_HTML.jpg
Figure 7-11

RIPEMD160 example with a hash value for an *.iso file

../images/493660_1_En_7_Chapter/493660_1_En_7_Fig12_HTML.jpg
Figure 7-12

Example of SHA256 implementation

using System;
using System.IO;
using System.Security.Cryptography;
public class HASH256ManagedImplementation
{
    public static void Main(String[] args)
    {
        string path_to_folder = @"D:SHA256FileExample";
        //if (args.Length < 1)
        //{
        //    Console.WriteLine("There is no directory selected.");
        //    return;
        //}
        string directoryPathSelected = path_to_folder;
        if (Directory.Exists(directoryPathSelected))
        {
            //** the directory that has been selected
            var theDirectory = new DirectoryInfo(directoryPathSelected);
            //** get details about every file
            //**in the directory
            FileInfo[] theFiles = theDirectory.GetFiles();
            //** declare an instance of SHA256
            using (SHA256 theSha256 = SHA256.Create())
            {
                //** calculate the hash value
                //** of each of the files
                foreach (FileInfo informationOfTheFile in theFiles)
                {
                    try
                    {
                        //** declare a stream for the file
                        FileStream file_stream = informationOfTheFile.Open(FileMode.Open);
                        //** set the cursor at the
                        //** beginning of the stream
                        file_stream.Position = 0;
                        //** get the hash of the stream
                        byte[] the_hash_value =
                        theSha256.ComputeHash(file_stream);
                        //** show in the console the
                        //** name and hash of the file
                        Console.Write($"{informationOfTheFile.Name}: ");
                        ShowReadableByteArray(the_hash_value);
                        //** make sure that once is
                        //** done the file is closed
                        file_stream.Close();
                    }
                    catch (IOException e)
                    {
                        Console.WriteLine($"There is an Input / Output Exception:{ e.Message}");
                    }
                    catch (UnauthorizedAccessException e)
                    {
                        Console.WriteLine($"There is an error with the access: { e.Message}");
                    }
                }
            }
        }
        else
        {
            Console.WriteLine("The directory cannot be found. Select another one.");
        }
    }
    //** show the byte array in a
    //** possible readable format
    public static void ShowReadableByteArray(byte[] byteArray)
{
    for (int k = 0; k < byteArray.Length; k++)
    {
        Console.Write($"{byteArray[k]:X2}");
        if ((k % 4) == 3) Console.Write(" ");
    }
    Console.WriteLine();
}
}
Listing 7-8

SHA256Managed Implementation

Random Number Generation

Many cryptographic operations are based on the generation of random numbers. As an example, let’s consider cryptographic keys that are required to be random as much as possible in such way that it will impossible to reproduce them.

The RNGCryptoServiceProvider class [20] provides a professional implementation of a cryptographic random number generation (RNG) based on the implementation that is provided by the cryptographic service provided (CSP).

ClickOnce Manifests

ClickOnce [21] is a deployment technology that provides the ability to create a self-updating windows-based application. It offers a minimal user interaction and it is as easy as possible to use.

ClickOnce technology is still used within small and medium businesses. There are challenges due to the security of the signatures and the collection of signatures. Consumer applications are largely developed in UWP or Win32 and use different technologies for the deployment process. Some internal business applications use different deployment technologies these days.

.NET Framework 3.5 offers cryptography classes that allow professionals to get and verify the information of the signatures assigned to the ClickOnce manifests that are deployed. The classes are as follows:
  • ManifestSignatureInformation class [22]. Used to obtain information related to the manifest signature. This can be achieved with the help of the VerifyMethod method. An example method for verifying the signature of a manifest can be seen in Listing 7-9.

  • ManifestKinds enumeration [23]. This enumeration is used to mention which manifests should be checked. The output of the checking process is represented by an enumeration value within SignatureVerificationResult enum type [24].

  • ManifestSignatureInformationCollection class [25]. The class provides guidance related to the read-only collection of the objects of the ManifestSignatureInformation class [26] for verifying the signatures.

public static System.Security.Cryptography.
      ManifestSignatureInformationCollection
      VerifyingTheSignature
           (ActivationContext nameOfApplication,
           System.Security.ManifestKinds listOfManifests,
           System.Security.Cryptography.X509Certificates.
                      X509RevocationFlag theRevocationFlag,
           System.Security.Cryptography.X509Certificates.
                      X509RevocationMode theRevocationMode);
Listing 7-9

Verifying the Signature with X509

The following classes provide information about signatures:
  • StrongNameSignatureInformation class [27]. The class holds the (strong) name of the signature and its information for a specific manifest.

  • AuthenticodeSignatureInformation class [28]. Represents a special type of signature, called authenticode, which is contained together with the signature information for a specific manifest.

  • TimestampInformation class [29]. Gets the time stamp on an authenticode signature.

  • TrustStatus enum. Checks if an authenticode signature can be trusted or not .

Suite B Support

.NET Framework 3.5 offers support for Suite B , an important set of cryptographic algorithms published by the National Security Agency (NSA). In 2018, NSA replaced Suite B with the Commercial National Security Algorithm Suite (CNSA) [31].

Here are some of algorithms that are included and recognized:
  • Advanced Encryption Standard (AES) algorithm. Support for key sizes 128, 192, and 256.

  • Secure Hash Algorithm SHA-1, SHA-256, SHA-384, and SHA-512

  • Elliptic Curve Digital Signature Algorithm (ECDSA). There is support for 256-bit, 384-bit, and 521-bit curves.

  • Elliptic Curve Diffie-Hellman (ECDH) algorithm. There is support for 256-bit, 384-bit, and 521-bit curves. This is used for key exchanges and secret agreements.

Cryptography Next Generation Classes

The CNG classes offer support for managed wrappers related to native CNG operations and functions. It is very important to mention that CNG is the replacement for CryptoAPI. All of the classes contain “CNG” as part of their name. The main class or CNG wrapper is CngKey [32], which is a container class that contains an abstraction of the storage and the way the CNG keys will be used.

With the help of this class, we can store a public key or key pair in a secure way and have the possibility of referring to it based on a string name. Classes related to elliptic curves, such as ECDsaCng for signatures and ECDiffieHellmanCng [33] for encryption operations, can use CngKey instances [34].

.NET Framework 3.5 supports different CNG classes, such as
  • CngProvider class [35]. Deals with the provider for key storage.

  • CngAlgorithm class [36]. Deals with the CNG algorithm.

  • CngProperty class [37]. Contains key properties that are quite frequently used .

Conclusion

The chapter covered the most important cryptographic services from the .NET Framework, starting with version 3.5. Professionals can perform complex implementation tasks of different cryptographic algorithms and providers.

The chapter provided a short and comprehensive guide on how to implement cryptographic services and providers available in .NET without having to implement them from scratch. The cryptographic services and providers covered were
  • Symmetric encryption key algorithms

  • Asymmetric encryption key algorithms

  • Digital signatures

  • Hash algorithms and working with their values

  • Random number generation

  • ClickOnce manifests and solutions for checking their identities

  • Suite B support and its main role

Bibliography

  1. [1]
     
  2. [2]
     
  3. [3]
     
  4. [4]
     
  5. [5]
     
  6. [6]
     
  7. [7]
     
  8. [8]
     
  9. [9]
     
  10. [10]
     
  11. [11]
     
  12. [12]
     
  13. [13]
     
  14. [14]
     
  15. [15]
     
  16. [16]
     
  17. [17]
     
  18. [18]
     
  19. [19]
     
  20. [20]
     
  21. [21]
     
  22. [22]
     
  23. [23]
     
  24. [24]
     
  25. [25]
     
  26. [26]
     
  27. [27]
     
  28. [28]
     
  29. [29]
     
  30. [30]
     
  31. [31]

    Commercial National Security Algorithm. Available online: https://apps.nsa.gov/iaarchive/programs/iad-initiatives/cnsa-suite.cfm.

     
  32. [32]
     
  33. [33]
     
  34. [34]
     
  35. [35]
     
  36. [36]
     
  37. [37]
     
  38. [38]
     
  39. [39]
     
  40. [40]
     
  41. [41]
     
  42. [42]
     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.181.154