Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Programming Public Key CryptoStreams, Part 2


In this newsletter, I’ll show a C# example that applies the RSA public key encryption algorithm to the task of file encryption using IO streams. The C# source code download associated with this article shows the application code necessary to apply the RSA public key encryption algorithm to the task of file encryption using IO streams.

Because symmetric key encryption is commonly used to conduct bulk encryption of data, it is sometimes referred to as bulk encryption. Encryption algorithms are referred to as ciphers, and the technique used by the cipher to accomplish encryption is usually included as part of its more descriptive name. For example, a symmetric cipher that applies encryption one block at a time, where a block is defined as a certain number of bits transformed by the cipher as a single unit, is known as a symmetric block cipher. All of the symmetric ciphers provided in the .NET Framework are block ciphers. Asymmetric ciphers are normally not used for bulk encryption because they are substantially slower than symmetric ciphers and they aren’t optimized to operate in block mode since there is no need to chain blocks of ciphertext together to produce the resulting encrypted output. Instead, asymmetric ciphers are normally used to encrypt entire messages all at once. This poses something of a dilemma for optimal data security, since the entire plaintext message must be in memory in order for the message to be encrypted by an asymmetric cipher. On a practical level, it is more difficult for a malicious attacker to intercept many blocks of plaintext out of memory over time than it is for the attacker to intercept a single large block of plaintext. Block ciphers offer this additional practical advantage for increased data security over other types of cipher.

Block ciphers also increase data security by making cryptanalysis more difficult through the introduction of feedback from previous ciphertext blocks into each subsequent block. The noise created by feedback ensures that repetitive data elements in the plaintext do not obviously result in repetitive regions of ciphertext (see Figure 1). When a block of data is transformed without feedback, it always results in the same ciphertext output as every other identical block of plaintext, inadvertently revealing something about the plaintext to a cryptanalyst due to the presence of patterns in the ciphertext corresponding to patterns in the plaintext. There are many ways to code feedback algorithms as part of the design of a block cipher. This work is done for you already when you use one of the .NET Framework symmetric block ciphers. The System.Security.Cryptography.CipherMode enumeration defines the block cipher feedback modes that any symmetric block cipher might support. One member of the enumeration, Electronic Code Book (ECB), indicates that no feedback should be introduced into the ciphertext; therefore, identical plaintext blocks always result in identical ciphertext blocks. ECB mode is mostly of academic interest, as the default CipherMode of .NET Framework symmetric ciphers is Cipher Block Chaining (CBC), a mode that causes an exclusive OR transformation of plaintext bits with the ciphertext bits from the previous block. To ensure unpredictable feedback noise throughout the ciphertext, the first block is scrambled with feedback derived from an initialization vector (IV). During decryption the cipher must operate in the same mode as was used for encryption and the same IV must be supplied; otherwise, decryption fails.

Figure 1: Initialization Vectors Ensure Unpredictable Feedback

Listing 1 shows excerpts of this article's downloadable C# console program, PKEncrypt. The System.Security.Cryptography.CryptoStream class is used to transform a plaintext IO FileStream named f using the RSA cipher and the specified public key. FromXmlString is the method of the RSACryptoServiceProvider class that loads the public key for use by the cipher. The CryptoStream calls into an object which implements the ICryptoTransform interface to apply the cipher to blocks of data as they are read from the underlying FileStream. Command-line usage for the PKEncrypt program is as follows (keyfile and inputfile are required):

Usage: PKEncrypt keyfile inputfile [ciphertext filename] omit [ciphertext filename] 
  to decrypt to plaintext.out keyfile must contain a full <RSAKeyValue> 
  for decryption
  
  

