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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With