Eric
Eric

Reputation: 4301

ProtectedData.Protect intermittent failure

I'm writing a password encryption routine. I've written the below app to illustrate my problem. About 20% of the time, this code works as expected. The rest of the time, the decryption throws a cryptographic exception - "The data is invalid".

I believe the problem is in the encryption portion, because the decryption portion works the same every time. That is, if the encryption routine yields a value that the decryption routine can decrypt, it can always decrypt it. But if the encryption routine yields a value that chokes the decryption routine, it always chokes. So the decrypt routine is consistent; the encrypt routine is not.

I suspect my use of Unicode encoding is incorrect, but I've tried others with the same result.

What am I doing wrong?

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Security.Cryptography;

namespace DataProtectionTest
{
    public partial class Form1 : Form
    {
        private static readonly byte[] entropy = { 1, 2, 3, 4, 1, 2, 3, 4 };
        private string password;
        public Form1()
        {
            InitializeComponent();
        }

        private void btnEncryptIt_Click(object sender, EventArgs e)
        {
            Byte[] pw = Encoding.Unicode.GetBytes(textBox1.Text);
            Byte[] encryptedPw = ProtectedData.Protect(pw, entropy, DataProtectionScope.LocalMachine);
            password = Encoding.Unicode.GetString(encryptedPw);     
        }

        private void btnDecryptIt_Click(object sender, EventArgs e)
        {
            Byte[] pwBytes = Encoding.Unicode.GetBytes(password);
            try
            {
                Byte[] decryptedPw = ProtectedData.Unprotect(pwBytes, entropy, DataProtectionScope.LocalMachine);
                string pw = Encoding.Unicode.GetString(decryptedPw);
                textBox2.Text = pw;
            }
            catch (CryptographicException ce)
            {
                textBox2.Text = ce.Message;
            }
        }
    }
}

Upvotes: 4

Views: 5708

Answers (6)

Dave Black
Dave Black

Reputation: 8049

You should never use any of the System.Text.Encoding classes for cipher text. You will experience intermittent errors. You should use Base64 encoding and the System.Convert class methods.

  1. To obtain an encrypted string from an encrypted byte[], you should use:

    Convert.ToBase64String(byte[] bytes)
    
  2. To obtain a a raw byte[] from a string to be encrypted, you should use:

    Convert.FromBase64String(string data)
    

For additional info, please refer to MS Security guru Shawn Fanning's post.

Upvotes: 3

Vladimir Arustamian
Vladimir Arustamian

Reputation: 842

This class should help:

public static class StringEncryptor
{
    private static readonly byte[] key = { 0x45, 0x4E, 0x3A, 0x8C, 0x89, 0x70, 0x37, 0x99, 0x58, 0x31, 0x24, 0x98, 0x3A, 0x87, 0x9B, 0x34 };

    public static string EncryptString(this string sourceString)
    {
        if (string.IsNullOrEmpty(sourceString))
        {
            return string.Empty;
        }

        var base64String = Base64Encode(sourceString);
        var protectedBytes = ProtectedData.Protect(Convert.FromBase64String(base64String), key, DataProtectionScope.CurrentUser);
        return Convert.ToBase64String(protectedBytes);
    }

    public static string DecryptString(this string sourceString)
    {
        if (string.IsNullOrEmpty(sourceString))
        {
            return string.Empty;
        }

        var unprotectedBytes = ProtectedData.Unprotect(Convert.FromBase64String(sourceString), key, DataProtectionScope.CurrentUser);
        var base64String = Convert.ToBase64String(unprotectedBytes);
        return Base64Decode(base64String);
    }

    private static string Base64Encode(string plainText)
    {
        var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
        return Convert.ToBase64String(plainTextBytes);
    }

    private static string Base64Decode(string base64EncodedData)
    {
        var base64EncodedBytes = Convert.FromBase64String(base64EncodedData);
        return Encoding.UTF8.GetString(base64EncodedBytes);
    }
}

Upvotes: 1

to StackOverflow
to StackOverflow

Reputation: 124746

The best solution is to convert the byte array to a base 64 string.

You can also use Latin-1 aka ISO-8859-1 aka codepage 28591 for this scenario, as it maps values in the range 0-255 unchanged. The following are interchangeable:

Encoding.GetEncoding(28591)
Encoding.GetEncoding("Latin1")
Encoding.GetEncoding("iso-8859-1")

With this encoding you will always be able to convert byte[] -> string -> byte[] without loss.

See this post for a sample that illustrates the use of this encoding.

Upvotes: 1

Nir
Nir

Reputation: 29594

The problem is the conversion to unicode and the end of the encryption method, Encoding.Unicode.GetString works only if the bytes you give it form a valid UTF-16 string.

I suspect that sometimes the result of ProtectedData.Protect is not a valid UTF-16 string - so Encoding.Unicode.GetString drops bytes that not make sense out of the returned string resulting in a string that can't be converted back into the encrypted data.

Upvotes: 1

Eric
Eric

Reputation: 4301

On the advice of a colleague, I opted for Convert.ToBase64String. Works well. Corrected program below.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Security.Cryptography;

namespace DataProtectionTest
{
    public partial class Form1 : Form
    {
        private static readonly byte[] entropy = { 1, 2, 3, 4, 1, 2, 3, 4 };
        private string password;
        public Form1()
        {
            InitializeComponent();
        }

        private void btnEncryptIt_Click(object sender, EventArgs e)
        {
            Byte[] pw = Encoding.Unicode.GetBytes(textBox1.Text);
            Byte[] encryptedPw = ProtectedData.Protect(pw, entropy, DataProtectionScope.LocalMachine);
            //password = Encoding.Unicode.GetString(encryptedPw);       
            password = Convert.ToBase64String(encryptedPw);
        }

        private void btnDecryptIt_Click(object sender, EventArgs e)
        {
            //Byte[] pwBytes = Encoding.Unicode.GetBytes(password);
            Byte[] pwBytes = Convert.FromBase64String(password);
            try
            {
                Byte[] decryptedPw = ProtectedData.Unprotect(pwBytes, entropy, DataProtectionScope.LocalMachine);
                string pw = Encoding.Unicode.GetString(decryptedPw);
                textBox2.Text = pw;
            }
            catch (CryptographicException ce)
            {
                textBox2.Text = ce.Message;
            }
        }
    }
}

Upvotes: 9

Stu Mackellar
Stu Mackellar

Reputation: 11638

I strongly suspect that it's the call to Encoding.Unicode.GetString that's causing the problem. You need to ensure that the data passed to the Unprotect call is exactly the same as that returned from the Protect call. If you're encoding the binary data as Unicode text as an interim step then you can't guarantee this. Why do you need this step anyway - why not just store the byte[]?

Upvotes: 0

Related Questions