Click here to Skip to main content
Click here to Skip to main content

Password protecting IO streams

By , 28 Aug 2006
Rate this:
Please Sign up or sign in to vote.

Introduction

Encryption is one of those things I prefer not to touch. Yet, it sometimes comes in my way, and I can do nothing to avoid an encounter. This time, I decided to confront it instead. I need to password protect a file?! I will up you one with password protecting a stream! That is exactly what I did. This article is here because I really want feedback on any potential loopholes I’ve missed; how can this code be made more secure? And no, I am not working for a credit card company, so there is no immediate security threat to anyone.

Which algorithm to choose?

Let’s go briefly over the available options. Which one suites requirements of encrypting a stream the best?

Security through obscurity

One could build security through obscurity by writing a proprietary encryption algorithm. There are two drawbacks to this approach, at least in a production environment.

  • Developers come and go, but a hard-coded algorithm stays under source control. There’s a risk the “obscure” algorithm will one day be known to outsiders when another person leaves a company.
  • Not only is it insecure, but it also makes us reinvent a wheel, a crooked decrepit wheel. I considered it for a brief moment, and went on looking for a better, and, also important, an easier solution.

Why bother with the hard and insecure way when .NET offers cryptography classes. What do these classes have to offer and how do they compare to each other?

Public key encryption (asymmetric)

Asymmetric encryption relies on a public / private key pair. The public key is used to encrypt data, while the private key is used to decrypt it. The two keys are mathematically linked. Since I am not a mathematician, I would never attempt creating such a key pair based on a password. Other disadvantages of asymmetric encryption are:

  • It is very slow compared to secret key encryption.
  • It is not designed for large amounts of data. Streams happen to fall into the large amount of data category.

Secret key encryption (symmetric)

This type of encryption is well suited for encrypting streams. That is the preferred way as long as neither the key nor the initialization vector are hard-coded. The program can be disassembled and the secret information discovered.

So, we have contradictory requirements to fulfill. One one hand, we must not use hard-coded values. On the other hand, we must not use random or default values either. What we need is to use the password string as the source for the key. Thankfully, .NET provides exactly a class that we need: PasswordDeriveBytes. As long as input to this class depends on properties of the password string alone, the algorithm will produce the same results both for the encryptor and the decryptor. What’s more, it will be no less secure than the password itself, in the sense that it will be much faster to guess the password than to perform a search for every possible key.

Tiny piece of code that almost worked

The easiest way to write a Stream is to inherit from one, especially since .NET provides CryptoStream. My idea of a password-protected CryptoStream constructor includes a stream to attach to, read or write flag, and a password. That is exactly how I started writing my class.

public PwdTdsCryptoStream(System.IO.Stream Stream, 
       CryptoStreamMode Mode, string Password)
       : base(Stream, CreateTripleDESTransform(Mode, Password), Mode)
{
}

To initialize the base CryptoStream class, we need an instance of ICryptoTransform. An ICryptoTransform instance is created in a private static function CreateTripleDESTransform. Inputs to CreateTripleDESTransform function are CryptoStreamMode and the password. CreateTripleDESTransform initializes an instance of the PasswordDeriveBytes class using the password string. The next step is to call CryptDeriveKey() to make a key for an algorithm of our choice, TripleDES in this case. And the final step is to create an ICryptoTransform that uses this key. To recap, Password -> PasswordDeriveBytes -> CryptDeriveKey() -> Key -> ICryptoTransform.

private static ICryptoTransform CreateTripleDESTransform(
               CryptoStreamMode Mode, string Password)
{
    byte[] key = null;
    byte[] pdbsalt = null;
    byte[] iv = null;
    try
    {
        // Salt byte array.
        pdbsalt = GetPdbSalt(Password);

        // Create PasswordDeriveBytes object that will generate
        // a Key for TripleDES algorithm.
        PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password, pdbsalt);

        iv = new byte[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
        // Create a private key for TripleDES algorithm.
        // The iv parameter is not currently used.
        // * http://blogs.msdn.com/shawnfa/archive/2004/04/14/113514.aspx
        key = pdb.CryptDeriveKey("TripleDES", "SHA1", 192, iv);

        switch (Mode)
        {
            case CryptoStreamMode.Read:
                return TripleDES.Create().CreateDecryptor(key, iv);
            case CryptoStreamMode.Write:
                return TripleDES.Create().CreateEncryptor(key, iv);
            default:
                return null;
        }
    }
    catch (CryptographicException)
    {
        return null;
    }
    finally
    {
        if (key != null)
            Array.Clear(key, 0, key.Length);
        if (pdbsalt != null)
            Array.Clear(pdbsalt, 0, pdbsalt.Length);
        if (iv != null)
            Array.Clear(iv, 0, iv.Length);
    }
}

