First some background, in case I'm taking the wrong approach. I have two requirements:
I want to encrypt the data written and read from AnonymousPipeServerStream and AnonymousPipeClientStream
I must use a FIPS-compliant NIST-accredited cryptographic module.
I'm actually using StreamJsonRpc to read and write the pipes, so I have no control over how many bytes are read and written at once. What I'm looking for is an encrypting/decrypting Stream that I can use to wrap unencrypted streams.
I'm trying to use the FIPS-compliant Bouncy Castle .Net library to do this using AES in CTR mode, which I understand is a reasonable way to encrypt a stream.
I can't work out how to increment the counter when encrypting a long data stream, so I get an exception "counter in CTR mode out of range".
Below is a compilable console app which demonstrates what I have so far. It's strongly based on the sample code in section 3.2.2 of the Bouncy Castle BC-FNA user guide.
The code below works, but if you increase the amount of data written to the stream by changing const int COPIES = 50; to const int COPIES = 60;, it will throw an exception.
My questions:
- Is this a reasonable approach?
- If so, how can I handle large streams?
The code:
using System;
using System.IO;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Fips;
using Org.BouncyCastle.Utilities.Encoders;
namespace Demo
{
class Program
{
static void Main()
{
const int COPIES = 50;
CryptoServicesRegistrar.SetApprovedOnlyMode(true);
var key = new FipsAes.Key(Hex.Decode("aafd12f659cae63489b479e5076ddec2"));
IBlockCipherService provider = CryptoServicesRegistrar.CreateService(key);
// define IV – note 15 bytes, so max message length 255 * 16 bytes
byte[] iv = Hex.Decode("000102030405060708090a0b0c0d0e");
// define data to be encrypted.
string text = "'Twas brillig, and the slithy toves did gyre and gymble in the wabe.\n";
byte[] toBeEncrypted = Encoding.UTF8.GetBytes(text);
// encrypt the data.
var bOut = new MemoryStream();
var encryptorBldr = provider.CreateEncryptorBuilder(FipsAes.Ctr.WithIV(iv));
var encryptor = encryptorBldr.BuildCipher(bOut);
using (Stream encryptingStream = encryptor.Stream)
{
// Write several copies a byte at a time to prove it works.
writeMultipleCopiesOneByteAtOnce(toBeEncrypted, COPIES, encryptingStream); // Change copies to 60 and it breaks.
}
byte[] cipherText = bOut.ToArray();
// decrypt the resulting cipher text
var decryptorBldr = provider.CreateDecryptorBuilder(FipsAes.Ctr.WithIV(iv));
var decryptor = decryptorBldr.BuildCipher(new MemoryStream(cipherText));
using var decIn = decryptor.Stream;
var bytes = readAllOneByteAtOnce(decIn); // Prove that we can read the decryptor stream a byte at a time.
string result = Encoding.UTF8.GetString(bytes); // Print decrypted text.
Console.WriteLine(result);
}
// Just for test purposes. I wouldn't normally write bytes like this.
static void writeMultipleCopiesOneByteAtOnce(byte[] data, int copies, Stream output)
{
for (int i = 0; i < copies; ++i)
{
foreach (byte b in data)
output.WriteByte(b);
}
}
// Just for test purposes. I wouldn't normally read bytes like this.
static byte[] readAllOneByteAtOnce(Stream stream)
{
using var memStream = new MemoryStream();
while (true)
{
int b = stream.ReadByte();
if (b < 0)
return memStream.ToArray();
memStream.WriteByte((byte)b);
}
}
}
}