Cybersecurity of External Streaming Data - Confidentiality

Introduction

The external data is recognized as the data we must pull or push from outside of a boundary of the process hosting the computer program. In general, the external data may be grouped as follows.

  • streaming: bitstreams managed using content of files, or network payload.
  • structural: data fetched/pushed from/to external database management systems using queries.
  • graphical: data rendered on a Graphical User Interface (GUI).

This article covers selected topics related to the cybersecurity of streaming data. For this kind of data, bitstreams are the most popular because they are used as file content and for network communication (see also External Data Management (ExDM)).

Cybersecurity describes the practice of protecting computer systems, networks, and data from cyber threats. Cybersecurity is sometimes spelled "cyber security" but there are no major differences in the meaning. Some sources may prefer one spelling over the other. Still, both terms are widely accepted and used interchangeably. In this article, only cybersecurity related to bitstreams is considered. Precisely we will talk about using cryptography algorithms to protect the security of bitstreams.

First, let me remind you of the issues we must address in this respect. We have three of them. The first one is to ensure that all users of a bitstream can verify that it was not modified while being archived or transmitted. It is the main topic of the Cybersecurity of External Streaming Data - Integrity article.

The second goal is to ensure bitstream confidentiality. Confidentiality in the context of bitstream cybersecurity refers to the protection of sensitive information from unauthorized access or disclosure. Let me stress that it doesn't mean selective availability of the bitstream itself. The main goal of the confidentiality implementation is that only authorized individuals or systems can access information associated with the bitstream. It is the main topic of this article.

The third goal is the non-repudiation of the author. These two goals will be covered in the following articles in this cycle. Follow me to get the full story related to non-repudiation implementation in practice.

Now let's talk about securing bitstreams using cryptography algorithms. Cryptography is a broad concept, but we will focus only on selected, very practical aspects related to the security of bitstreams implementation. Discussion of the cryptography algorithms is out of this article's scope.

We already know how to create bitstreams. We can also attach coding to them, i.e. the natural language alphabet and recognizing the bitstream as text. The next step is to assign syntax and semantics that allow the streams to be transformed into a coherent document that allows the association of information with these documents by a computer user. If this is not enough, we can also display these documents in graphical form.

However, from the security point of view, the most important thing is that always we must deal with bitstreams as a sequence of bits that can be sent, archived, and processed by another computer. It must be stressed again that this infrastructure is always binary. Well, this is where the problem arises. It is required that this binary document is protected against malicious operations. For example, if this document contains a transfer order to our bank, the problem becomes real, material, and meaningful in this context.

It often happens that only authorized persons should have access to the information represented by a bitstream, and therefore unauthorized persons shouldn't have access to it. To address this requirement, we may use the bidirectional transformation mechanism to replace a source bitstream with another bitstream to which we can no longer attach the encoding, syntax, and semantics rules. As a result, it makes it impossible to associate information with this bitstream. The obtained from this scrambling bitstream resembles white noise.

However, any person who has the right to access the associated with the source bitstream information; should be able to recover the source bitstream and as a result associate back the encoding, syntax, and semantics rules and finally recover the information represented by the source bitstream. The process is similar to replacing music with noise but granting at the same time the possibility to recover music from that notice by the authorized user. Unauthorized users can hear only noise, but authorized users can transform the notice back to the original music. This reversible transformation function we will call encryption.

In this article on the cryptographic security of bitstreams, the encryption concept is addressed. Thanks to the hash function, we can secure the integrity of the controlled bitstream, provided that we can transfer the hash value to the destination in such a way that malicious users cannot modify it. Otherwise, modifying the source stream is not a problem because calculating a new hash function value that takes this modification into account is quite a trivial operation.

Hence, selective access is required to protect any bitstream including but not limited to hash value against unauthorized access. Selective access is the ability to access information that is associated with a bitstream only by people who are authorized to do so. We can accomplish this in two ways.

  • selective availability of the bitstream itself
  • selective availability of the bitstream meaning