Woohoo!! The password protected crypto stream is done!

Battling the Bad Data

This seemed way too easy to be true. And testing proved exactly that. I wrote a test application that password protected and unprotectes files using the new stream. Setting password worked every time. Un-protecting using a correct password also worked every time, but un-protecting using a bad password would crash and burn with CryptographicException “Bad Data”.

This Bad Data exception was killing me. When keys don't match, it is thrown on every call to the CryptoStream class, Close() and Dispose() included. Having a Close() in the finally{} block would re-throw the Bad Data exception. If Close() and Dispose() fail, how on earth do we release the resources of PwdTdsCryptoStream properly? There isn't much information on this exception. Searching the Internet didn’t help to pinpoint the cause of “bad data”. It only reaffirmed that other people get the same exception in case of a key mismatch.

At this point, I was really lucky to guess that I should try to dispose off ICryptoTransform before trying to close my stream. Quick testing code included a static ICryptoTransform variable in my class.

public class PwdTdsCryptoStream : CryptoStream
{
    private static ICryptoTransform m_CryptoTransform;
........
    private static ICryptoTransform CreateTripleDESTransform(
                   CryptoStreamMode Mode, string Password)
    {
........
        switch (Mode)
        {
            case CryptoStreamMode.Read:
                m_CryptoTransform = TripleDES.Create().CreateDecryptor(key, iv);
                return m_CryptoTransform;
........
    }

    public override int ReadByte()
    {
        try
        {
            return base.ReadByte();
        }
        catch (CryptographicException)
        {
            m_CryptoTransform.Dispose();
            this.Close();
        }
        return -1;
    }
}

This setup has worked to perfection! A Bad Data exception was caught as usual. ICryptoTransform was disposed of, and Close() did not throw another Bad Data exception! It threw an ObjectDisposedException instead. Disposing of a transform disposes off a stream too. Which is just fine, since my worry was not being able to clean up my stream properly.

Now, the only problem that remains is the static m_CryptoTransform. How can I make it non-static, while creating it in a static function??

  1. Why do I need that static function anyway? Because I want this class to create its own ICryptoTransform object. This slightly simplifies life for those users of the class who don't care about encryption details (myself included, on an average day). Hence the constructor that only takes a Stream, a CryptoStreamMode, and a password as parameters. And this function must remain in the initialization list because the base CryptoStream can only be initialized by calling the base constructors.
    public PwdTdsCryptoStream(System.IO.Stream Stream, 
           CryptoStreamMode Mode, string Password)
         : base(Stream, CreateTripleDESTransform(Mode, Password), Mode)
    {
    }
  2. The non-static m_CryptoTransform could be assigned to in the constructor body. Too bad return value from the initialization list is not accessible in the constructor body.
    public PwdTdsCryptoStream(System.IO.Stream Stream, 
           CryptoStreamMode Mode, string Password)
         : base(Stream, CreateTripleDESTransform(Mode, Password), Mode)
    {
        m_CryptoTransform = ??
    }
  3. We need an overloaded constructor, which will be called from the initialization list of the current constructor and which has an ICryptoTransform parameter. The ICryptoTransform parameter will then be assigned to a non-static member variable in the body of the new constructor.
    public PwdTdsCryptoStream(System.IO.Stream Stream, 
           CryptoStreamMode Mode, string Password)
        :  this(Stream, CreateTripleDESTransform(Mode, Password), Mode)
    {
    }
    public PwdTdsCryptoStream(System.IO.Stream Stream, 
           ICryptoTransform Transform, CryptoStreamMode Mode)
         : base(Stream, Transform, Mode)
    {
        m_CryptoTransform = Transform;
    }

Done! That takes care of static. Now that the toughest hurdles are out of the way, the rest is child’s play. Here’s the final implementation of the PwdTdsCryptoStream class with the several functions omitted to avoid repetition.

public class PwdTdsCryptoStream : CryptoStream
{
    private ICryptoTransform m_CryptoTransform;
    private bool m_bDisposed;

