Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I encrypt multiple strings using one encryptor on MonoTouch?

I'm using the following (trimmed-down) class to encrypt some data before sending it from an iPad app to a WCF web service.

public class FlawedAlgorithm
{
    protected static byte[] key = { 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42 };
    protected static byte[] vector = { 13, 37, 13, 37, 13, 37, 13, 37, 13, 37, 13, 37, 13, 37, 13, 37 };
    protected ICryptoTransform encryptor, decryptor;
    protected UTF8Encoding encoder;

    public FlawedAlgorithm()
    {
        using (var rijndael = new RijndaelManaged())
        {
            this.encryptor = rijndael.CreateEncryptor(key, vector);
            this.decryptor = rijndael.CreateDecryptor(key, vector);
        }

        this.encoder = new UTF8Encoding();
    }

    public string Encrypt(string unencrypted)
    {
        var buffer = this.encoder.GetBytes(unencrypted);

        return Convert.ToBase64String(Encrypt(buffer));
    }

    public string Decrypt(string encrypted)
    {
        var buffer = Convert.FromBase64String(encrypted);

        return this.encoder.GetString(Decrypt(buffer));
    }

    private byte[] Encrypt(byte[] buffer)
    {
        var encryptStream = new MemoryStream();

        using (var cryptoStream = new CryptoStream(encryptStream, this.encryptor, CryptoStreamMode.Write))
        {
            cryptoStream.Write(buffer, 0, buffer.Length);
        }

        return encryptStream.ToArray();
    }

    private byte[] Decrypt(byte[] buffer)
    {
        var decryptStream = new MemoryStream();

        using (var cryptoStream = new CryptoStream(decryptStream, this.decryptor, CryptoStreamMode.Write))
        {
            cryptoStream.Write(buffer, 0, buffer.Length);
        }

        return decryptStream.ToArray();
    }
}

When I run the following code on the server and the iPad, both print the same encrypted string.

var algorithm = new FlawedAlgorithm();

Console.WriteLine(algorithm.Encrypt("Some string"));

However, when I try to encrypt a second value, the results on the server and iPad are different.

var algorithm = new FlawedAlgorithm();

// The first encryption still functions correctly.
Console.WriteLine(algorithm.Encrypt("Some string"));

// This second encryption produces a different value on the iPad.
Console.WriteLine(algorithm.Encrypt("This text is a bit longer"));

When I decrypt the deviating iPad result on the server, part of the decrypted string is gibberish. The encrypted results from the server decrypt correctly.

The problem does not manifest itself if I create a new FlawedAlgorithm instance for each call, e.g.:

// These statements produce the correct results on the iPad.
Console.WriteLine(new FlawedAlgorithm().Encrypt("Some string"));
Console.WriteLine(new FlawedAlgorithm().Encrypt("This text is a bit longer"));

This leads me to think that the problem lies somewhere in the state of the objects involved. I have inspected the buffer variable in the Encrypt(string) method and the values produced by the UTF8Encoding instance are correct. This implies that the encryptor field (or its underlying implementation) is the culprit.

When I start varying the size of the first encrypted value, I can see changes in the result of the second encryption call. This would probably mean that some part of a stream isn't being cleared or overwritten properly. But the streams the FlawedAlgorithm class uses, aren't part of its state; they are recreated on each method call. And the encryptor object doesn't seem like the type that manages its own streams.

Has anyone else encountered a problem similar to this? Is the RijndaelManaged class flawed? Or are there some stream and memory management pitfalls in MonoTouch at play here, unrelated to this cryptography example?

P.S.: I have tested this on both the iPad and the iPad Simulator; both display this strange behavior.

like image 275
Niels van der Rest Avatar asked Aug 11 '11 14:08

Niels van der Rest


1 Answers

When using .NET cryptography you must always check ICryptoTransform.CanReuseTransform (or assume it will return false). If it returns false then you cannot reuse the same encryptor/decryptor and must create new instances.

Skipping this check means that any changes in the framework (or via configuration files, since cryptography is pluggable) will likely break your application in the future.

You can use something like:

 ICryptoTransform Decryptor {
    get {
       if (decryptor == null || !decryptor.CanReuseTransform)
          decryptor = rijndael.CreateDecryptor (key, vector);
        return decryptor;
    }
 }

to hide this complexity from the caller of your cryptographic routines.

like image 114
poupou Avatar answered Sep 21 '22 07:09

poupou