Create a Simple, Reusable Infrastructure for Public Key Encryption Using VB.NET

Create a Simple, Reusable Infrastructure for Public Key Encryption Using VB.NET

ryptography is the science of transforming messages so that the meaning is hidden (encryption) and recovering the hidden meaning from transformed messages (decryption). A transformed message, in which the meaning is hidden, is called cipher text, while the non-transformed or recovered message, in which the meaning is not hidden, is called plain text. There are many methods of reversible transformation suitable for encryption/decryption applications.

Most modern cryptographic methods treat messages as a sequence of characters represented numerically. These are transformed through mathematical algorithms, involving the use of one or more “keys.” A key is a number or group of numbers that, used in a transformation algorithm, transform or restore the numbers that represent the characters of a message. Symmetric cryptographic transformations use the same key for encryption and decryption. The process is therefore described as “symmetrical.” Asymmetric cryptography, on the other hand, uses different but mathematically related keys for encryption and decryption.

Asymmetric Cryptography
In asymmetric cryptography, the key that is used to encrypt messages can be freely distributed via any non-secure medium, or a public key. Thus, asymmetric cryptography is also referred to as public key cryptography. However, messages encrypted with the public key cannot be decrypted using the public key. Public key encrypted messages can only be decrypted using the corresponding private key which is kept secure. Adding to the confusion, messages encrypted using the private key can only be decrypted using the corresponding public key. Encryption and decryption are very useful tools. However, these tools alone cannot address all secure messaging concerns. For example, since public keys are public, anyone can encrypt messages that decrypt with a private key. It would be very easy for an imposter to encrypt a message and claim to be someone you trust. Furthermore, it is possible for someone familiar with the public key cryptography algorithms to alter an encrypted message in an undetectable manner. This altered message could involve inserting, deleting, or replacing part of the original message.

You can alleviate these concerns by using a message hash in conjunction with public key encryption and decryption. A message hash is a digest of a message. Hashing algorithms process a message of any length into a fixed length hash. These algorithms are designed so that even very minor alterations to the message?such as inserting, deleting or changing a single character?results in a very different hash result.A Real-life Scenario
Here’s a typical secure messaging scenario:

Alice and Bob each generate a pair of public and private keys and exchange public keys. Alice composes a message to Bob.Alice computes a hash of the encrypted message, encrypts the hash using her own private key and appends it to the bottom of the message. This is referred to as signing the message with a digital signature.

Next, Alice encrypts the signed message using Bob’s public key and transmits the message to Bob. Bob then decrypts the message using his private key.

Bob uses Alice’s public key to decrypt the hash at the bottom of the message and compares it to the hash he computes for the message (after the signature portion has been removed). This process is called authentication. If any key other than Alice’s private key is used to encrypt the signature, the decryption would produce nonsense and would not match the computed message hash. If the message had been altered in any way during transit, the computed hash would differ from the one in the signature. Therefore, if the hash results match, Bob knows that the signature must have been generated with Alice’s private key, and that the message he has received is identical in content to the message she sent.

To summarize, a secure messaging model requires four distinct operations:

  1. Signing: encrypting a message digest with the sender’s private key and appending it to the message.
  2. Encrypting: encrypting a message (signed or unsigned) with the recipient’s public key.
  3. Decrypting: decrypting a message with the recipient’s private key to recover the plain text message.
  4. Authenticating: decrypting a message digest (or hash) using the sender’s public key and comparing it to the computed digest.

Subsequently, a public key infrastructure provides methods for each of these activities as well as a system of generating, sharing, and organizing public and private keys.

Designing the Public Key Infrastructure
Before starting with the public key infrastructure, you should be familiar with the objects it will contain:

The message object is a container for message text. It can contain either plain text or cipher text. In this article, the infrastructure messages will be implemented as string objects.

Key objects contain the information needed to perform the signing, encryption, decryption, and authentication operations on messages. Key objects need a property to indicate which type of key?Public or Private. Key objects also contain a property for a label. The label serves as a unique identifier to facilitate organizing, storing, and retrieving keys. The key object exposes the key (group of numbers for the cryptography algorithms) as an XML string through a key property. The .NET cryptography classes make use of a collection of information about keys to facilitate cryptographic operations. However, this collection of information can be read, stored, and used as an XML string. Therefore, you can simply treat the entire XML string as if it were a simple key.

A key pair object is a container that can hold either a public or private key. The KeyPair object will expose properties for PublicKey and PrivateKey. Each of these properties may either contain a key object of the indicated type or nothing. When you generate a public key and private key set (appropriate for encrypting and decrypting messages), the set is returned as a KeyPair. You can get the Key value (XML string) from the public key and distribute it. You can also create a KeyPair containing only a public key for storing the public key information (XML string) of someone with whom you wish to have secure communications.