    public PwdTdsCryptoStream(System.IO.Stream Stream, 
           CryptoStreamMode Mode, string Password)
         : this(Stream, CreateTripleDESTransform(Mode, Password), Mode)
    {
    }

    public PwdTdsCryptoStream(System.IO.Stream Stream, 
           ICryptoTransform Transform, CryptoStreamMode Mode)
         : base(Stream, Transform, Mode)
    {
        m_CryptoTransform = Transform;
        m_bDisposed = false;
    }

    private static ICryptoTransform CreateTripleDESTransform(
                   CryptoStreamMode Mode, string Password)
    {
        byte[] key = null;
        byte[] pdbsalt = null;
        byte[] iv = null;

        try
        {
            // Salt byte array.
            pdbsalt = GetPdbSalt();

            // Create PasswordDeriveBytes object that will generate
            // a Key for TripleDES algorithm.
            PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password, pdbsalt);

            iv = new byte[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
            // Create a private key for TripleDES algorithm.
            // The iv parameter is not currently used.
            // * http://blogs.msdn.com/shawnfa/archive/2004/04/14/113514.aspx
            key = pdb.CryptDeriveKey("TripleDES", "SHA1", 192, iv);

            switch (Mode)
            {
                case CryptoStreamMode.Read:
                    return TripleDES.Create().CreateDecryptor(key, iv);
                case CryptoStreamMode.Write:
                    return TripleDES.Create().CreateEncryptor(key, iv);
                default:
                    return null;
            }
        }
        catch (CryptographicException)
        {
            return null;
        }
        finally
        {
            if (key != null)
                Array.Clear(key, 0, key.Length);
            if (pdbsalt != null)
                Array.Clear(pdbsalt, 0, pdbsalt.Length);
            if (iv != null)
                Array.Clear(iv, 0, iv.Length);
        }
    }


    private static byte[] GetPdbSalt()
    {
        RNGCryptoServiceProvider Gen = new RNGCryptoServiceProvider();

        // Byte array of the same size as SHA1 hash, which is 160 bits.
        // Would PasswordDeriveBytes benefit from a larger size salt array?
        byte[] arrRandom = new byte[20];
        // Fill the array with random values.
        Gen.GetBytes(arrRandom);
        return arrRandom;
    }

    public override int ReadByte()
    {
        try
        {
            return base.ReadByte();
        }
        catch (CryptographicException)
        {
            KillStream();
        }
        return -1;
    }

    public override void Close()
    {
        if (!m_bDisposed)
            m_CryptoTransform.Dispose();
    }

