bcgit / bc-csharp

BouncyCastle.NET Cryptography Library (Mirror)
https://www.bouncycastle.org/csharp
MIT License
1.66k stars 555 forks source link

OcbBlockCipher - Better to use predictable, numerical, incrementing nonce. Or a completely random IV each time, and why? #344

Closed firepacket closed 2 years ago

firepacket commented 2 years ago

As you can see from this whitepaper describing OCB mode , it says the following:

Unlike some modes, the plaintext provided to OCB can be of any length, as can the associated data, and OCB will encrypt the plaintext without padding it to some convenient-length string, an approach that would yield a longer ciphertext.

Unfortunately this is not true in this current version of bc-sharp.

If you try to encrypt any text less than the blocksize, the function will return all zeros all the way up to DoFinal(). If you stretch the array to meet the the size of the blocksize length, a portion of that array will contain random looking data, but the rest will be zeros.

Is this class supposed to be used differently? Or is this a bug?

I shouldn't have to pad plaintext in this mode. Something must be wrong?

peterdettman commented 2 years ago

I'm unclear what you're asking. OCB doesn't need padding (and our implementation doesn't add any). However prior to DoFinal partial block input is buffered internally. ProcessByte, ProcessBytes and DoFinal all return a value indicating how much output was written by that call. Finally we add the tag to the output for encryption and expect it in the input for decryption.

If pain persists, please include sample code that you think is misbehaving.

firepacket commented 2 years ago

But I can't encrypt 1 character with OCB, right? It has to match the blocksize? If I try encrypting text under 16 bytes it returns nothing and a length of 0 is returned. So it actually NEEDS padding, it's just not being used?

peterdettman commented 2 years ago

Of course you can encrypt 1 byte with OCB. Are you calling DoFinalto get the final output (and checking the return value)?

If I try encrypting text under 16 bytes it returns nothing and a length of 0 is returned.

If you will just show the code you are trying to use, I'm sure we can quickly clear this up.

firepacket commented 2 years ago
    private void EncBTN_Click(object sender, EventArgs e)
        {
            string k = keyTxt.Text.Substring(0, 32);
            byte[] key = k.ToByteArray();
            KeyParameter kp = new KeyParameter(key);
            ocb = new OcbBlockCipher(new AesLightEngine(), new AesLightEngine());

            Rfc2898DeriveBytes pk = new Rfc2898DeriveBytes(k + ":" + encTxt.Text, data, 10000);
            byte[] ad = pk.GetBytes(32);
            encADtxt.Text = ad.ToBase32k();
            decADtxt.Text = encADtxt.Text;

            ocb.Init(true, new AeadParameters(kp, 128, NewNonce().ToByteArray(), ad));
            byte[] input = encTxt.Text.ToByteArray();
            byte[] o = new byte[64];

            ocb.Init(true, new AeadParameters(kp, 128, NewNonce().ToByteArray(), ad));
            int len = ocb.ProcessBytes(input, 0, input.Length, o, 0);

            if (len >= o.Length)
                Array.Resize<byte>(ref o, o.Length + len);
            ocb.Init(true, new AeadParameters(kp, 128, NewNonce().ToByteArray(), ad));
            len = ocb.DoFinal(o, len);
            encMacTxt.Text = ocb.GetMac().ToByteString();
            decMacTxt.Text = encMacTxt.Text;
            decTxt.Text = o.ToByteString();
          }
    private void DecBtn_Click(object sender, EventArgs e)
        {
            string k = keyTxt.Text.Substring(0, 32);
            byte[] key = k.ToByteArray();
            KeyParameter kp = new KeyParameter(key);
            ocb = new OcbBlockCipher(new AesLightEngine(), new AesLightEngine());

            byte[] ad = decADtxt.Text.FromBase32k();
            ocb.Init(false, new AeadParameters(kp, 128, NewNonce().ToByteArray(), ad));

            byte[] inp = decTxt.Text.ToByteArray();

            int len = ocb.GetUpdateOutputSize(inp.Length);
            byte[] o = new byte[128]; // bigger just in case

            len = ocb.ProcessBytes(inp, 0, inp.Length, o, len);

            string s = o.ToByteString();
            ocb.Init(false, new AeadParameters(kp, 128, NewNonce().ToByteArray(), ad));
            if (len >= o.Length)
                Array.Resize<byte>(ref o, o.Length + len);
            len = ocb.DoFinal(o, 0); // #ERROR: 'mac check in OCB failed' 
                                               // I also sometimes get ERROR:  'data too short' (o = 128.Length, len = 112)??
            s = o.ToByteString();
            ocb.Init(false, new AeadParameters(kp, 128, NewNonce().ToByteArray(), ad));

            string mac = ocb.GetMac().ToByteString();
            if (mac.SequenceEqual(decMacTxt.Text.AsEnumerable()))
                decMacTxt.BackColor = Color.FromArgb(3, 60, 0);

            decMacTxt.Text = mac;

            encTxt.Text = o.FromByteArray();
            //encTxt.Text = len.ToString();
        }