Listing 1: Excerpts from the PKEncrypt Console Program

 static void Main(string[] args) {

  FileStream f, o;

  int bytes;

  byte[] buf;

  string sRSAKeyValue = null;

  CryptoStream csTransformStream;

  RSAEnCryptoTransform rsaPlaintextTransform;

  RSADeCryptoTransform rsaCiphertextTransform;

  RSACryptoServiceProvider rsaEncryptor = new

   RSACryptoServiceProvider(1024);

  RSACryptoServiceProvider rsaDecryptor = new

   RSACryptoServiceProvider(1024);

...

  f = File.Open(args[0],FileMode.Open);

  sRSAKeyValue = new StreamReader(f).ReadToEnd();

...

  f = File.Open(args[1],FileMode.Open);

  rsaEncryptor.FromXmlString(sRSAKeyValue);

  rsaPlaintextTransform = new

   RSAEnCryptoTransform(rsaEncryptor);

  o = File.Create(args[2]);

  buf = new byte[rsaPlaintextTransform.OutputBlockSize];

  csTransformStream = new CryptoStream(

   f,rsaPlaintextTransform,CryptoStreamMode.Read);

...

  while((bytes = csTransformStream.Read(buf,0,buf.Length)) > 0)

  {o.Write(buf,0,bytes);}

Listing 2 shows how the RSAEnCryptoTransform class implements ICryptoTransform so that instances of RSAEnCryptoTransform can be passed to the CryptoStream¾ as shown in Listing 1. The CryptoStream class takes care of assembling plaintext blocks and passing them to the ICryptoTransform object for transformation. CryptoStream determines valid block sizes in bytes for the cipher through InputBlockSize and OutputBlockSize, accessors of the ICryptoTransform object.

Listing 2: RSAEnCryptoTransform Implements ICryptoTransform for Block

  Transformations

class RSAEnCryptoTransform : ICryptoTransform, IDisposable {

  public bool CanReuseTransform {get {return(true);}}

  public bool CanTransformMultipleBlocks {get {return(false);}}

  public int InputBlockSize {get {return(117);}}

  public int OutputBlockSize {get {return(128);}}

  private RSACryptoServiceProvider rsaEncryptor;

  public RSAEnCryptoTransform() {}

  public RSAEnCryptoTransform(RSACryptoServiceProvider rsaCSP)

  {rsaEncryptor = rsaCSP;}

  public void Dispose() {}

  public int TransformBlock(byte[] inputBuffer,

  int inputOffset, int inputCount, byte[] outputBuffer,

  int outputOffset ) {

   byte[] plaintext = new byte[inputCount];

   Array.Copy(inputBuffer,inputOffset,plaintext,0,inputCount);

   byte[] ciphertext;

   ciphertext = rsaEncryptor.Encrypt(plaintext,false);

    ciphertext.CopyTo(outputBuffer,outputOffset);

    return(ciphertext.Length);

   }

  public byte[] TransformFinalBlock(byte[] inputBuffer,

  int inputOffset, int inputCount ) {

   byte[] plaintext = new byte[inputCount];

   Array.Copy(inputBuffer,inputOffset,plaintext,0,inputCount);

   byte[] ciphertext;

   ciphertext = rsaEncryptor.Encrypt(

    plaintext,false);

    return(ciphertext); }}

In Listing 2, you can see that I’ve set these values to 117 and 128, respectively. The input block size used by the RSA algorithm is equal to the size of the key minus 11 bytes because a minimum of 11 bytes are added to the plaintext in each RSA transformation as block-formatting overhead and padding. The ciphertext output block size is always equal in size to the encryption key. However, this doesn’t translate directly into the block sizes used in your code because the RSACryptoServiceProvider automatically chops up plaintext into blocks sized 11 bytes smaller than the key size and fills the output buffer supplied by your code with the chained result of the individual block transformations. You can specify any OutputBlockSize you wish, provided that it is a multiple of the key size, and the InputBlockSize you specify must always be (OutputBlockSize–11) or smaller. Small InputBlockSize with large OutputBlockSize results in more padding being added by the RSA algorithm.

The Encrypt method of the RSACryptoServiceProvider class accepts as its first parameter the plaintext to be encrypted using the public key specified. Its second parameter is a Boolean value indicating whether to use Optimal Asymmetric Encryption Padding (OAEP). OAEP is an enhanced padding algorithm that introduces substantially greater variability in the output ciphertext. It has been shown to be provably secure, whereas the padding algorithm typically implemented with the RSA cipher, known as PKCS#1 version 1.5, is known to have certain flaws that a cryptanalyst can exploit under the right conditions. OAEP is only available under Windows 2000 with the high encryption pack installed. When OAEP is not used, ciphertext produced by application of the RSA algorithm according to the PKCS#1 version 1.5 standard can conceivably be deciphered by a cryptanalyst if enough information is known about the underlying plaintext and the cryptanalyst possesses a very large number of ciphertext blocks known to have been encrypted with a particular public key. There is even a known attack against an automated system such as an SSL-secured web server that applies the RSA algorithm automatically whereby the attacker is able to discover the secret key used by the server. This attack is known as the "Million Message Attack” discovered by Daniel Bleichenbacher of Bell Laboratories.

The code shown in Listing 2 is invoked by the CryptoStream object named csTranformStream as data is read from the underlying plaintext FileStream. Importantly, both TransformBlock and TransformFinalBlock are called by the CryptoStream, so both methods must be implemented. The TransformFinalBlock call occurs only once when the end of the stream is encountered. All data buffered by CryptoStream from the end of the previous block up to the end of the stream is passed to TransformFinalBlock. This gives the encryption algorithm a chance to write data to the end of the ciphertext that is meaningful to decryption, if necessary. At the end of each block, the RSACryptoServiceProvider writes the length of the padding inserted according to PKCS#1 version 1.5 encoding. Therefore, there's no difference between transforming the final block and any of the previous blocks. The final block of ciphertext will always be equal in size to every other block, even if the input plaintext doesn't fill the input block size because of the padding inserted by the encoding standard used. Some block ciphers result in variable length output ciphertext or have another reason to differentiate between the final transformation and all that come before it.

To perform decryption using a public key CryptoStream rather than encryption, you simply reverse the input and output block sizes and call the Decrypt method rather than Encrypt. The source- code download included with this article-the PKEncrypt console program written in C#-combines encryption and decryption into a single Main function and determines which operation to invoke based on the command-line parameters provided. RSAKeyValue parameters are supplied by way of an XML input file also specified as a command-line parameter. Note that the XML representation of an RSA key pair includes the <Modulus> and <Exponent> representing the public key as well as the additional <P>, <Q>, <DP>, <DQ>, <InverseQ>, and <D> representing the rest of the key pair parameters including the private key. The private key is used to perform decryption, so it must be supplied, whereas it isn't necessary to supply <P>, <Q>, <DP>, <DQ>, <InverseQ>, and <D> in order to perform encryption.

Figure 2: PKEncrypt Applies RSA for Bulk Encryption and Decryption

It's very important to note also that and are derived from the private key parameters as Microsoft chose to represent them. Therefore, the public key in inseparable from the private key; possession of the private key implies possession of the corresponding public key whereas the opposite is not true-a private key cannot be derived from possession of an RSA public key. The only way to achieve one-way encryption with the RSA algorithm is in the encryption direction using <Modulus> and <Exponent>. When a private key is used to perform a plaintext transformation, the encryption is typically used as the basis of a digital signature since anyone in possession of the corresponding public key can verify the signature (decrypt the ciphertext produced using the private key). Decryption uses a different ICryptoTransform class called RSADeCryptoTransform. Listing 3 shows this class. Notice that the Decrypt method is called on the RSACryptoServiceProvider object instead of Encrypt, as shown in Listing 2 for the RSAEnCryptoTransform class.

Listing 3: RSADeCryptoTransform Implements ICryptoTransform for Block

  
  Transformations

class RSADeCryptoTransform : ICryptoTransform, IDisposable {

  public bool CanReuseTransform {get {return(true);}}

  public bool CanTransformMultipleBlocks {get {return(false);}}

  public int InputBlockSize {get {return(128);}}

  public int OutputBlockSize {get {return(117);}}

  private RSACryptoServiceProvider rsaDecryptor;

  public RSADeCryptoTransform() {}

  public RSADeCryptoTransform(RSACryptoServiceProvider rsaCSP)

  {rsaDecryptor = rsaCSP;}

  public void Dispose() {}

  public int TransformBlock(byte[] inputBuffer,

   int inputOffset, int inputCount, byte[] outputBuffer,

   int outputOffset        ) {

   byte[] ciphertext = new byte[inputCount];

   Array.Copy(inputBuffer,inputOffset,ciphertext,0,inputCount);

   byte[] plaintext;

   plaintext = rsaDecryptor.Decrypt(ciphertext,false);

   plaintext.CopyTo(outputBuffer,outputOffset);

   return(plaintext.Length); }

  public byte[] TransformFinalBlock(byte[] inputBuffer,

   int inputOffset, int inputCount ) {

   byte[] ciphertext = new byte[inputCount];

   Array.Copy(inputBuffer,inputOffset,ciphertext,0,inputCount);

   byte[] plaintext;

   plaintext = rsaDecryptor.Decrypt(

   ciphertext,false);

   return(plaintext); }}

To further improve upon the data security provided by the code shown in this article, you can implement for your ICryptoTransform classes a custom feedback algorithm to scramble the plaintext before it gets encrypted so that patterns do not emerge in the resulting ciphertext that would weaken its strength, or its ability to resist decryption through cryptanalysis. There are many ways to accomplish feedback, the simplest being to apply an exclusive OR bitwise transformation as defined by the Cipher Block Chaining mode of cipher operation starting with a random initialization vector (IV). The IV becomes a secret that must be protected, but not at all-costs like a symmetric encryption key. Any third party that obtains a copy of the IV and the ciphertext still has to steal the private key or succeed in cryptanalysis, and that's no easy task. Remember that if public key encryption is ever used to encrypt a predictable message such as "yes" or "no" and the public key is known to or intercepted by a third party, then it becomes trivial for the third party to discover the plaintext message simply by encrypting each possible message and comparing the resulting ciphertext with the intercepted ciphertext.

Asymmetric cryptography with OAEP, a custom feedback algorithm, or outright symmetric encryption of plaintext before asymmetric encryption is applied constitutes the very latest in modern cryptographic theory and practice. Only asymmetric encryption provides one-way data security for automated systems that even those systems are unable to decrypt. Optimal data security for automated systems can be achieved and the Catch-22 of symmetric encryption bypassed only through the application of asymmetric cryptography. This article and its associated source code download demonstrate one technique for programming asymmetric CryptoStreams in C#. The code shown in this article uses a FileStream, but any IO stream can be used instead, including a System.Net.Sockets.NetworkStream or System.IO.MemoryStream, making the demonstrated technique of using public key cryptography for bulk encryption in place of symmetric block ciphers versatile. The CryptoStreams technique also works with network streams, memory buffer streams, and other IO stream classes in the .NET Framework.


Jason Coombs works as forensic analyst and expert witness in court cases involving digital evidence. Information security and network programming are his areas of special expertise. He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.