BinaryFormatter & CryptoStream problem when deserializing

对着背影说爱祢 提交于 2019-12-10 18:59:12

问题


I'm getting a bit desperate here. I'm trying to write an encrypted file with a serialized object to disk and later retrieve the file, decrypt it and deserialize the object back.

UPDATE: I refactored the code to this:

using (Stream innerStream = File.Create(this.GetFullFileNameForUser(securityContext.User, applicationName)))
            {
                using (Stream cryptoStream = new CryptoStream(innerStream, GetCryptoProvider().CreateEncryptor(), CryptoStreamMode.Write))
                {
                    // 3. write to the cryptoStream 
                    //BinaryFormatter bf = new BinaryFormatter();
                    //bf.Serialize(cryptoStream, securityContext);
                    XmlSerializer xs = new XmlSerializer(typeof(SecurityContextDTO));
                    xs.Serialize(cryptoStream, securityContext);
                }
            }


 using (Stream innerStream = File.Open(this.GetFullFileNameForUser(user, applicationName), FileMode.Open))
        {
            using (Stream cryptoStream = new CryptoStream(innerStream, GetCryptoProvider().CreateDecryptor(), CryptoStreamMode.Read))
            {
                //BinaryFormatter bf = new BinaryFormatter();
                //return (SecurityContextDTO)bf.Deserialize(cryptoStream);
                XmlSerializer xs = new XmlSerializer(typeof(SecurityContextDTO));
                //CryptographicException here
                return (SecurityContextDTO)xs.Deserialize(cryptoStream);
            }
        }

Now I'm getting a cryptographic exception on deserialize: Bad Data

ORIGINAL:

I'm doing this:

public void StoreToFile(SecurityContextDTO securityContext, string applicationName)
    {
        if (securityContext.LoginResult.IsOfflineMode == false)
        {
            Stream stream = null;
            CryptoStream crStream = null;
            try
            {
                TripleDESCryptoServiceProvider cryptic = GetCryptoProvider();

                stream = File.Open(this.GetFullFileNameForUser(securityContext.User, applicationName), FileMode.Create);
                crStream = new CryptoStream(stream,
                   cryptic.CreateEncryptor(), CryptoStreamMode.Write);

                BinaryFormatter bFormatter = new BinaryFormatter();
                bFormatter.Serialize(crStream, securityContext);
            }
            catch(Exception)
            {
                throw;
            }
            finally
            {
                if (crStream != null)
                    crStream.Close();
            }
        }
    }



public SecurityContextDTO RetrieveFromFile(UserDTO user,string applicationName)
    {
        SecurityContextDTO objectToSerialize;
        Stream stream = null;
        CryptoStream crStream=null;
        try
        {
            stream = File.Open(this.GetFullFileNameForUser(user, applicationName), FileMode.Open);
             crStream= new CryptoStream(stream,
                GetCryptoProvider().CreateDecryptor(), CryptoStreamMode.Read);
            BinaryFormatter bFormatter = new BinaryFormatter();
            //Exception here
            objectToSerialize = (SecurityContextDTO)bFormatter.Deserialize(crStream); 
        }
        catch (Exception)
        {
            objectToSerialize = null;
        }
        finally
        {
            if (crStream!=null)
                crStream.Close();
        }
        return objectToSerialize;
    }


private static TripleDESCryptoServiceProvider GetCryptoProvider()
    {
        TripleDESCryptoServiceProvider cryptic = new TripleDESCryptoServiceProvider();
        try
        {
            cryptic.Key = ASCIIEncoding.ASCII.GetBytes(CrypKey);

            Rfc2898DeriveBytes db = new Rfc2898DeriveBytes("sdddsdsd", 8);
            cryptic.IV = db.GetBytes(8);
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            cryptic.Dispose();
        }
        return cryptic;
    }

Encrypting and writing works fine, the file appears on the disk and the content is there (encrypted of course). But when I call the retrieve method I always get a SerializationException

Binary stream '30' does not contain a valid BinaryHeader. Possible causes are invalid stream or object version change between serialization and deserialization.

When I leave the cryptographic methods out everything works fine.


回答1:


So,

You realize that in this code

