4

I need to communicate from a C# application to another application via encrypted messages in OFB mode. I know that RijndaelManaged does not have support for AES OFB mode. Is there anybody more experienced than me aware of any other way to encrypt/decrypt using OFB mode?

Maarten Bodewes
  • 80,169
  • 13
  • 121
  • 225
  • Could you make explicit your platform requirements? It seems that there is [OFB mode implemented in 4.5](http://msdn.microsoft.com/en-us/library/system.security.cryptography.ciphermode.aspx) – Maarten Bodewes Jul 01 '14 at 17:49
  • Note that you don't need to mention the language in the title, but you *should* indicate it using tags. Otherwise the persons following these tags won't be alerted to them. – Maarten Bodewes Jul 01 '14 at 17:53
  • CipherMode.OFB is valid for all frameworks, I changed the "Other Versions" to ".NET Framework 1.1" and OFB is listed. But when using it I get error similar to "Specified cipher mode is not valid for this algorithm", I'm using VS 2013 with Target Framework set to ".NET Framework 4.5" – user3794709 Jul 01 '14 at 19:23
  • also for "AesCryptoServiceProvider" I get: "System.Security.Cryptography.CryptographicException" - "An internal error occurred." – user3794709 Jul 01 '14 at 19:30
  • Also see http://stackoverflow.com/questions/4252411/does-net-support-of-aes-ofb – Maarten Bodewes Jul 01 '14 at 20:35
  • Found that, this is why I started this question, to see if anybody has any other ways to recommend to do it – user3794709 Jul 01 '14 at 20:47
  • In dire need, it is always possible to implement it yourself using the cipher directly as block cipher or in ECB mode. – Maarten Bodewes Jul 01 '14 at 21:28
  • I know that I can do ECB and then XOR but how do I decrypt ? – user3794709 Jul 01 '14 at 21:44

1 Answers1

3

The following stream implements OFB by using a key stream generated by a zero-fed CBC cipher stream.

public class OFBStream : Stream
{
    private const int BLOCKS = 16;
    private const int EOS = 0; // the goddess of dawn is found at the end of the stream

    private Stream parent;
    private CryptoStream cbcStream;
    private CryptoStreamMode mode;
    private byte[] keyStreamBuffer;
    private int keyStreamBufferOffset;
    private byte[] readWriteBuffer;

    public OFBStream (Stream parent, SymmetricAlgorithm algo, CryptoStreamMode mode)
    {
        if (algo.Mode != CipherMode.CBC)
            algo.Mode = CipherMode.CBC;
        if (algo.Padding != PaddingMode.None)
            algo.Padding = PaddingMode.None;
        this.parent = parent;
        this.cbcStream = new CryptoStream (new ZeroStream (), algo.CreateEncryptor (), CryptoStreamMode.Read);
        this.mode = mode;
        keyStreamBuffer = new byte[algo.BlockSize * BLOCKS];
        readWriteBuffer = new byte[keyStreamBuffer.Length];
    }

    public override int Read (byte[] buffer, int offset, int count)
    {
        if (!CanRead) {
            throw new NotSupportedException ();
        }

        int toRead = Math.Min (count, readWriteBuffer.Length);
        int read = parent.Read (readWriteBuffer, 0, toRead);
        if (read == EOS)
            return EOS;

        for (int i = 0; i < read; i++) {
            // NOTE could be optimized (branches for each byte)
            if (keyStreamBufferOffset % keyStreamBuffer.Length == 0) {
                FillKeyStreamBuffer ();
                keyStreamBufferOffset = 0;
            }

            buffer [offset + i] = (byte)(readWriteBuffer [i]
                ^ keyStreamBuffer [keyStreamBufferOffset++]);
        }

        return read;
    }

    public override void Write (byte[] buffer, int offset, int count)
    {
        if (!CanWrite) {
            throw new NotSupportedException ();
        }

        int readWriteBufferOffset = 0;
        for (int i = 0; i < count; i++) {
            if (keyStreamBufferOffset % keyStreamBuffer.Length == 0) {
                FillKeyStreamBuffer ();
                keyStreamBufferOffset = 0;
            }

            if (readWriteBufferOffset % readWriteBuffer.Length == 0) {
                parent.Write (readWriteBuffer, 0, readWriteBufferOffset);
                readWriteBufferOffset = 0;
            }

            readWriteBuffer [readWriteBufferOffset++] = (byte)(buffer [offset + i]
                ^ keyStreamBuffer [keyStreamBufferOffset++]);
        }

        parent.Write (readWriteBuffer, 0, readWriteBufferOffset);
    }

    private void FillKeyStreamBuffer ()
    {
        int read = cbcStream.Read (keyStreamBuffer, 0, keyStreamBuffer.Length);
        // NOTE undocumented feature
        // only works if keyStreamBuffer.Length % blockSize == 0
        if (read != keyStreamBuffer.Length)
            throw new InvalidOperationException ("Implementation error: could not read all bytes from CBC stream");
    }

    public override bool CanRead {
        get { return mode == CryptoStreamMode.Read; }
    }

    public override bool CanWrite {
        get { return mode == CryptoStreamMode.Write; }
    }

    public override void Flush ()
    {
        // should never have to be flushed, implementation empty
    }

    public override bool CanSeek {
        get { return false; }
    }

    public override long Seek (long offset, System.IO.SeekOrigin origin)
    {
        throw new NotSupportedException ();
    }

    public override long Position {
        get { throw new NotSupportedException (); }
        set { throw new NotSupportedException (); }
    }

    public override long Length {
        get { throw new NotSupportedException (); }
    }

    public override void SetLength (long value)
    {
        throw new NotSupportedException ();
    }

}

Additional class ZeroStream required by OFBStream

class ZeroStream : System.IO.Stream
{
    public override int Read (byte[] buffer, int offset, int count)
    {
        for (int i = 0; i < count; i++) {
            buffer [offset + i] = 0;
        }

        return count;
    }

    public override bool CanRead {
        get { return true; }
    }

    ... the rest is not implemented
}

And you can use it as I do for a test vector:

// NIST CAVP test vector F.4.1: OFB-AES128.Encrypt from NIST SP 800-38A

RijndaelManaged aes = new RijndaelManaged ();
aes.Key = FromHex ("2b7e151628aed2a6abf7158809cf4f3c");
aes.IV = FromHex ("000102030405060708090A0B0C0D0E0F");
MemoryStream testVectorStream = new MemoryStream (FromHex (
    "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"));
OFBStream testOFBStream = new OFBStream (testVectorStream, aes, CryptoStreamMode.Read);
MemoryStream cipherTextStream = new MemoryStream ();
testOFBStream.CopyTo (cipherTextStream);
Console.WriteLine (ToHex (cipherTextStream.ToArray ()));

Note that the stream handling has not been fully tested (yet).

Maarten Bodewes
  • 80,169
  • 13
  • 121
  • 225
  • Any comments on my C# skills are most welcome. This is not my primary language. Note that not all arguments are fully tested before they are used. – Maarten Bodewes Jul 02 '14 at 01:03
  • Thank you for the work, do you mind providing some code on how to use this new class? How to call it, pass the Key, IV and plaintext, all these are of byte[] in my case. – user3794709 Jul 02 '14 at 03:33
  • How are you getting along with this? If you need a ToHex/FromHex implementation, it was stolen from [here](http://stackoverflow.com/a/311179/589259) and renamed. – Maarten Bodewes Jul 02 '14 at 12:45
  • For a few day's I've been so close to the solution with my code (I tried to implement my own way), now I noticed that that byte[] of 0s was too short, i sent it 3 bytes, since this is how long the text that I plan to encrypt is. I sent 16 bytes of 0s and everything works fine now. Thank you very much for your help. – user3794709 Jul 02 '14 at 16:17
  • Yeah, that's the trouble with crypto. If it doesn't work, it often just produces garbage. Note that the NIST test vectors also contain the key stream (intermediate) information. Never hurts to look up the official documentation / test vectors. Glad to have been of help, was fun implementing this. – Maarten Bodewes Jul 02 '14 at 16:22