The first approach is to share the bitstream, for example, as a file, only with people who have the right to get access to it. This can be achieved thanks to the authentication and authorization offered by most operating systems. Authorization in the context of an operating system refers to granting or denying permissions to identity attempting to perform certain operations on a computer system. Thanks to this, each time an attempt is made to operate on a file, it is first checked whether the identity that requested the execution of an operation has the right to do so. Of course, if someone does not gain access to the bitstream (to the file content), he will necessarily not have access to the information that is associated with this bitstream.

Unfortunately, this approach is possible only if something is trusted in the middle between the file and the user, for example, a well-configured operating system. Generally, this article doesn't deal with operating systems implementation, so this approach is outside the scope of our interest. Hence, we have to deal with another security method.

The second option is to transform a bitstream (for example the file content, hash function value, etc.) into a form that an unauthorized user cannot associate any meaning with this bitstream. This method we call encryption. In other words, encryption involves transforming or scrambling bitstreams to make the underlying information unavailable to unauthorized users.

Encryption fundamentals

Encryption is a reversible bitstream transformation function into another bitstream. The transformation or scrambling function rearranges or modifies the order of bits in a bitstream. This function introduces complexity and randomness into the data, making it difficult for unauthorized parties to interpret or understand without the appropriate decryption process. The goal is to enhance the security of the information being transmitted or stored. After encryption, the encoding, syntax, and semantics rules no longer apply to the encryption function output bitstream. So, as a consequence, no information can be associated with the obtained this way bitstream. The diagram below shows how the encryption function works.

Encryption fundamentals

The result of this encryption function (Fe) depends on the K1 key. The K1 key is also a bitstream. The disadvantage of this solution is that the resulting bitstream is always the same because the Fe is a function. This is easily fixed after adding a few randomly generated bytes to the input stream; the so-called nonce. Thanks to this, the result will be different each time even if the key K1 is the same all the time. This approach protects against the possibility of repetition, i.e. using the same bitstream even without understanding its meaning. To perform the reverse operation, i.e. restore the source bitstream that was originally encrypted, a decryption operation must be performed. For this, we will need the second key marked K2 in the drawing above. If nonce has been added it has to be removed before the bitstream is ready for reusing.

If K1==K2 we have symmetric encryption, otherwise we call the encryption asymmetric.

Symmetric encryption employs a single key for both encryption and decryption operations. The same key is used by both interoperable parties, providing a more efficient process than asymmetric encryption. However, secure key exchange becomes crucial for maintaining confidentiality in symmetric encryption. The next problem with symmetric encryption is scaling. The scaling problem with symmetric encryption arises when a large number of parties need to be part of interoperability securely. In this scenario, each pair of interconnected parties requires a unique symmetric key for encryption and decryption. Managing and securely distributing a large number of keys becomes challenging, impacting the scalability of symmetric encryption in a practical setting.

Asymmetric encryption, also known as public-key cryptography, involves a pair of keys, namely a public key used for encryption and a private key for decryption. Bitstreams encrypted with the public key can only be decrypted by the corresponding private key, ensuring secure interoperability. On the other hand, bitstreams encrypted with the private key can only be decrypted by the corresponding public key.

Symmetric Cryptography Example

It is proposed to analyze the encryption and decryption process using the EncryptDecryptDataTest test method defined in the CryptographyHelpersUnitTest class. In this method, symmetric encryption is used that implements the 3DES algorithm. We will encrypt the selected XML file catalog.example.xml.

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="catalog.xslt"?>
<Catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
         xmlns="http://Viculu34.org/Catalog.xsd"
         >
  <CD>
    <Title>Empire Burlesque</Title>
    <Artist>Bob Dylan</Artist>
    <Country>USA</Country>
    <Company>Columbia</Company>
    <Price>10.90</Price>
    <Year>1985</Year>
  </CD>
  <CD>
    <Title>Hide your heart</Title>
    <Artist>Bonnie Tyler</Artist>
    <Country>UK</Country>
    <Company>CBS Records</Company>
    <Price>9.90</Price>
    <Year>1988</Year>
  </CD>
