Quick Crypto

Quick Crypto

Not long ago, I wrote a little Windows tool called QuickCrypto, for encrypting and decrypting text on my desktop. Part of what I use it for is to encrypt selected portions of my appSettings.json files, for use with my options extensions. It works really well for that.

The tool also has a complete implementation of Rijndael, which a really good, 256 bit cipher that I’ve used for a long, long time.

I though I might walk through the operation of the tool, for anyone who might be curious.

The tool itself looks like this:

There are two tabs, one for DPAPI, the other for Rijndael. The DPAPI tab is currently highlighted. On the tab are two text areas. The top area is where plain text goes. Put plain text there, press the Encrypt button, and the encrypted text shows up in the bottom text area. Or, if you need to go in the other direction, put encrypted text on the bottom text area, press the Decrypt button, and the plain text appears in the top text area.

There is a drop list towards the top of the tab for choosing between user scope and machine scope. There is also a button that pops up another window. That window allows for generating custom entropy bytes, for the DPAPI library to use. It looks like this:

The window allows for the creation of custom entropy, using the up/down controls. Or, random entropy, using the button, or, you can reset the entropy back to the values I use in my options extensions.

The Rijndael tab looks similar to the DAPI tab:

We follow a similar UX here as before: plain text on top, encrypted text on bottom, Encrypt button, Decrypt button. This tab also contains a text field for a passkey and a SALT value. Passkey seems obvious, but, what the heck is a SALT!? Well, SALT (sometimes calls IV) is a value, used by the Rijndael algorithm, to generate unique hash values during a cryptographic run. So, using different SALT values on the same data means you’ll get different hash values. That difference helps to prevent certain types of attacks on your encrypted data. It doesn’t really matter what the SALT actually is. In fact, with this tool, I take whatever is entered here, combine with the passkey, and use it to generate a secure key that is stronger than anything we could type into these fields. So, as long as whatever is entered in the SALT field is at least 8 bytes long, we’re in business! There are no limits or requirements for the passkey – other than the fact that you should probably use one ;o)

The application itself is fairly unimpressive WPF code. To be candid, I kinda sorta tossed this code together when I needed it on a project. For that reason, I’ll just focus on the cryptographic parts. If you’re interested in any of the code for this tool though, it’s available online HERE.

I’ll start with the DataPrivacyViewModelclass, which is where I put the DPAPI code. Here is what that looks like:

public class DataPrivacyViewModel : ViewModelBase
{
    private byte[] _entropy = new byte[] { 4, 8, 15, 16, 23, 42 }; // Don't tell Hugo!!
        
    private IEnumerable<string> _scopes;
    private string _selectedScope;
    private string _plainText;
    private string _encryptedText;

    private DelegateCommand _entropyCommand;
    private DelegateCommand _encryptCommand;
    private DelegateCommand _decryptCommand;

    public byte[] Entropy
    {
        get { return _entropy; }
        set
        {
            _entropy = value;
            OnPropertyChanged();
        }
    }

    public IEnumerable<string> Scopes
    {
        get { return _scopes; }
        set
        {
            _scopes = value;
            OnPropertyChanged();
        }
    }

    public string SelectedScope
    {
        get { return _selectedScope; }
        set
        {
            _selectedScope = value;
            OnPropertyChanged();
        }
    }

    public string PlainText 
    { 
        get { return _plainText; }
        set
        {
            _plainText = value;
            OnPropertyChanged();
        }
    }

    public string EncryptedText
    { 
        get { return _encryptedText; } 
        set
        {
            _encryptedText = value;
            OnPropertyChanged();
        }
    }

    public DelegateCommand EntropyCommand =>
        _entropyCommand ?? (_entropyCommand = new DelegateCommand(
            ExecuteEntropyCommand,
            CanExecuteEntropyCommand
            ));

    public DelegateCommand EncryptCommand =>
        _encryptCommand ?? (_encryptCommand = new DelegateCommand(
            ExecuteEncryptCommand,
            CanExecuteEncryptCommand
            ));