If I pad the plaintext it seems to work but, the mac matches the last part of the output, there appears to be cipher text for 1 block if I pad, but I cant get decrypt to work using the same values. There appears to be no ciphertext (unless plaintex=blocksize). Its all 0s as you can see in pic where I'm not padding.

If I pad the plain text, I get a full 128 byte string with the mac at the end but it doesn't decrypt.

Also, am I reseeding the nonce at the right times? I was never sure.

Even when I'm padding, I'm not getting it to decrypt and the MAC never matches. Something else must be wrong? I feel like it's encrypting because when I add encrypted text I see it in front of the mac, There's just a lot of zeros at the end. Should I cut them off? Even when I give DoFinal() 256 byte output it says "Data too short?" Is it really meaning to say "Array too big?"

ocb

firepacket commented 2 years ago

Of course you can encrypt 1 byte with OCB. Are you calling DoFinalto get the final outputvalue)?

Yes I am. If I do that I get Just a MAC at the beginning the rest zeros. (Assuming ocb.ProcessByte is under the blocksize)

(and checking the return value? If you will just show the code you are trying to use, I'm sure we can quickly clear this up.

What am I supposed to do with the return value of DoFinal? It's usually 16, the size of the mac which is what was written. So it's just telling me it wrote the mac.... but the ciphertext might be at the beginning, but I am confused as to what to do with all the zeros. I can't seem to decrypt the output, do I trim the zeros? If everything matches the blocksize there are no zeros but it still wont decrypt I am passing in the MAC and all the zeros in ocb.ProcessBlock().

Here's an example. Notice when the plaintext is under the blocksize (16) I get no cipher text, just a mac, but when it is exactly the blocksize I get ciphertext:

padding (I trimmed the zeros here)

firepacket commented 2 years ago

I can get 16 bytes from processbyte if length=blocksize but I always get:

"Org.BouncyCastle.Crypto.InvalidCipherTextException: 'mac check in OCB failed'"

Here: int len = ocb.ProcessBytes(inp, 0, 32, o, 0); // I get 16 byes in byte[] o Great! (If I don't pass the WHOLE input with the mac o=all zeros) now... len = ocb.DoFinal(inp, 0); // ERROR: 'mac check in OCB failed' len = ocb.DoFinal(inp, 32); // ERROR: 'mac check in OCB failed' len = ocb.DoFinal(inp, 31); // ERROR: 'mac check in OCB failed' len = ocb.DoFinal(inp, 33); // ERROR: 'mac check in OCB failed' len = ocb.DoFinal(inp, 16); // ERROR: 'mac check in OCB failed' len = ocb.DoFinal(inp, 15); // ERROR: 'mac check in OCB failed' len = ocb.DoFinal(inp, 17); // ERROR: 'mac check in OCB failed' len = ocb.DoFinal(decMacTxt.Text.ToByteArray(), 0); // ERROR: 'mac check in OCB failed'

If I add an Init() before DoFinal() I get another error:

byte[] o = new byte[64]; int len = ocb.ProcessBytes(inp, 0, 32, o, 0); ocb.Init(false, new AeadParameters(kp, 128, NewNonce().ToByteArray())); len = ocb.DoFinal(o, 16);

I get Error: 'data too short' And keep in mind I only get this far if the plaintext is exactly 16 bytes. Otherwise I get nothing but a MAC.

firepacket commented 2 years ago

I posted all my work because you made it seem as if you were willing to help me.

firepacket commented 2 years ago

Hello it's been days. Have I done something wrong? Are you no longer allowed to help me? Have I triggered something? You seemed to eager to help me so I posted everything and now I'm being stonewalled.

peterdettman commented 2 years ago

The main problem seems to be that your are calling Init repeatedly. There only needs to be one call to Init for the entire encryption (or decryption). Here is sample code for encrypting a single byte plaintext:

KeyParameter keyParameter = new KeyParameter(new byte[16]);
AeadParameters aeadParameters = new AeadParameters(keyParameter, 128, new byte[15], null);

OcbBlockCipher c = new OcbBlockCipher(new AesEngine(), new AesEngine());
c.Init(true, aeadParameters);

int outputSize = c.GetOutputSize(1);
byte[] output = new byte[outputSize];
int outputPos = 0;

outputPos += c.ProcessByte(0, output, outputPos);
outputPos += c.DoFinal(output, outputPos);

Console.WriteLine(outputPos);
Console.WriteLine(Hex.ToHexString(output));

There has to be a single call to Init at the beginning, and a single call to DoFinal at the end. There can be 0 or more calls to ProcessByte(s) in between, each of which supplies some input. Each call to ProcessByte(s) and DoFinal returns a value indicating how much output that call produced. After the DoFinal call, all output will have been written.

GetOutputSize is used to get the maximum possible size of output for the given total amount of input. For OCB, it will actually be an exact prediction, but it is good practice to use the calculated output length (outputPos in the above example) to tell how much total output you got.

I suggest that you separate out your encryption/decryption code and get it working apart from all the other stuff first. Then include the AAD and check that things work. Then add the key derivation and check again, and so on.

firepacket commented 2 years ago

Thank you for your reply. In my research of OCB mode I read that the nonce was only supposed to be 12 bytes, I noticed you have it set to 15, that clears up some confusion for me.

I also read that OCB can accept 32 byte keys, but I tried the following code with 16 byte keys and a 15 byte nonce but still got the error while trying to encrypt 3 characters (xxx) "Output buffer too short" on DoFinal()

The ToByteArray() function I am using is straight out of Org.BouncyCastle.Utilities.Strings I just made it into an extension method.

string k = keyTxt.Text.Substring(0, 16);
byte[] key = Hex.DecodeStrict(k);
KeyParameter kp = new KeyParameter(key);

ocb = new OcbBlockCipher(new AesEngine(), new AesEngine());
ocb.Init(true, new AeadParameters(kp, 128, NewNonce(15).ToByteArray()));

byte[] input = encTxt.Text.ToByteArray(); // xxx
int len = ocb.GetOutputSize(input.Length);
byte[] o = new byte[len];

len += ocb.ProcessBytes(input, 0, input.Length, o, 0);
len += ocb.DoFinal(o, len); // Org.BouncyCastle.Crypto.OutputLengthException: 'Output buffer too short'

byte [] o = o[19] (all zeros) len = 19

I don't know what I am doing wrong here, but I need to encrypt more than one character and I need to use a real key. I tried Hex.DecodeStrict(k) to no avail. Switched to AesEngine.

I don't understand what could be going wrong here. Should I just be encrypting one byte at a time in a loop?

Do you happen to know the difference between GetOutputSize() and GetUpdateOutputSize()?

Here's some screenshots of the objects if it helps any: ocb

Thanks I would appreciate any continued help.

peterdettman commented 2 years ago

In my research of OCB mode I read that the nonce was only supposed to be 12 bytes, I noticed you have it set to 15, that clears up some confusion for me.

15 is the maximum length of the nonce. The actual length to use should be specified in the particular context you are using it.

I also read that OCB can accept 32 byte keys

Yes, it can be used with any key permitted by the underlying cipher (in your case AES), so 256 bit key is fine too.

I don't know what I am doing wrong here

This code is wrong because you didn't reset len to 0 after using it to get the output size. Just change the ProcessBytes line to:

len = ocb.ProcessBytes(input, 0, input.Length, o, 0);

Do you happen to know the difference between GetOutputSize() and Get_Update_OutputSize()?

GetOutputSize returns the total length of output for a total input length (possibly across multiple ProcessByte(s) calls). This is usually the only one needed. GetUpdateOutputSize tells you how much output (maximum) would be generated by the next call to ProcessBytes with the given length. It can sometimes be useful in streaming large contents, but I doubt you need it here.

firepacket commented 2 years ago

YESS!!!!!! THANKYOU!!

len = ocb.ProcessBytes(input, 0, input.Length, o, 0);

That was it!! Thank you, AGAIN! You were using += because you were just doing one byte at a time and I didn't change it. Encryption appears to be working perfectly now!!

It looks like if the KeyParameter is only 16 bytes then the Init line causes an exception: (Key length not 128/192/256 bits):

ocb.Init(true, new AeadParameters(kp, 128, NewNonce(15).ToByteArray()));

So I changed back to a 32 byte key and everything worked! I am so grateful, but unfortunately now I am now having trouble decrypting :-(

I am getting the error "'mac check in OCB failed", even though it finds the correct output length and attempts a decryption (that is incorrect).

EDIT: .... blah blah blah... you were using a different NONCE OF COURSE .... blah .... blah

firepacket commented 2 years ago

I resolved the problem. Answer:

The NONCE must be the SAME for ENCRYPTING and DECRYPTING - it is the SAME AS AN IV

I read really some bad information... (There's a lot of junk on OCB out there). Thanks for your help. Now if anyone would like to answer before I close this, is it better to use a RANDOM IV or a INCREMENTING NUMERICAL NONCE?

And WHY?

Thanks again for all your help.

firepacket commented 2 years ago

I have answered my own question:

A nonce is good for when two parties have a communication channel and can predictably derive the same nonce reliably and independently, by themselves, each time a message is sent or received. This avoids having to send it with the cipher text. OCB was designed to allow this without weakening its function.

Thanks everyone, sorry for taking so much space.