</Catalog>

The test method must be preceded by an attribute that ensures all necessary files are copied to the test workspace before this method is invoked. First, we check whether this file exists. An assertion must always be true indicating that the file exists. We will save the encrypted result in another file. If this file exists, it is deleted. ProgressMonitor is a local class that will be used to track the progress of encryption and decryption progress. We will come back to this class shortly. The next step is directly related to encryption.

The purpose of the following instructions is to create an object that generates an encryption/decryption key.

TripleDESCryptoServiceProvider _tripleDesProvider = new TripleDESCryptoServiceProvider();

The key consists of two independent parts and can have different lengths depending on your needs. For default parameters, the length is 192 bits.

The next method EncryptData encrypts the input file and places the result in the output file.

    public static void EncryptData(string inFileName, string outFileName, byte[] dESKey, byte[] dESIV, IProgress<long> progress)
    {
      //Create the file streams to handle the input and output files.
      using (FileStream _inFileStream = new FileStream(inFileName, FileMode.Open, FileAccess.Read))
      {
        const int _bufferLength = 100;
        byte[] _buffer = new byte[_bufferLength]; //This is intermediate storage for the encryption.
        long _bytesWritten = 0; //This is the total number of bytes written.
        TripleDESCryptoServiceProvider _tripleProvider = new TripleDESCryptoServiceProvider();
        FileStream _outFileStream = new FileStream(outFileName, FileMode.Create, FileAccess.Write);
        using (CryptoStream _outCryptoStream = new CryptoStream(_outFileStream, _tripleProvider.CreateEncryptor(dESKey, dESIV), CryptoStreamMode.Write))
        {
          //Read from the input file, then encrypt and write to the output file.
          while (true)
          {
            int _length = _inFileStream.Read(_buffer, 0, _bufferLength);
            if (_length == 0)
            {
              _outCryptoStream.FlushFinalBlock();
              break;
            }
            _outCryptoStream.Write(_buffer, 0, _length);
            _bytesWritten = _bytesWritten + _length;
            progress.Report(_bytesWritten);
          }
        }
      }
    }

But to perform the encryption operation, we still need to pass two parameters, a key and an initialization vector. Please note that both, the initialization vector and the key are arrays of bytes, they are simply bitstreams. Where these keys are generated is important because access to these keys guarantees selective access to the bitstream meaning. This means that anyone who has both the key and the initialization vector will be able to decrypt the encrypted bitstream. Although this example doesn't show it, we should take care of the distribution of keys and, of course, the initialization vector. We can treat these two things as one whole.

So let's see how the encryption may be applied. In this example, we are encrypting an XML file used to save a directory containing CD descriptions. Let me stress that the content of the file has no impact on the encryption/decryption process. The most important thing is that it is just a bitstream. The EncryptData encryption method has the following parameters: input file name, output file name, key, and initialization vector. Next, dependency injection is used to allow the invoking method to keep track of the process as the encryption process happens in stages.

First, we open the file to read the input bitstream, which contains the source bitstream that is to be encrypted. The bitstream is encrypted step by step using small chunks and is preserved in a buffer that has a predetermined length. In this case, the buffer is 100 bytes long. Encryption requires the creation of a bitstream complying with the CryptoStream type. To create its instance, we will need an object for which we pass the key and an initialization vector. The encryption itself is performed using the Write method, which writes bytes from the local buffer to the CryptoStream object. After saving, we inform the invoking party about the process progress returning the total number of bytes that are saved in this step to monitor the progress of work. Please note here that although the source file is a text file, we treat it as a bitstream. Associated encoding with this file content is not important and can be neglected. So, to read it, we create an object of the FileStream type because - as it was said - encrypting the encoding of the input file is irrelevant. In other words, encryption is always performed for the bitstreams. The encryption process ends when we read zero bytes into the buffer.

Then in the test method, after encrypting the source file, we check that the output file exists. There is an assertion that checks that this file exists and finally, we check if the number of bytes in the source file is equal to the number reported and written in the output file.