    public DelegateCommand DecryptCommand =>
        _decryptCommand ?? (_decryptCommand = new DelegateCommand(
            ExecuteDecryptCommand,
            CanExecuteDecryptCommand
            ));

    public DataPrivacyViewModel()
    {
        Scopes = Enum.GetNames(typeof(DataProtectionScope));
        SelectedScope = Enum.GetName(typeof(DataProtectionScope), DataProtectionScope.LocalMachine);
    }

    void ExecuteEntropyCommand(object args)
    {
        var dialog = new EntropyWindow();
        dialog.Owner = Application.Current.MainWindow;
        (dialog.DataContext as EntropyWindowViewModel).Entropy = Entropy;

        if (true == dialog.ShowDialog())
        {
            Entropy = (dialog.DataContext as EntropyWindowViewModel).Entropy;
        }
    }

    bool CanExecuteEntropyCommand(object args)
    {
        return true;
    }

    void ExecuteEncryptCommand(object args)
    {
        try
        {
            var scope = (DataProtectionScope)Enum.Parse(
                typeof(DataProtectionScope),
                SelectedScope,
                true
                );

            var unprotectedBytes = Encoding.UTF8.GetBytes(
                PlainText
                );

            var protectedBytes = ProtectedData.Protect(
                unprotectedBytes,
                Entropy,
                scope
                );

            var protectedPropertyValue = Convert.ToBase64String(
                protectedBytes
                );

            EncryptedText = protectedPropertyValue;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    bool CanExecuteEncryptCommand(object args)
    {
        return true;
    }

    void ExecuteDecryptCommand(object args)
    {
        try
        {
            var scope = (DataProtectionScope)Enum.Parse(
                typeof(DataProtectionScope),
                SelectedScope,
                true
                );

            var encryptedBytes = Convert.FromBase64String(
                EncryptedText
                );

            var unprotectedBytes = ProtectedData.Unprotect(
                encryptedBytes,
                Entropy,
                scope
                );

            var unprotectedPropertyValue = Encoding.UTF8.GetString(
                unprotectedBytes
                );

            PlainText = unprotectedPropertyValue;
        } 
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    bool CanExecuteDecryptCommand(object args)
    {
        return true;
    }
}

The class derives from my ViewModelBase class, which is just a quick implementation of the INotifyPropertyChanged interface. I won’t cover the ViewModelBase class here.

The class contains several properties that are bound to controls on the corresponding WPF view. There are also several command objects that are also bound to the UI.

The constructor simply creates default values for the Scopes and SelectedScope properties.

The ExecuteEntropyCommand handler is what shows the popup dialog for changing the entropy bytes.

The ExecuteEncryptCommand handler is where we actually do something interesting. Here we take the values in the SelectedScope, PlainText, and Entropy properties, and feed them into the ProtectedData.Protect method, which is the DPAPI library method for encrypting data. The resulting encrypted bytes are converted to Base64, then poked back into the EncryptedText property, for display on the UI.

The ExecuteDecryptCommand handler is where we go the other direction, taking the values in the SelectedScope, EncryptedText, and Entropy properties, and feeding them into the ProtectedData.Unprotect method, which is the DPAPI library method for decrypting data. Then we convert the resulting bytes to text before poking the results into the PlainText property, for display on the UI.

The RijndaelViewModel class is where I put the Rijndael related code. Here is what that class looks like:

public class RijndaelViewModel : ViewModelBase
{
    private string _salt;
    private string _password;
    private string _plainText;
    private string _encryptedText;

    private DelegateCommand _encryptCommand;
    private DelegateCommand _decryptCommand;

    public string Salt
    {
        get { return _salt; }
        set
        {
            _salt = value;
            OnPropertyChanged();
        }
    }

    public string Password
    {
        get { return _password; }
        set
        {
            _password = value;
            OnPropertyChanged();
        }
    }

    public string PlainText
    {
        get { return _plainText; }
        set
        {
            _plainText = value;
            OnPropertyChanged();
        }
    }

    public string EncryptedText
    {
        get { return _encryptedText; }
        set
        {
            _encryptedText = value;
            OnPropertyChanged();
        }
    }

    public DelegateCommand EncryptCommand =>
        _encryptCommand ?? (_encryptCommand = new DelegateCommand(
            ExecuteEncryptCommand,
            CanExecuteEncryptCommand
            ));

    public DelegateCommand DecryptCommand =>
        _decryptCommand ?? (_decryptCommand = new DelegateCommand(
            ExecuteDecryptCommand,
            CanExecuteDecryptCommand
            ));

    void ExecuteEncryptCommand(object args)
    {
        try
        {
            var encryptedBytes = new byte[0];
            var passwordBytes = Encoding.UTF8.GetBytes(
                Password
                );

            var saltBytes = Encoding.UTF8.GetBytes(
                Salt
                );

            using (var alg = new RijndaelManaged())
            {
                alg.KeySize = 256;
                alg.BlockSize = 128;

                var key = new Rfc2898DeriveBytes(
                    passwordBytes,
                    saltBytes,
                    10000
                    );

                alg.Key = key.GetBytes(alg.KeySize / 8);
                alg.IV = key.GetBytes(alg.BlockSize / 8);

                using (var enc = alg.CreateEncryptor(
                    alg.Key,
                    alg.IV
                    ))
                {
                    using (var stream = new MemoryStream())
                    {
                        using (var cryptoStream = new CryptoStream(
                            stream,
                            enc,
                            CryptoStreamMode.Write
                            ))
                        {
                            using (var writer = new StreamWriter(
                                cryptoStream
                                ))
                            {
                                writer.Write(
                                    PlainText
                                    );
                            }

                            encryptedBytes = stream.ToArray();
                        }
                    }
                }
            }

            var encryptedValue = Convert.ToBase64String(
                encryptedBytes
                );

            EncryptedText = encryptedValue;
        }
        catch (Exception ex)
        {
            EncryptedText = "";
            MessageBox.Show(ex.Message);
        }
    }

    bool CanExecuteEncryptCommand(object args)
    {
        return true;
    }

    void ExecuteDecryptCommand(object args)
    {
        try
        {
            var encryptedBytes = Convert.FromBase64String(
                EncryptedText
                );

            var passwordBytes = Encoding.UTF8.GetBytes(
                Password
                );

            var saltBytes = Encoding.UTF8.GetBytes(
                Salt
                );

            var plainValue = "";

            using (var alg = new RijndaelManaged())
            {
                alg.KeySize = 256;
                alg.BlockSize = 128;

                var key = new Rfc2898DeriveBytes(
                    passwordBytes,
                    saltBytes,
                    10000
                    );

                alg.Key = key.GetBytes(alg.KeySize / 8);
                alg.IV = key.GetBytes(alg.BlockSize / 8);

                using (var dec = alg.CreateDecryptor(
                    alg.Key,
                    alg.IV
                    ))
                {
                    using (var stream = new MemoryStream(
                        encryptedBytes
                        ))
                    {
                        using (var cryptoStream = new CryptoStream(
                            stream,
                            dec,
                            CryptoStreamMode.Read
                            ))
                        {
                            using (var reader = new StreamReader(
                                cryptoStream
                                ))
                            {
                                plainValue = reader.ReadToEnd();
                            }
                        }
                    }
                }
            }

            PlainText = plainValue;
        }
        catch (Exception ex)
        {
            PlainText = "";
            MessageBox.Show(ex.Message);
        }
    }

    bool CanExecuteDecryptCommand(object args)
    {
        return true;
    }
}

Just like with the last class, this one also derives form my ViewModelBase class. It also contains a number of properties that are bound to the WPF UI. It also contains a number of command objects that are also bound to the WPF UI.

The ExecuteEncryptCommand handler is where we get to have some fun. Here is where I added the code to implement the Rijndael encryption.

I start by converting the password into a byte array. I then convert the SALT into another byte array.

After that I create an instance of the RijndaelManaged class. This class is part of .NET and simply implements the Rijndael algorithm for us. I then manually set the key and block size to something I know will work, for the algorithm I’m using.

Then I create a new instance of the Rfc2898DerivedBytes class, using the password bytes, the SALT bytes, and a relatively large number, for iterations. The Rfc2898DerivedBytes object generates the actual key we’ll use for our cryptographic operation. Internally, the object uses that number, 10000, to loop through and re-hash the resulting key, over and over again. That means, anyone who wants to brute force attack our cryptographic key will also have to iterate that many times for every attempt to match our hash codes. That will slow them down, nicely. :o)

The other reason for using the Rfc2898DerivedBytes object here, instead of directly using our password and SALT values, is that, in .NET, all strings are UNICODE. That means that all (assuming we’re using English), .NET strings have zeros for the upper bytes. That, in turn, means that anyone attacking our password/SALT can throw out all the guesses that involve non-zero upper bytes. That makes their hacking job much easier! The Rfc2898DerivedBytes generates a key that doesn’t suffer from this phenomena. That makes a hacker’s job harder. I like making their jobs harder. :o)

After generating the key, we set the values in the RijndaelManaged algorithm object we created earlier. Now we’re setup with a secure key and a strong encryption algorithm. We’re ready to roll!

Next we create an encryptor object, which contains the logic needed to perform an encryption operation. Next we create a MemoryStream object, as a temporary buffer. Next we wrap that temporary stream in a CryptoStream object. Next we wrap the crypto stream in a .NET StreamWriter object. Finally, after all that wrapping, we write the plain text to the .NET writer, which kicks off the encryption operation and leaves the encrypted results in the MemoryStream object. From there, we write the contents of the stream to a byte array. convert the bytes to a Base64 encoded string, and poke the results into the EncryptedText property, which is bound to the WPF UI.

Going the other way is just as much fun. The ExecuteDecryptCommand handler is where I added the code to decrypt strings. That method starts by decoding the encrypted text in the EncryptedText property into a byte array. It also converts the password and SALT values into bytes array, as well.

Next the method creates an instance of the RijndaelManaged class. Again, we set the sizes for the key and block sizes, in the RijndaelManaged object. And again, we generate a strong crypto key using an Rfc2898DeriveBytes object, along with the passkey and SALT values. After generating the key we set the values back into the RijndaelManaged we created before.

From there, we create a decryptor object, which contains all the logic required to perform the decryption operation, for us. Once more, we create a MemoryStream object, then we create a CryptoStream object to wrap that first stream with. From there, we wrap the crypto stream in a standard .NET StreamReader object. Then, we read the decrypted bytes out using the reader’s ReadToEnd method. After that, we poke the results into the PlainText property, on the view model, which is bound to the WPF UI.

That’s about it. Using this tool, it’s possible to quickly and easily encrypt and/or decrypt text using either Rijndael or DPAPI algorithms. I use this tool for most of my local encryption needs. It works well. Feel free to use it if you like it.

One thing I’ll mention, real quick, is that I don’t yet know how to get my Azure build pipeline to to generate a release EXE back to the GITHUB repository. Until I figure that out, I won’t have downloadable bits for anyone. You’ll be stuck with downloading the code and building it on your own box.

Since I use Syncfusion controls, for my UI, that also means you’ll either have to: (A) put up with the annoying popup text, from Syncfusion, urging you to buy their controls, or (B) go get one of Syncfusion’s ridiculously generous free Community licenses from HERE, then pluggin in your own license key to stop the annoying poup.

Did I mention that Syncfusion Community license is FREE? As in, FREE? Yeah, go get one before they change their minds.

Once I get the releases issue resolved, in my build pipeline, I’ll plug in my own Syncfusion key and we won’t have to deal with any of this anymore.

Thanks for reading!

Photo by Markus Spiske on Unsplash