This article contains the private and public keys of the person with whom you are corresponding.

The KeyRing object contains a collection of KeyPairs indexed by the labels from the public key. It also contains methods for adding, retrieving, and removing KeyPairs.

The CryptographicFunctions class exposes the KeyRing object and provides a method for generating new KeyPair objects (that contain public and private keys). This class also contains the methods for signing, encrypting, decrypting, and authenticating messages using a conversational key pair (that contains your private key and the other party’s public key).

Implementing the Public Key Infrastructure
Enough background! Let’s get started on your infrastructure. First, create a new VB.NET Class Library project. Name the Project PKInfrastructure. In the solution explorer, right click the project name and select properties. On the Common PropertiesGeneral settings, specify PKInfrastructure as the assembly name and also as the root namespace. Also in the solution explorer right click on the Class1.vb file and delete it.

The Key, KeyPair, and KeyRing classes are fairly straightforward. Right click on the project name in the solution explorer, select Add, then Add Class. Name the class “Key.” When the new file (Key.vb) is opened for editing, past the code from Listing 1. Repeat this process to add each of the other classes to the project: KeyPair (Listing 2), KeyRing (Listing 3),CryptographyFunctions (Listing 4) and PublicKeyGenerator (Listing 5).

The CryptograhyFunctions class (CryptographyFunctions.vb?Listing 4) is where almost all of the action takes place. This class encapsulates and abstracts the functionalities you need from the .NET framework, and handles the four cryptographic functions identified earlier (the sign, encrypt, decrypt, and authenticate functions). However, the creation of new key pairs is delegated to the PublicKeyGenerator class (PublicKeyGenerator.vb?Listing 5).

Generate a Public/Private KeyPair
System.Security.Cryptography.RSACryptoServiceProvider (RSA) is the workhorse of public key cryptography in the .NET framework. By default, this class creates a new public/private key set when it is instantiated. It also attempts to use a “key store” for the user account of the current user. Override these default behaviors by passing a customized parameters object during instantiation. The GetRSA function (Listing 5) abstracts this and returns an instance of the RSA class that is configured to use a machine key store rather than one based on the current user account.

The RSA class provides access to the public and private keys through the serialization functions ToXmlString and FromXmlString. The ToXmlString function takes a Boolean parameter which indicates whether private key information should be included. The MakeKeyPair function calls this method with false to get the public key XML string and again with true to get the private key XML. In each case, the XML is loaded into a Key instance of the new KeyPair object. This KeyPair object is then returned from the MakeKeyPair function.

Sign and Encrypt a Message
Many of the cryptographic functions use an RSA instance. Therefore, when the CryptographicFunctions class instantiates, it instantiates an RSA object and stores it in a member variable (m_objRSA) where it is accessible to all of the functions of the class.

The SignAndEncrypt function (Listing 4) takes a KeyPair object containing the sender’s private key and the recipients public key and a plain text string to sign and encrypt. The function calls Sign, passing the KeyPair and plaintext. The resulting signed text is then encrypted by the Encrypt function.

The Sign function initializes the m_objRSA object with the private key information and calls the SignData method to get the signature. However, the RSA’s SignData method does not operate on string data. Therefore, the string text is converted into an array of bytes that are then passed into the SignData method. The SignData method returns an array of bytes which is then converted back into a string. Since most of the .NET public key cryptography functions use byte arrays rather than strings, these conversions are handled in helper functions (StringAsByteArray and ByteArrayAsString). The RSA class’ SignData method requires a HashAlgorithm object to compute the hash bytes for the message. Pass in a new instance of the default hash algorithm from System.Security.Cryptography.HashAlgorithm.Create().

The Encrypt function takes a KeyPair object and the plain text to be encrypted. The m_objRSA object is initialized?this time with the public key?for encryption. This is where it gets a little tricky. The RSA class supports encryption through the Encrypt method. However, the Encrypt function operates on an array of bytes. It modifies each byte of input using a corresponding portion of the public key. If the input array has fewer bytes and doesnt use the entire key, padding is used so that the output is always the same length and depends only on the size of the key. If the input is larger than the public key, the encryption fails with an error indicating “bad data” or “bad length.” The work-around for this conundrum breaks the plain text into blocks to ensure that each block is not too long to be encrypted. Convert these encrypted blocks back into strings (from byte arrays) and concatenate them together to produce the final cipher text.