And now we move on to the step where the file is decrypted. That one we created. The entire procedure is carried out in the DecryptData method.

    public static void DecryptData(string inCryptoFileName, string outFileName, byte[] dESKey, byte[] dESIV, IProgress<long> progress)
    {
      //Create the file streams to handle the input and output files.
      using (FileStream _outFileStream = new FileStream(outFileName, FileMode.OpenOrCreate, FileAccess.Write))
      {
        _outFileStream.SetLength(0);
        const int _bufferLength = 100;
        byte[] _buffer = new byte[_bufferLength]; //This is intermediate storage for the decrypted content.
        long _bytesWritten = 0;
        TripleDESCryptoServiceProvider _tripleProvider = new TripleDESCryptoServiceProvider();
        FileStream _inFileStream = new FileStream(inCryptoFileName, FileMode.Open, FileAccess.Read);
        using (CryptoStream _inCryptoStream = new CryptoStream(_inFileStream, _tripleProvider.CreateDecryptor(dESKey, dESIV), CryptoStreamMode.Read))
        {
          while (true)
          {
            int _length = _inCryptoStream.Read(_buffer, 0, _bufferLength);
            if (_length == 0)
              break;
            _outFileStream.Write(_buffer, 0, _length);
            _bytesWritten = _bytesWritten + _length;
            progress.Report(_bytesWritten);
          }
        }
      }
    }

This is again the library method. We pass similar parameters to it. Let me stress that to succeed the same key and the initialization vector that was used earlier have to be passed. Of course, in a real scenario, decryption is performed in a different location usually by a different computer, or even in a completely different place in the world, therefore we must ensure that whoever performs the decryption process in the location where the decryption process is to be carried out need the same key and the initialization vector used while the stream is encrypted.

So let's take a look at how the decryption procedure is implemented in the DecryptData method. It is easy to note that it is very similar to the encryption method. Again, we treat the output file as a bitstream opened for writing so that we can store the decrypted bytes. We will carry out the entire process step by step using small chunks preserved in a buffer, which has the same length as the previous one. What is important is that we must have an object of the TripleDESCryptoServiceProvider class that provides the same key and the same initialization vector that was previously used. This time CryptoString will have a mode parameter indicating that it will be used only to read a file's content, so it will generally operate by decrypting the content of the specified file. In the DecryptData we have created an object that is responsible for performing decryption operations. Again, we end the process when we have read all the bits from the file containing the encrypted bitstream. The progress of this process is reported using the Report method. The operation finishes when everything has been saved to the output file. Of course, the output file is automatically closed thanks to the using statement. For the sake of simplicity, in the EncryptDecryptDataTest test method, the only correctness validation of the encryption/decryption round trip process is that the length of the file after decryption is equal to the length of the input file that is the source file.

Conclusion

We have already learned that there are two types of encryption. In the examples discussed in this article, only the symmetric encryption method of the bitstreams was the subject of examination. Asymmetric encryption will be the subject of the next article covering digital signature generation and validation.

In the symmetric scenario, the encryption and decryption inter-operating parties use identical keys. From the point of encryption up to decryption, the bitstream is highly likely to be secured because no meaning can be associated with it. In other words, it cannot be used to recover the information it originally represents though the encrypted meaningless bitstream could be available.

In the next part, we move on to asymmetric encryption. Precisely, not the encryption itself because the performance of asymmetric encryption is not enough hence it is only used in selected scenarios. The next section explores examples illustrating digital signature scenarios in which asymmetric encryption can and should be used to provide a safe interchange hash value interchange channel. Asymmetric encryption is also used to distribute a session key securely. The session key for communication encryption is a temporary cryptographic key used to secure communication between parties to establish a secure session. It is generated for a short duration to be used to establish a secure session allowing for encrypting and decrypting bitstreams exchanged between the communicating entities. Initially, session keys can be securely exchanged using asymmetric cryptography, where each party has a pair of public and private keys. The public keys can be exchanged openly, while the private keys are kept secret.


Similar Articles