locked
Deserialize causes exception "Unable to find assembly" RRS feed

  • Question

  • This appears to be an old problem which never got fixed (Bug ID 119402).

     

    I am using VS 2005 and .NET 3.0.  I wanted to encrypt some keys/passwords, Serialize, then write them to a file with one application. Then embed the resultant file in another application which contains the keys to decrypt. But upon deserialize, I get an exception "Unable to find assembly". The assembly of course does not exist in the second application. I tried FormatterAssembly.Simple but this setting did not change anything.

     

    Is this still a bug or am I just doing something wrong?

     

    Some code fragments below: (Original code example located at http://www.codeproject.com/csharp/simple_password_manager.asp)

     

    Code Snippet

    public Storage(byte [] bytes)

    {

    Entries = new List<Entry>();

    using (CryptoCore cryptor = new CryptoCore())

    {

    byte[] storage = cryptor.DecryptBuffer(bytes);

    using (MemoryStream ms = new MemoryStream(storage))

    {

    BinaryFormatter bf = new BinaryFormatter();

    bf.AssemblyFormat = FormatterAssemblyStyle.Simple;

    bf.FilterLevel = TypeFilterLevel.Low;

    Entries = ((Storage)bf.Deserialize(ms)).Entries;

    }

    }

    }

     

    public void Serialize(string filePath)

    {

    byte[] storage;

    using (MemoryStream ms = new MemoryStream())

    {

    BinaryFormatter bf = new BinaryFormatter();

    bf.AssemblyFormat = FormatterAssemblyStyle.Simple;

    bf.Serialize(ms, this);

    storage = ms.ToArray();

    }

    using (CryptoCore cryptor = new CryptoCore())

    {

    File.WriteAllBytes(filePath, cryptor.EncryptBuffer(storage));

    }

    }

     

    Wednesday, September 26, 2007 11:08 PM

Answers

  •  

    I think you are assuming a different behaviour for FormatterAssemblyStyle.Simple. You still have to have the assembly, only the identity checks are less through.

     

    Thank you

    Thursday, September 27, 2007 12:09 PM
    Moderator
  • I have adjusted my two applications to use a DLL that does the encryptions/decryptions and serialization, but still have not got it to work.

     

    Now I am getting "Unable to load type CurrentNameSpace.Storage. Storage is located in a different NameSpace and DLL which I have referenced and have also added a using statement.

     

    Why is BinaryFormatter.Deserialize not looking in the DLL for Storage?

     

    Please help, thanks,

    Jeff

    Thursday, September 27, 2007 5:17 PM
  • When my error changed from "Unable to find assembly" to "Unable to load type", this sent me totally down the wrong road. In the end the second error had little to nothing to do with the assembly and Storage class located now in a DLL, but my pw.sps file was generated with an older version of my Storage class which resulted in this new error. I had copied the pw.sps file but I had the build action on the file set to resource and it was not recopying it to the output directory. Changing the build action to none allowed the file to be copied correctly. I had another file key.sps which was being used as an embedded resource which worked all along.

     

    It is a bitter sweat victory since it took way too long to discover the incompatibility. I wish the exception had something to do with the file during reading rather than pointing to the Storage type.

     

    Thanks for all the help.

     

     

    Saturday, September 29, 2007 12:38 AM

All replies

  •  

    I think you are assuming a different behaviour for FormatterAssemblyStyle.Simple. You still have to have the assembly, only the identity checks are less through.

     

    Thank you

    Thursday, September 27, 2007 12:09 PM
    Moderator
  • Thank you for your fast response. I reread the description of Style.Simple and see your point.

     

    "In simple mode, the assembly used during deserialization need not match exactly the assembly used during serialization. Specifically, the version numbers need not match"

     

    But the answer to my question is still not completely answered. The only thing I can think of is that I need a separate DLL with it's own assembly which is used by both programs to do the serialization.

     

    Is this the recommended solution or is there a method which does not require an external DLL?

    Thursday, September 27, 2007 3:34 PM
  • I have adjusted my two applications to use a DLL that does the encryptions/decryptions and serialization, but still have not got it to work.

     

    Now I am getting "Unable to load type CurrentNameSpace.Storage. Storage is located in a different NameSpace and DLL which I have referenced and have also added a using statement.

     

    Why is BinaryFormatter.Deserialize not looking in the DLL for Storage?

     

    Please help, thanks,

    Jeff

    Thursday, September 27, 2007 5:17 PM
  • Are you still using "Simple" serialization? Can you post the code of the different parts of the app?

     

    In your case, why not just serialize the byte array instead of the container class?

     

    Thursday, September 27, 2007 5:25 PM
    Moderator
  • Yes, the Simple setting is still in there. I think the class is being serialzed because there are multiple entries that are written to one file.  I have inserted four code blocks below. Most have not been modified from the original example code, I started with.  Is there a better way to attach files or maintain the formatting?

     

    The first code block is the Storage class

    The second code block is the crypt class which the Storage class uses.

    The third code block is the first windows app used to set key/password  values and call the storage methods 

    The fourth code block is located in the second windows app and includes the method that calls the Storage class to perform the decrypt and deserialization.

     

    Only the Storage and Crypt classes are included in the DLL used by both applications.

     

    It is the deserialzation in the Storage method that is throwing the exception.

     

    Hopefully, I have not confused you completely. I know I have a lot to learn still.

     

    Thanks,

    Jeff

     

    First Code Block

    Code Block

    namespace Mgmt

    {

    [Serializable]

    public class Storage

    {

    [Serializable]

    public struct Entry

    {

    string name;

    public string Name

    {

    get { return this.name; }

    set { this.name = value; }

    }

     

    string user;

    public string User

    {

    get { return this.user; }

    set { this.user = value; }

    }

     

    string password;

    public string Password

    {

    get { return this.password; }

    }

     

    string site;

    public string Site

    {

    get { return this.site; }

    set { this.site = value; }

    }

     

    string comment;

    public string Comment

    {

    get { return this.comment; }

    set { this.comment = value; }

    }

     

    public Entry(string name, string user, SecureString password, string site, string comment)

    {

    this.name = name;

    this.user = user;

    this.comment = comment;

    this.site = site;

    using (Crypt cryptor = new Crypt())

    {

    IntPtr ptr = Marshal.SecureStringToBSTR(password);

    this.password = cryptor.EncryptString(Marshal.PtrToStringAuto(ptr));

    Marshal.ZeroFreeBSTR(ptr);

    }

    }

     

    public void UpdatePassword(SecureString password)

    {

    using (Crypt cryptor = new Crypt())

    {

    IntPtr ptr = Marshal.SecureStringToBSTR(password);

    this.password = cryptor.EncryptString(Marshal.PtrToStringAuto(ptr));

    Marshal.ZeroFreeBSTR(ptr);

    }

    }

    }

     

    public List<Entry> Entries;

     

    public Storage()

    {

    Entries = new List<Entry>();

    }

     

    public Storage(string filePath)

    {

    Entries = new List<Entry>();

    using (Crypt cryptor = new Crypt())

    {

    byte[] storage = cryptor.DecryptBuffer(File.ReadAllBytes(filePath));

    using (MemoryStream ms = new MemoryStream(storage))

    {

    BinaryFormatter bf = new BinaryFormatter();

    bf.AssemblyFormat = FormatterAssemblyStyle.Simple;

    Entries = ((Storage)bf.Deserialize(ms)).Entries;

    }

    }

    }

     

    public void Serialize(string filePath)

    {

    byte[] storage;

    using (MemoryStream ms = new MemoryStream())

    {

    BinaryFormatter bf = new BinaryFormatter();

    bf.AssemblyFormat = FormatterAssemblyStyle.Simple;

    bf.Serialize(ms, this);

    storage = ms.ToArray();

    }

    using (Crypt cryptor = new Crypt())

    {

    File.WriteAllBytes(filePath, cryptor.EncryptBuffer(storage));

    }

    }

    }

    }

     

     

     

    Second Code block

    Code Block

    namespace Mgmt

    {

    public sealed class Crypt : IDisposable

    {

    private static readonly byte[] Key = {

    0xda, 0x3c, 0x35, 0x6f, 0xbd, 0xd, 0x87, 0xf0,

    0x9a, 0x7, 0x6d, 0xab, 0x7e, 0x82, 0x36, 0xa,

    0x1a, 0x5a, 0x77, 0xfe, 0x74, 0xf3, 0x7f, 0xa8,

    0xaa, 0x4, 0x11, 0x46, 0x6b, 0x2d, 0x48, 0xa1

    };

     

    private static readonly byte[] IV = {

    0x6d, 0x2d, 0xf5, 0x34, 0xc7, 0x60, 0xc5, 0x33,

    0xe2, 0xa3, 0xd7, 0xc3, 0xf3, 0x39, 0xf2, 0x16

    };

     

    private SymmetricAlgorithm algorithm;

     

    public Crypt()

    {

    this.algorithm = new RijndaelManaged();

    this.algorithm.Mode = CipherMode.CBC;

    this.algorithm.Key = Key;

    this.algorithm.IV = IV;

    }

     

    public void Dispose()

    {

    this.algorithm.Clear();

    }

     

    public void SetBinaryKeys(byte[] Key, byte[] IV)

    {

    this.algorithm.Key = Key;

    this.algorithm.IV = IV;

    }

     

    public void ExtractBinaryKeys(out byte[] Key, out byte[] IV)

    {

    Key = this.algorithm.Key;

    IV = this.algorithm.IV;

    }

     

    private byte[] Process(byte[] data, int startIndex, int count, ICryptoTransform cryptor)

    {

    //

    // the memory stream granularity must match the block size

    // of the current cryptographic operation

    //

    int capacity = count;

    int mod = count % algorithm.BlockSize;

    if (mod > 0)

    {

    capacity += (algorithm.BlockSize - mod);

    }

    MemoryStream memoryStream = new MemoryStream(capacity);

    CryptoStream cryptoStream = new CryptoStream(

    memoryStream,

    cryptor,

    CryptoStreamMode.Write);

    cryptoStream.Write(data, startIndex, count);

    cryptoStream.FlushFinalBlock();

    cryptoStream.Close();

    cryptoStream = null;

    cryptor.Dispose();

    cryptor = null;

    return memoryStream.ToArray();

    }

     

    public byte[] EncryptBuffer(byte[] cleanBuffer)

    {

    byte[] output;

    // Encryptor object

    ICryptoTransform cryptoTransform = this.algorithm.CreateEncryptor();

    // Get the result

    output = this.Process(cleanBuffer, 0, cleanBuffer.Length, cryptoTransform);

    //clean

    cryptoTransform.Dispose();

    return output;

    }

     

    public byte[] DecryptBuffer(byte[] cryptoBuffer)

    {

    byte[] output;

    // Decryptor object

    ICryptoTransform cryptoTransform = this.algorithm.CreateDecryptor();

    // Get the result

    output = this.Process(cryptoBuffer, 0, cryptoBuffer.Length, cryptoTransform);

    //clean

    cryptoTransform.Dispose();

    return output;

    }

     

    public string EncryptString(string plainText)

    {

    return Convert.ToBase64String(EncryptBuffer(Encoding.UTF8.GetBytes(plainText)));

    }

     

    public string DecryptString(string encyptedText)

    {

    return Encoding.UTF8.GetString(DecryptBuffer(Convert.FromBase64String(encyptedText)));

    }

    }

    }

     

     

     

     

    Third Code block

    Code Block

    namespace KeyGen

    {

    ///

    /// Interaction logic for Window1.xaml

    ///

    public partial class Window1 : System.Windows.Window

    {

    Params cnt;

    string product;

    string productDesc;

    string customer;

    string customerDesc;

    string level;

    string levelDesc;

     

    public Window1()

    {

    InitializeComponent();

    cnt = new Params();

    product = "";

    customer = "";

    level = "";

    UpdateDisplay();

    }

     

    void SetProduct(object sender, SelectionChangedEventArgs args)

    {

    ListBoxItem lbi = ((sender as ListBox).SelectedItem as ListBoxItem);

    if (lbi != null)

    {

    product = lbi.Name.ToString();

    productDesc = lbi.Content.ToString();

    cnt.setParams(product, customer);

    }

    else

    {

    product = "";

    productDesc = "";

    }

    }

     

    void SetCustomer(object sender, SelectionChangedEventArgs args)

    {

    ListBoxItem lbi = ((sender as ListBox).SelectedItem as ListBoxItem);

    if (lbi != null)

    {

    customer = lbi.Name.ToString();

    customerDesc = lbi.Content.ToString();

    if (product == "")

    {

    MessageBox.Show("Error: Please select a product first");

    }

    else

    {

    cnt.setParams(product, customer);

    }

    }

    else

    {

    customer = "";

    customerDesc = "";

    }

    }

     

    void SetLevel(object sender, SelectionChangedEventArgs args)

    {

    ListBoxItem lbi = ((sender as ListBox).SelectedItem as ListBoxItem);

    if (lbi != null)

    {

    level = lbi.Name.ToString();

    levelDesc = lbi.Content.ToString();

    }

    else

    {

    level = "";

    levelDesc = "";

    }

    }

     

    // Update ListBox with products, levels, and customers

    void UpdateDisplay()

    {

    ListBoxItem lbi;

     

    for (int p = 0; p < cnt.products.Length; p=p+2)

    {

    lbi = new ListBoxItem();

    lbi.Name = cnt.products[p];

    lbi.Content = cnt.products[p+1];

    ProductsList.Items.Add(lbi);

    }

     

    for (int l = 0; l < cnt.levels.Length; l=l+2)

    {

    lbi = new ListBoxItem();

    lbi.Name = cnt.levels[l];

    lbi.Content = cnt.levels[l];

    LevelsList.Items.Add(lbi);

    }

     

    for (int c = 0; c < cnt.customers.Length; c=c+2)

    {

    lbi = new ListBoxItem();

    lbi.Name = cnt.customers[c];

    lbi.Content = cnt.customers[c+1];

    CustomersList.Items.Add(lbi);

    }

    }

     

    void GenerateSPS(object sender, RoutedEventArgs e)

    {

    if (product == "" || level == "")

    {

    MessageBox.Show("Error: A product and a level must be selected.");

    }

    else

    {

    Storage KeyStorage = new Storage();

    Storage CurrentStorage = new Storage();

    SecureString password = new SecureString();

     

    if (File.Exists(cnt.outFile))

    File.Delete(cnt.outFile);

     

    if (File.Exists(cnt.keyFile))

    File.Delete(cnt.keyFile);

     

    for (int l = 0; l < cnt.levels.Length; l=l+2)

    {

    for (int p = 0; p < cnt.levels[l+1].Length; p++)

    {

    password.AppendChar(cnt.levels[l+1][p]);

    }

     

    if (level == cnt.levels[l])

    CurrentStorage.Entries.Add(new Storage.Entry("key", cnt.levels[l], password, "", ""));

    KeyStorage.Entries.Add(new Storage.Entry("key", cnt.levels[l], password, "", ""));

    password.Clear();

    }

     

    String site = "";

    String comment = "";

     

    for (int n = 0; n < cnt.name.Length; n++)

    {

    for (int p = 0; p < cnt.pw[n].Length; p++)

    {

    password.AppendChar(cnt.pw[n][p]);

    }

     

    CurrentStorage.Entries.Add(new Storage.Entry(cnt.name[n],

    cnt.users[n], password, site, comment));

     

    password.Clear();

    }

     

    KeyStorage.Serialize(cnt.keyFile);

    CurrentStorage.Serialize(cnt.outFile);

    MessageBox.Show("Password and key file generation complete!");

    }

    }

    }

    }

     

     

    Fourth Code Block

    Code Block

    public Storage InitPwStorage(string AppHome)

    {

    String PwFile = AppHome + "pw.sps";

    Storage CurrentStorage = null;

     

    if (File.Exists(PwFile))

    {

    CurrentStorage = new Mgmt.Storage(PwFile);

    }

    else

    {

    System.Windows.MessageBox.Show("Error: No default password file found.");

    }

     

    return CurrentStorage;

    }

     

     

    Thursday, September 27, 2007 6:09 PM
  • When deserializing you always need to have the assemblies with all the target types available. If the serializer cannot find a type it will throw an exception. This is actually not a bug.

     

    Thanks,

     

     

    Thursday, September 27, 2007 10:06 PM
  • I'm not sure I understand what available means? It appears that all the types are available. The Storage type is included in the DLL and is referenced in the project. At runtime it is looking in the wrong namespace or assembly for the Storage Type. But only within the Deserializer.

    Thursday, September 27, 2007 10:11 PM
  • Ok, how is the Storage type packaged?

     

    a. Do you include the same code in the two applications and compile it in both assemblies?

    b. Do you have a single assembly with the type and both applications reference the same assembly?

     

    If the answer is a) this will not work, if the type is in different assemblies it consider different

    If the answer is b), let me ask one more question, how are you deploying the assembly

     

    a. Is it in the GAC?

    b. Is it in the AppPath (same directory as the executable, bin directory in Asp.Net?

     

    Thanks,

     

    Friday, September 28, 2007 1:46 AM
  • Thank you for responding. This problem should be simple but I just don't understand something about the runtime environment.

    The Storage and Crypt Classes exists in their own namespace and is compiled once into a DLL. This DLL is referenced (via add reference in VS) in both applications. Both these classes are included above. So I would say only one assemply exists for these classes. Answer is B.

    VS is copying the DLL to the application path bin/debug. I think it is doing this because of the added reference in the second application. So the answer is AppPath. After looking up GAC, I definately did not do this. My application will be deployed on client machines so the library would have to be packaged with the application. I did add a key file using VS because I read that maybe because it wasn't using strong names that the CLR may be ignoring it. The key file didn't help.

    I need to describe the second application a little more maybe. The second applicaton contains two namespaces, lets just say top and utils. Top uses classes and methods that exist in utils. The Storage  class is accessed from the utils names space. The exception that is being returned is Unable to load type utils.Storage.  Storage should be found at Mgmt.Storage.


    Anymore clues?

    Thanks,
    Jeff
    Friday, September 28, 2007 2:20 PM
  • When my error changed from "Unable to find assembly" to "Unable to load type", this sent me totally down the wrong road. In the end the second error had little to nothing to do with the assembly and Storage class located now in a DLL, but my pw.sps file was generated with an older version of my Storage class which resulted in this new error. I had copied the pw.sps file but I had the build action on the file set to resource and it was not recopying it to the output directory. Changing the build action to none allowed the file to be copied correctly. I had another file key.sps which was being used as an embedded resource which worked all along.

     

    It is a bitter sweat victory since it took way too long to discover the incompatibility. I wish the exception had something to do with the file during reading rather than pointing to the Storage type.

     

    Thanks for all the help.

     

     

    Saturday, September 29, 2007 12:38 AM