There is one further complicating twist. The type of padding employed depends on the operating system?and a minimum amount of padding may be applied to each block. This is described on Microsoft’s MSDN site here. You need to determine how much is the minimum amount of padding that will be applied to each encrypted block and the key size in order to determine the maximum block size. Windows XP and later support ‘OAEP padding (PKCS#1 v2)’ in which the minimum amount of padding applied to each encrypted block depends upon the size of the key and the size of the hash (message digest). Windows 2000 or later with the high encryption pack support ‘PKCS#1 v1.5’ padding in which the minimum amount of padding is 11 bytes. Previous versions of Windows and Windows 2000 without the high encryption pack may use no padding.

If you use the default key size of 1024 bits (128 bytes), PKCS#1 v1.5 padding (with a minimum of 11 bytes added to each block), and 1 byte per string character, then the maximum string length you can safely encode is 117 characters at a time. The helper function GetModulusSize returns the key size in bytes. The encryption algorithm subtracts 11 from this to get the maximum number of characters and repeatedly calls the EncodeBlock function with the next group of characters.

The EncodeBlock function simply calls the encode method of the m_objRSA object. The string is converted into an array of bytes for encryption and the encrypted bytes (with padding) are converted back into a string. The encrypted block strings are accumulated in the Encrypt function and returned.

Decrypt and Authenticate a Message
The decryption process is very similar to the encryption process. The DecryptAndAuthenticate function calls Decrypt, passing the KeyPair along with the encrypted text string. Decrypt initializes the m_objRSA object?with the private key this time?and makes repeated calls to DecryptBlock until all of the encrypted text is decrypted. In this case, the block size is simply the size of the key in bytes (from the GetModulusSize function), because the encrypted strings have been padded to this size. DecryptBlock simply calls the m_objRSA’s Decrypt method. This converts the string into a byte array for decrypting and then converts the decrypted bytes back into a string. Again, the decrypted blocks are accumulated until all of the text has been decrypted.

Figure 1:The rich text box populates with the key information, the test string, the encrypted string, and the recovered test string.

After the message has been decrypted, DecryptAndAuthenticate passes it to Authenticate. The Sign function adds a signature, with and tags around it, to the end of the message. The Authenticate function relies on these tags. StripSignature removes the signature and these tags from the message, restoring it to its original content. The string inside the signature tags is returned from the StripSignature function. AuthenticateText calls the m_objRSAs VerifyData function with the original text string (as a byte array) and the signature that was appended to the message. In order to verify the data, this function must compute the hash (message digest) for the original content and compare it to the decrypted hash from the message (that was encrypted using the senders private key and is decrypted by the VerifyData function using the m_objRSA objects public key). This hash algorithm must be the same as the one used to sign the message. So, once again, pass in a new instance of the default hash algorithm from System.Security.Cryptography.HashAlgorithm.Create().

Build the project to create the PKInfrastructure.dll file in the project’s Bin subfolder.

Testing the Public Key Cryptography Infrastructure
Create a new VB.Net Windows Application project. Name the project PKInfrastructureTester. Add a reference to the project to the PKInfrastructure.dll file from the PKInfrastructure project (from the menu, projectadd reference?then browse to the PKInfrastructure project folder and navigate to the BIN subfolder and select the PKInfrastructure.dll file). Double-click on the form to open the code window and replace what is there with the code from Listing 6. Build and start the application. Click the Do Test button. You should see the rich text box populate with the key information, the test string, the encrypted string and the recovered test string (Figure 1).

Wrapping Up
Public key cryptography is a powerful and essential technology. The ability to widely distribute public keys and communicate securely over an open network is truly revolutionary.

However, sometimes a combination of asymmetric and symmetric cryptography will produce the best results. Public key cryptography algorithms are very intensive and applications that involve time sensitive operations and/or large quantities of content will fare better with a symmetric cryptographic approach. In the most common scenario, a temporary symmetric key is generated specifically for a given communication session and exchanged using public key cryptography. This symmetric key is then used for the remainder of the conversation.

Public key cryptography also has applications beyond communications. For example, an application that needs to protect its data files might use a public/private key pair for reading and writing these files. If the data files are large, you might use symmetric cryptography and store the public key encrypted symmetric key at the beginning of the file. Because the symmetric key is encrypted with the application’s public key, only this application can decrypt it using the corresponding private key?which is compiled into the application. You could even publish the public key and the file format in order to allow developers and other applications to generate data files that our application can read and process. But those developers and applications would not be able to read those files or any other data files for our application.

The .NET framework provides everything needed to use public key cryptography alone or in conjunction with symmetric cryptography. However, the large volume of documentation and the lack of concise yet complete examples can be bewildering. You must be familiar with the idiosyncrasies and limitations and it helps to know a few tricks for getting around them.

This article and accompanying project files are a useful example of public key encryption in VB.Net. The example application demonstrated how a framework facilitates key generation and provides access to both public and private key information, while supporting each of the four cryptographic activities of the secure messaging model. This PKInfrastructure assembly is re-usable and offers a simple, friendly interface for using public key encryption in your projects.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist