views:

564

answers:

4

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;
      }
     }
    }
}
A: 

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[]?

Stu Mackellar
I'm converting back to a string because I need to store it in a database as varchar.
Eric
A: 

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.

Joe
+3  A: 

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;
      }
     }
    }
}
Eric
+1  A: 

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.

Nir