    private void KillStream()
    {
        m_CryptoTransform.Dispose();
        m_CryptoTransform = null;
        m_bDisposed = true;
        throw new BadPasswordException();
    }
}

The only noticeable difference is the Close() function. To prevent it from throwing an ObjectDisposedException in the finally{} block after catching a BadPasswordException, it checks a flag and does nothing if the stream has already been disposed.

I have just read "Asynchronous Method Invocation", a great article by Mike Peretz. It convinced me not to override BeginRead() and BeginWrite() since these functions do not throw exceptions.

Test application

To test the password-protected stream, I wrote a little application that password-protects and un-protects files.

Once a file is password protected, it can no longer be opened by its associated program. Since a file loses its program association, there is no point keeping its original extension. I use a .pbin extension for password protected files. However, we need to restore the file extension eventually, when the password is successfully verified. To do so, the original file name is the first bit of information written to the crypto stream.

First, we get a byte array that represents the file name. The length of this byte array is written to the crypto stream first. The length itself is stored as a byte array of two bytes, which represents an unsigned short. Next, we write the file name byte array. Then, we copy the contents of the file to the password protected stream.

// First, store file name length and file name.
byte[] arrName = Encoding.Unicode.GetBytes(OriginalName);
byte[] arrLength = BitConverter.GetBytes((System.UInt16)arrName.Length);

strmPwdProtect.Write(arrLength, 0, arrLength.Length);
strmPwdProtect.Write(arrName, 0, arrName.Length);

CopyStream(strmInput, strmPwdProtect);

Un-protecting a file is the reverse of the procedure above. First, the size of the file name is read and converted to ushort. An array of that size is allocated and the original file name is read into it. Finally, the file contents from the password protected stream are copied to the output file stream.

try
{
    // Read the file name from the decrypted stream.
    byte[] arrLength = new byte[sizeof(System.UInt16)];
    strmPwdProtect.Read(arrLength, 0, arrLength.Length);
    ushort uLength = BitConverter.ToUInt16(arrLength, 0);
    byte[] arrName = new byte[uLength];
    strmPwdProtect.Read(arrName, 0, arrName.Length);

    string strFilename = Encoding.Unicode.GetString(arrName);
    DecryptedFileName = DirectoryName + "\\" + strFilename;

    // Create the decrypted file.
    strmOutput = new FileStream(DecryptedFileName, 
                                FileMode.Create, 
                                FileAccess.Write);

    CopyStream(strmPwdProtect, strmOutput);
}
catch (BadPasswordException)
{
    MessageBox.Show("Bad password");
}

Notes and things to avoid

  1. Upon decryption, the test application reads the arrLengh and arrName byte arrays. These are very small. Even when providing an incorrect password, decryption does not seem to fail immediately on the first several bytes. Probably, CryptoStream throws a cryptographic exception after reading several cipher blocks, 8 bytes each for TripleDES. This implies that the code in my example can get real garbage instead of the file name, and fail with a non-BadPasswordException trying to create this file, while the root cause is still a bad password. Happened to me several times while testing.
  2. Do not put FlushFinalBlock() in the finally{} block on the decrypting end. FlushFinalBlock is not a virtual function; it is not overridden in PwdTdsCryptoStream. In case of an incorrect password, FlushFinalBlock will throw an ObjectDisposedException in the finally{} block.
  3. Rijndael is apparently not supported by CryptDeriveKey(). It kept throwing a CryptographicException “Object identifier (OID) is unknown.” Though I found example code on the Internet that uses CryptDeriveKey() to create a Rijndael key, it didn’t work for me. Any idea why?

Conclusion

Literally, in several lines of code, a CryptoStream has been transformed into a much easier to use class. It provides all the functionality of a Stream while hiding the cryptography complexity behind a single password string. This gives me an opportunity to blissfully forget about cryptography for a while again.

References:

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Alex Perepletov
Software Developer (Senior)
Canada Canada
No Biography provided

Comments and Discussions

 
GeneralNice Work! PinmemberMember 437758718-Jun-08 22:19 
You have done a good work!
GeneralRijndael and CryptDeriveKey PinmemberJeremy Lloyd13-Feb-08 18:31 
AnswerRe: Rijndael and CryptDeriveKey PinmemberAlex Perepletov17-Feb-08 13:22 
GeneralA ready to use function PinmemberElmue29-Jan-08 7:44 
GeneralGreat Work Pinmembershirsatd15-Nov-06 6:29 
AnswerRe: Great Work PinmemberDaniel Ruehmer19-Oct-07 22:33 
General,Great Pinmembershirsatd15-Nov-06 6:25 
GeneralYou made is sound simple Pinmembermsali5-Sep-06 21:54 
GeneralRe: You made is sound simple PinmemberAlex Perepletov12-Sep-06 16:12 
GeneralVery nice Pinmembermikeperetz5-Sep-06 4:53 
GeneralRe: Very nice PinmemberAlex Perepletov12-Sep-06 15:59 
GeneralGreat Article! PinmemberDoug K. Wilson29-Aug-06 6:13 
GeneralRe: Great Article! PinmemberAlex Perepletov29-Aug-06 16:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140415.2 | Last Updated 28 Aug 2006
Article Copyright 2006 by Alex Perepletov
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid