Search code examples
javac#aesbouncycastlepublic-key-encryption

Java BC SicBlockCipher direct output equivalent in c#


I am implementing something in C#, for which I have a separate spec and fairly clear understanding of what I need to do, but at the same time as a reference I have a Java implementation and would like to follow the Java implementation in this case as close as I can.

The code involves an encrypted stream and the Java source is here The relevant lines are here:

  private final StreamCipher enc;
...
  BlockCipher cipher;
  enc = new SICBlockCipher(cipher = new AESEngine());
  enc.init(true, new ParametersWithIV(new KeyParameter(secrets.aes), new byte[cipher.getBlockSize()]));
...
...
byte[] ptype = RLP.encodeInt((int) frame.type); //Result can be a single byte long
...
...
enc.processBytes(ptype, 0, ptype.length, buff, 0);
out.write(buff, 0, ptype.length); //encrypt and write a single byte from the SICBlockCipher stream

The above Java BouncyCastle SicBlockCipher is a StreamCipher and allows processing a single or small number of bytes less than the Aes block size.

In c# BouncyCastle the SicBlockCipher only offers ProcessBlock and BufferedBlockCipher does not seem to offer a way of guaranteeing an output using ProcessBytes.

What do I need to do with C# BouncyCastle library to achieve the equivalent functionality?


Solution

  • Unfortunately the SicBlockCipher itself is not implemented as a stream cipher, so this functionality is (indeed) not available directly.

    BufferedBlockCipher has been created with many different modes of operation in mind. It buffers the input, while for counter (CTR) mode which SicBlockCipher implements, you would need to buffer the encrypted counter blocks instead.

    The encrypted counter blocks make up the key stream, which can then be XOR'ed with the plaintext to create the cipherstream (or indeed, with the ciphertext to retrieve the plaintext again, encryption is decryption for counter mode).

    The only way I see how to do this is to create your own implementation of IBlockCipher and implement said functionality.


    Here is the counter mode as stream cipher...

    using Org.BouncyCastle.Crypto;
    using Org.BouncyCastle.Crypto.Modes;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SicStream
    {
        public class SicStreamCipher : IStreamCipher
        {
            private SicBlockCipher parent;
            private int blockSize;
    
            private byte[] zeroBlock;
    
            private byte[] blockBuffer;
            private int processed;
    
            public SicStreamCipher(SicBlockCipher parent)
            {
                this.parent = parent;
                this.blockSize = parent.GetBlockSize();
    
                this.zeroBlock = new byte[blockSize];
    
                this.blockBuffer = new byte[blockSize];
                // indicates that no bytes are available: lazy generation of counter blocks (they may not be needed)
                this.processed = blockSize;
            }
    
            public string AlgorithmName
            {
                get
                {
                    return parent.AlgorithmName;
                }
            }
    
            public void Init(bool forEncryption, ICipherParameters parameters)
            {
                parent.Init(forEncryption, parameters);
    
                Array.Clear(blockBuffer, 0, blockBuffer.Length);
                processed = blockSize;
            }
    
            public void ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff)
            {
                int inputProcessed = 0;
                while (inputProcessed < length)
                {
                    // NOTE can be optimized further
                    // the number of available bytes can be pre-calculated; too much branching
                    if (processed == blockSize)
                    {
                        // lazilly create a new block of key stream
                        parent.ProcessBlock(zeroBlock, 0, blockBuffer, 0);
                        processed = 0;
                    }
    
                    output[outOff + inputProcessed] = (byte)(input[inOff + inputProcessed] ^ blockBuffer[processed]);
    
                    processed++;
                    inputProcessed++;
                }
            }
    
            public void Reset()
            {
                parent.Reset();
    
                Array.Clear(blockBuffer, 0, blockBuffer.Length);
                this.processed = blockSize;
            }
    
            public byte ReturnByte(byte input)
            {
                if (processed == blockSize)
                {
                    // lazily create a new block of key stream
                    parent.ProcessBlock(zeroBlock, 0, blockBuffer, 0);
                    processed = 0;
                }
                return (byte)(input ^ blockBuffer[processed++]);
            }
        }
    }
    

    ... and here it is wrapped so that it can be retrofitted in code where a block cipher mode of operation is used ...

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Org.BouncyCastle.Crypto;
    using Org.BouncyCastle.Crypto.Modes;
    
    namespace SicStream
    {
        /**
         * A class that implements an online Sic (segmented integer counter mode, or just counter (CTR) mode for short).
         * This class buffers one encrypted counter (representing the key stream) at a time.
         * The encryption of the counter is only performed when required, so that no key stream blocks are generated while they are not required.
         */
        public class StreamingSicBlockCipher : BufferedCipherBase
        {
            private SicStreamCipher parent;
            private int blockSize;
    
            public StreamingSicBlockCipher(SicBlockCipher parent)
            {
                this.parent = new SicStreamCipher(parent);
                this.blockSize = parent.GetBlockSize();
            }
    
            public override string AlgorithmName
            {
                get
                {
                    return parent.AlgorithmName;
                }
            }
    
            public override byte[] DoFinal()
            {
                // returns no bytes at all, as there is no input
                return new byte[0];
            }
    
            public override byte[] DoFinal(byte[] input, int inOff, int length)
            {
                byte[] result = ProcessBytes(input, inOff, length);
    
                Reset();
    
                return result;
            }
    
            public override int GetBlockSize()
            {
                return blockSize;
            }
    
            public override int GetOutputSize(int inputLen)
            {
                return inputLen;
            }
    
            public override int GetUpdateOutputSize(int inputLen)
            {
                return inputLen;
            }
    
            public override void Init(bool forEncryption, ICipherParameters parameters)
            {
                parent.Init(forEncryption, parameters);
            }
    
            public override byte[] ProcessByte(byte input)
            {
                return new byte[] { parent.ReturnByte(input) };
            }
    
            public override byte[] ProcessBytes(byte[] input, int inOff, int length)
            {
                byte[] result = new byte[length];
                parent.ProcessBytes(input, inOff, length, result, 0);
                return result;
            }
    
            public override void Reset()
            {
                parent.Reset();
            }
        }
    }
    

    Note that the last code is less efficient because of the additional arrays that need to be created.