private static TripleDESCryptoServiceProvider GetCryptoProvider()
{
    TripleDESCryptoServiceProvider cryptic = new TripleDESCryptoServiceProvider();
    try
    {
        cryptic.Key = ASCIIEncoding.ASCII.GetBytes(CrypKey);

        Rfc2898DeriveBytes db = new Rfc2898DeriveBytes("sdddsdsd", 8);
        cryptic.IV = db.GetBytes(8);
    }
    catch (Exception)
    {
        throw;
    }
    finally
    {
        cryptic.Dispose(); // <------- Don't do this until you are done decrypting.
    }
    return cryptic;
}

you will ALWAYS dispose of the provider meaning you are always using a random key and iv




回答2:


You are close. However, the stream you pass into the creation of CryptoStream is always, always, always the buffer that will hold your end result. It is not the stream that holds the data you want to encrypt or decrypt. I put the emphasis in there because I remember learning this for the first time and I did exactly what you were doing. So here:

// this is for encryption
var memStreamEncryptedData = new MemoryStream();
var encryptStream = new CryptoStream(memStreamEncryptedData, 
   transform, CryptoStreamMode.Write);

// this is for decryption
var memStreamDecryptedData = new MemoryStream();
var decryptStream = new CryptoStream(memStreamDecryptedData, 
   transform, CryptoStreamMode.Write);

Notice in both cases, CryptoStream is being initialized with a blank output stream. Your stream does not enter into the picture until later. So, during a write, you will do the following:

encryptStream.Write(dataToBeEncrypted);
encryptStream.FlushFinalBlock();
encryptStream.Close();
// memStreamEncryptedData now safely holds your encrypted data

And during the read, you will:

decryptStream.Write(dataToBeDecrypted);
encryptStream.FlushFinalBlock();
encryptStream.Close();
// memStreamDecryptedData now safely holds your decrypted data

So, to save you some trouble, here's a nice simple Symmetric method that will perform both encryption and decryption. The only difference between this and yours is that I am working directly on byte arrays, but perhaps that augmentation can be an exercise:

public static byte[] Symmetric(bool encrypt, byte[] plaintext, string ikey)
{
    if (plaintext.Length == 0) return plaintext;

    // setting up the services can be very expensive, so I'll cache them
    // into a static dictionary.
    SymmetricSetup setup;
    if (!_dictSymmetricSetup.TryGetValue(ikey, out setup))
    {
        setup = new SymmetricSetup();
        setup.des = new DESCryptoServiceProvider { Mode = CipherMode.CBC, 
            Padding = PaddingMode.Zeros };
        setup.hash = Hash(Encoding.ASCII.GetBytes(ikey));
        setup.key = setup.hash.ForceLength(8, 0);
        setup.IV = Encoding.ASCII.GetBytes("init vec");
        setup.des.Key = setup.key;
        setup.des.IV = setup.IV;

        setup.encrypt = setup.des.CreateEncryptor(setup.des.Key, setup.des.IV);
        setup.decrypt = setup.des.CreateDecryptor(setup.des.Key, setup.des.IV);
        _dictSymmetricSetup[ikey] = setup;
    }

    var transform = encrypt ? setup.encrypt : setup.decrypt;

    var memStreamEncryptedData = new MemoryStream();

    var encStream = new CryptoStream(memStreamEncryptedData, transform, CryptoStreamMode.Write);

    if (encrypt)
        encStream.Write(new[] {(byte) ((8 - (plaintext.Length + 1)%8)%8)}, 0, 1);

    encStream.Write(plaintext, 0, plaintext.Length);
    encStream.FlushFinalBlock();
    encStream.Close();

    memStreamEncryptedData.Flush();

    var ciphertext = memStreamEncryptedData.ToArray();

    byte b;

    if (!encrypt)
        if (byte.TryParse("" + ciphertext[0], out b))
            ciphertext = ciphertext.Skip(1).Take(ciphertext.Length - b - 1).ToArray();

    return ciphertext;
}

And to call it, you might do something like this:

static public byte[] DecryptData(this byte[] source, string password) {
    return Symmetric(false, source, password);
}

static public byte[] EncryptData(this byte[] source, string password) {
    return Symmetric(true, source, password);
}

Again, you'll do something slightly different to work with streams, but hopefully you get the gist. Instead of MemoryStream, it will be whatever stream you need to feed into your serializer.




回答3:


Some previous posts that can be of use:

How do I encrypt a string in vb.net using RijndaelManaged, and using PKCS5 padding?

Does BinaryFormatter apply any compression?

In later, you can see how I stacked compression with encryption with serialization. And it works.



来源:https://stackoverflow.com/questions/7026702/binaryformatter-cryptostream-problem-when-deserializing

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!