views:

70

answers:

3

Sort of a continuation from an earlier question, done a bunch of googling, but I don't think I'm on the right path here. Essentially what I'm doing is opening a file with a segment of code like so:

byte[] cipherText = File.ReadAllBytes(encryptedFile);

Then passing that byte array to a static bool method which calls the actual decryption method and returns true if no cryptographic exception is thrown. On my main form, I've got a button click which calls the static bool from an If statement, unlocking some additional interface items if it comes back true, and popping up an "invalid password" messagebox if it returns false.

I stepped through the entire process, and its throwing a "Padding is invalid and cannot be removed." exception, which some searching has revealed that should happen when the wrong decryption key is supplied. I know I'm passing the same key, at least I'm typing it in, so I'm assuming the problem has to do with either the way the byte[] is passed, or with the streams themselves. Here's an example of the decryption method:

public static string Decrypt(string password, byte[] ciphertext)
    {
        byte[] key, iv;
        CreateKeyIV(password, out key, out iv);
        using (MemoryStream encrypted = new MemoryStream(ciphertext))
        using (CryptoStream dec = new CryptoStream(encrypted, _algorithm.CreateDecryptor(key, iv), CryptoStreamMode.Read))
        using (StreamReader reader = new StreamReader(dec))
        {
            try
            {
                return reader.ReadToEnd();
            }
            catch (CryptographicException)
            {
                throw new CryptographicException("Invalid password.");
            }
            finally
            {
                encrypted.Close();
                dec.Close();
                reader.Close();
            }
        }
    }

Does anyone see what I'm missing?

A: 

You said that you load the data from file binary. But do you also save them binary? That may be the problem.

Markos
Not sure I follow you. The file starts out as a single word of text. That text is then sent to an encryption method which returns an array of the encrypted data. which is then passed into a Streamwriter in the following manner: byte[] newCipher = Encryptor.Encrypt(newPass, newPass); string writeIt = System.Text.Encoding.UTF8.GetString(newCipher)When looking inside the file afterwards, the only thing in it is a single encrypted word.
Stev0
@Stev0: That's your problem then. See my answer.
Jon Skeet
A: 

I won't open a StreamReader on a CryptoStream, but actually I will call directly the method Read of the class CryptoStream, passing a byte[] buffer that it will hold the decrypted data.


Here is a snippet of mine. I've used it with MemoryStream and FileStream.

Stream s = ...;

ICryptoTransform cTransform = Encryptor.CreateDecryptor();
using (CryptoStream cStream = new CryptoStream(s, cTransform, CryptoStreamMode.Read)) {
byte[] mCryptoData = new byte[s.Length];
cStream.Read(mCryptoData, 0, mCryptoData.Length);
...
Luca
Could you show me a simple example? I understand what the Read method does, but do I not need to specify the number of bytes to read? How do I know how many bytes to pass to the method?
Stev0
There's nothing wrong with creating a `StreamReader` over a `CryptoStream`, if the decrypted data will actually be text. Why introduce another layer?
Jon Skeet
You are right! @Stev0 Good point for the length of the stream. @Jon Skeet Since CryptoStream is a Stream, I've always used "as is" without wrapping on another Stream implementation; however I will investigate more on this issue... it may have good consequences! :) It could be usefull on NetworkStreams?
Luca
+3  A: 

The decryption code looks okay to me, although I would make a few changes:

  • Why not pass in a stream or filename rather than the encrypted data as a byte array? There's no need to read it all in one go, then wrap the data in a MemoryStream when you can just use a FileStream as the source
  • You don't need to call Close - that's what the using statements are for
  • Why are you throwing a new CryptographicException which have miscommunicated the problem, rather than letting the existing exception bubble up?

In other words, this is what my method would look like:

public static string Decrypt(string password, Stream encrypted)
{
    byte[] key, iv;
    CreateKeyIV(password, out key, out iv);
    using (CryptoStream dec = new CryptoStream(encrypted,
           _algorithm.CreateDecryptor(key, iv), CryptoStreamMode.Read))
    using (StreamReader reader = new StreamReader(dec))
    {
        return reader.ReadToEnd();
    }
 }

However, you've shown what's wrong in another comment:

 // Bad code! Do not use!
 byte[] newCipher = Encryptor.Encrypt(newPass, newPass);
 string writeIt = System.Text.Encoding.UTF8.GetString(newCipher);

You're mangling your encrypted data. The result of encryption will rarely be a valid UTF-8 string. You should just write it straight to disk in binary, never trying to convert it to text. Either your encryption code could do this directly, or you could call File.WriteAllBytes(). If you absolutely have to convert arbitrary binary data to a string at some point, you should convert it to base64, using Convert.ToBase64String(data). That way you can definitely get at the same binary data later on by converting it back again. Trying to "decode" it as text is an almost sure-fire way to lose data - as you're doing here.

Jon Skeet