Thursday, July 17, 2003

System.Security.Crytpography Buglet

I was talking to my friend Rich the other day about a problem he was having with the .NET encryption routines. He was using my How to Encrypt a String sample, so he asked me if I could take a look at it.

The symptoms were strange. He built an app that was almost exactly like the one in my sample. One of the few differences was that he was using the 3DES algorithm instead of the Rijndael algorithm. But he discovered that if he hit the “Encrypt” button a bunch of times, that after a few times, the encrypted output would change to something else. I would have expected that if he were using an asymmetric algorithm like RSA, since random padding is added to the data in that case, but for 3DES it should encrypt to the same thing every time. The really weird part was that it was only exhibiting this behavior on one machine.

The first thing I asked him was what he was using for an Initialization Vector (IV). The IV is important because symmetric algorithms are often implemented using a sort of feedback loop: they encrypt a block, then use the output of that encryption to parameterize the encryption of the next block. If you do this, you need something to feed into the encryption of the first block: that’s the IV. If he was somehow changing the IV, that would change the encryption of the first block and thus the entire output. Rich said he wasn’t mucking with the IV at all: he was initializing it once at process startup and storing it in a static variable. Then he’d hand a reference to that array into each call to create the encryptor or decryptor object.

Well, Rich is no dummy. Following up on my suggestion around the IV as the culprit, and combining it with his own insight that this sort of intermittent error could be caused by something getting finalized after a garbage collection cycle, he put in some code to force a GC. Sure enough, as soon as a GC was forced, the encryption changed. From this, he was able to figure out what was going on.

It turns out that there’s a bug in the 1.0 implementation of the wrapper classes that System.Security.Cryptography uses to give you access to the Crypto API in Windows. When you call CreateEncryptor or CreateDecryptor, the object that comes back implements IDisposable. The object that they’re using zeros out the IV in Dispose. This wouldn’t be so bad, but the problem is that it isn’t making a copy of the IV during creation – it’s making a copy of a reference to the IV. So during finalization of the object, the buggy code will actually zero out an array that someone else owns. Bad.

Clearly, Microsoft is aware of this problem, because it’s fixed in the 1.1 framework. This is why Rich only saw it on one of his computers. And it’s not a problem if you’re using RijndaelManaged – only if you use the DES, 3DES, or RC2 implementations in the libraries. But be aware of it – if you think you might run code on the 1.0 version of the framework, be sure to use Array.Copy or Clone to hand the crypto libraries something they can safely zero out without screwing up your code.

No comments:

Post a Comment