Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HIGHT cipher troubles with FileSource (CTR mode only) #1010

Closed
clementmartin971 opened this issue Feb 26, 2021 · 12 comments
Closed

HIGHT cipher troubles with FileSource (CTR mode only) #1010

clementmartin971 opened this issue Feb 26, 2021 · 12 comments
Labels

Comments

@clementmartin971
Copy link

clementmartin971 commented Feb 26, 2021

Using the code provided as HIGHT example in Wiki and replacing CBC_Mode by CTR_Mode is working... but next switching from StringSource to FileSource doesn't work anymore.

  • the first ciphered block has 0 content (with 0 Key and Iv) instead of correct values
  • ciphered content with StringSource : 7f 9f 0c 1c c1 9f c4 33 82 3f ee 8f dc
  • ciphered content with FileSource : 00 00 00 00 00 00 00 00 82 3F EE 8F DC

The source code to reproduce:

int main(int argc, char* argv[])
{
   AutoSeededRandomPool prng;
   SecByteBlock key(HIGHT::DEFAULT_KEYLENGTH);
   SecByteBlock iv(HIGHT::BLOCKSIZE);

   prng.GenerateBlock(key, key.size());
   prng.GenerateBlock(iv, iv.size());
   // set key to 0 to better see error
   memset(key.BytePtr(),0,key.size());
   memset(iv.BytePtr(),0,iv.size());
   std::string plain = "CTR Mode Test";
   std::string cipher, encoded, recovered;

   /*********************************\
   \*********************************/

   try
   {
      std::cout << "plain text: " << plain << std::endl;

      CTR_Mode< HIGHT >::Encryption e;
      e.SetKeyWithIV(key, key.size(), iv);

      #if 0 
      // string source is working well
      StringSource s(plain, true, 
         new StreamTransformationFilter(e,
            new StringSink(cipher)
         ) // StreamTransformationFilter
      ); // StringSource
      #endif

      // file source doesn't work :(
      FileSource f("ansi_text_file_with_content_CTR Mode Test_.txt", true, 
         new CryptoPP::StreamTransformationFilter(e,
            new CryptoPP::StringSink(cipher)
         ) // StreamTransformationFilter
      ); // StringSource
      // at this point cipher differ from StringSource and FileSource
   }
   catch(const CryptoPP::Exception& e)
   {
      std::cerr << e.what() << std::endl;
      exit(1);
   }

   /*********************************\
   \*********************************/

   Print("key", std::string((const char*)key.begin(), key.size()));
   Print("iv", std::string((const char*)iv.begin(), iv.size()));
   Print("cipher text", cipher);

   /*********************************\
   \*********************************/

   try
   {
      CTR_Mode< HIGHT >::Decryption d;
      d.SetKeyWithIV(key, key.size(), iv);

      // The StreamTransformationFilter removes
      //  padding as required.
      StringSource s(cipher, true, 
         new StreamTransformationFilter(d,
            new StringSink(recovered)
         ) // StreamTransformationFilter
      ); // StringSource

      std::cout << "recovered text: " << recovered << std::endl;
   }
   catch(const CryptoPP::Exception& e)
   {
      std::cerr << e.what() << std::endl;
      exit(1);
   }
   return 0;
}
@noloader noloader added the Bug label Feb 27, 2021
@noloader
Copy link
Collaborator

noloader commented Feb 27, 2021

Thanks @clementmartin971.

Yeah, something is going sideways here. Whatever is wrong is not a new break. I'm seeing the same result back to Crypto++ 5.6.2.

We also lack a good set of test vectors for HIGHT in https://github.com/weidai11/cryptopp/blob/master/TestVectors/hight.txt. I don't think the missing test vectors affected this problem, however. Test vectors are run through a StringSource.

@noloader
Copy link
Collaborator

noloader commented Feb 27, 2021

So I've got this tracked down to a function called AdditiveCipherTemplate::ProcessData around line 94. It calls a function called OperateKeystream(operation, outString, inString, iterations).

I think the problem is inString == outString. They are the same pointers, and it violates GCC aliasing rules. Sometimes GCC gives us a demerit and breaks us. Other times it does not.

This is a tricky problem because the compiler is removing code without a diagnostic. We can't debug what is not there...

I think we need to get on a code path that calls OperateKeystream(operation, inoutString, iterations). The problem is, the underlying cipher may not support in-place encryption.

This class has caused us a lot of trouble in the past. Scroll down to around 200 and read the comments for CFB mode.

Let me see what I can do to get this fixed.

@clementmartin971
Copy link
Author

clementmartin971 commented Feb 27, 2021

I can understand that it's not easy to fix.

I have tried a little to see what was wrong and didn't success (else I would have post the patch :) )

That surprised me was that it works for StringSource but not for FileSource; like FileStore::TransferTo2 / FileStore::CopyRangeTo2 where doing more processing.

Thank you for working on it

@clementmartin971
Copy link
Author

clementmartin971 commented Mar 9, 2021

You were right in analysis troubles occurred when inString == outString.

For string source inString != outString because StreamTransformationFilter::NextPutMultiple is called (and calls m_cipher.ProcessString(space, inString, len)) while for file source StreamTransformationFilter::NextPutModifiable is called providing m_cipher.ProcessString(inString, len).

A patch can be done in strciphr.cpp AdditiveCipherTemplate<S>::ProcessData by allocating a temporary output buffer if inString == outString.

I have done the following but I'm not sure to use correct allocation functions and if wiping is necessary (I didn't know enough cryptopp internals functions and way of protecting data)

template <S>
void AdditiveCipherTemplate<S>::ProcessData(byte *outString, const byte *inString, size_t length)
{
	byte *outStringBack = outString;
	byte *outStringAlloc = NULLPTR;
	size_t FullLength = length;
	if (outString == inString)
	{
		outStringAlloc = static_cast<byte*>(AlignedAllocate(FullLength));
		if (outStringAlloc == NULLPTR)
			throw std::bad_alloc();
        
		// fill outStringAlloc with inString, in case of error or early break (for copy back at the end of the function)
		memcpy(outStringAlloc,inString,FullLength);

		// make outString point to new allocated buffer
		outString = outStringAlloc;
	}

	if (m_leftOver > 0)
	{
		const size_t len = STDMIN(m_leftOver, length);
		xorbuf(outString, inString, PtrSub(KeystreamBufferEnd(), m_leftOver), len);

		length -= len; m_leftOver -= len;
		inString = PtrAdd(inString, len);
		outString = PtrAdd(outString, len);

		// if (!length) {return;}
		if (!length) {goto CleanUp;}
	}

	PolicyInterface &policy = this->AccessPolicy();
	unsigned int bytesPerIteration = policy.GetBytesPerIteration();

	if (policy.CanOperateKeystream() && length >= bytesPerIteration)
	{
		const size_t iterations = length / bytesPerIteration;
		unsigned int alignment = policy.GetAlignment();
		volatile int inAligned = IsAlignedOn(inString, alignment) << 1;
		volatile int outAligned = IsAlignedOn(outString, alignment) << 0;

		KeystreamOperation operation = KeystreamOperation(inAligned | outAligned);
		policy.OperateKeystream(operation, outString, inString, iterations);

		inString = PtrAdd(inString, iterations * bytesPerIteration);
		outString = PtrAdd(outString, iterations * bytesPerIteration);
		length -= iterations * bytesPerIteration;

		// if (!length) {return;}
		if (!length) {goto CleanUp;}
	}

	size_t bufferByteSize = m_buffer.size();
	size_t bufferIterations = bufferByteSize / bytesPerIteration;

	while (length >= bufferByteSize)
	{
		policy.WriteKeystream(m_buffer, bufferIterations);
		xorbuf(outString, inString, KeystreamBufferBegin(), bufferByteSize);

		length -= bufferByteSize;
		inString = PtrAdd(inString, bufferByteSize);
		outString = PtrAdd(outString, bufferByteSize);
	}

	if (length > 0)
	{
		bufferByteSize = RoundUpToMultipleOf(length, bytesPerIteration);
		bufferIterations = bufferByteSize / bytesPerIteration;

		policy.WriteKeystream(PtrSub(KeystreamBufferEnd(), bufferByteSize), bufferIterations);
		xorbuf(outString, inString, PtrSub(KeystreamBufferEnd(), bufferByteSize), length);
		m_leftOver = bufferByteSize - length;
	}
    
CleanUp:
	if (outStringAlloc)
	{
		memcpy(outStringBack,outStringAlloc,FullLength);
		SecureWipeBuffer(outStringAlloc,FullLength); // Is this required ???
		AlignedDeallocate(outStringAlloc);
	}
}

noloader added a commit that referenced this issue Mar 17, 2021
…1010, PR #1019)

We found we can avoid the memcpy in the previous workaround by using a volatile pointer. The pointer appears to tame the optimizer so the compiler does not short-circuit some calls when outString == inString.
@noloader
Copy link
Collaborator

noloader commented Mar 17, 2021

@clementmartin971,

No joy yet. PR #1019 cleaned up strciphr.cpp. Commit 4eac79fad8e4 cleaned up some shady casts in xorbuf.

I also tried your patch with no joy. It failed to decrypt HIGHT ciphertext properly. It also broke OFB mode. (It did produce ciphertext, but I am not sure if it was correct).

I'm scratching my head...

noloader added a commit that referenced this issue Mar 17, 2021
We think this is another instance problem that surfaced under GH #683 when inString==outString. It violates aliasing rules and the compiler begins removing code.

The ultimate workaround was to add a member variable m_tempOutString as scratch space when inString==outString. We did not loose much in the way of perforamce for some reason. It looks like AES/CTR lost about 0.03-0.05 cpb.

When combined with the updated xorbuf from GH #1020, the net result was a speedup of 0.1-0.6 cpb. In fact, some ciphers like RC6, gained almost 5 cpb.
noloader added a commit that referenced this issue Mar 17, 2021
We think this is another instance problem that surfaced under GH #683 when inString==outString. It violates aliasing rules and the compiler begins removing code.

The ultimate workaround was to add a member variable m_tempOutString as scratch space when inString==outString. We did not loose much in the way of perforamce for some reason. It looks like AES/CTR lost about 0.03-0.05 cpb.

When combined with the updated xorbuf from GH #1020, the net result was a speedup of 0.1-0.6 cpb. In fact, some ciphers like RC6, gained almost 5 cpb.
@noloader
Copy link
Collaborator

noloader commented Mar 17, 2021

Thanks again @clementmartin971,

This issue was cleared at Commit 71a812ed9e7c and Commit bbc45ddfd7fc. (The first commit failed to check-in the updated header file).

Re: this workaround + the xorbuf cleanup... It looks like AES/CTR lost about 0.03-0.05 cpb on x86_64 with both changes. That is acceptable. In general, most ciphers gained 0.1-0.6 cpb. And RC6 gained 4.4 cpb.

I also tried your patch with no joy...

This was incorrect. My implementation was bad. I revisited it this morning and fixed my mistake.

Thanks again for your help.

@clementmartin971
Copy link
Author

Thanks a lot for the resolution. I was about to test again my "workaround" according to your comment of yesterday.
Happy to see you finally solve it, and sorry for not supporting you so much, but internal code is quite complex for me.
I need to spend more time to see all the interactions between classes and internal structures and to be able to provide you real help
Many thanks for your support

@noloader
Copy link
Collaborator

noloader commented Mar 19, 2021

@clementmartin971,

We now have some self tests that use FileSource in addition to StringSource. The new tests were mostly copy/paste of existing StringSource tests. We should be able to detect a problem like this in the future.

Also see Commit 07951c11e5d1 and Commit 0c88471e7aad.

EAddario pushed a commit to EAddario/cryptopp that referenced this issue Apr 10, 2021
…, GH weidai11#1010, PR weidai11#1019)

We found we can avoid the memcpy in the previous workaround by using a volatile pointer. The pointer appears to tame the optimizer so the compiler does not short-circuit some calls when outString == inString.
EAddario pushed a commit to EAddario/cryptopp that referenced this issue Apr 10, 2021
We think this is another instance problem that surfaced under GH weidai11#683 when inString==outString. It violates aliasing rules and the compiler begins removing code.

The ultimate workaround was to add a member variable m_tempOutString as scratch space when inString==outString. We did not loose much in the way of perforamce for some reason. It looks like AES/CTR lost about 0.03-0.05 cpb.

When combined with the updated xorbuf from GH weidai11#1020, the net result was a speedup of 0.1-0.6 cpb. In fact, some ciphers like RC6, gained almost 5 cpb.
EAddario pushed a commit to EAddario/cryptopp that referenced this issue Apr 10, 2021
We think this is another instance problem that surfaced under GH weidai11#683 when inString==outString. It violates aliasing rules and the compiler begins removing code.

The ultimate workaround was to add a member variable m_tempOutString as scratch space when inString==outString. We did not loose much in the way of perforamce for some reason. It looks like AES/CTR lost about 0.03-0.05 cpb.

When combined with the updated xorbuf from GH weidai11#1020, the net result was a speedup of 0.1-0.6 cpb. In fact, some ciphers like RC6, gained almost 5 cpb.
EAddario pushed a commit to EAddario/cryptopp that referenced this issue Apr 10, 2021
EAddario pushed a commit to EAddario/cryptopp that referenced this issue Apr 10, 2021
EAddario pushed a commit to EAddario/cryptopp that referenced this issue Apr 10, 2021
EAddario pushed a commit to EAddario/cryptopp that referenced this issue Apr 10, 2021
@noloader
Copy link
Collaborator

noloader commented Feb 8, 2022

@PassMark, @dgm3333, @clementmartin971

I checked in some code to avoid the extra buffer and memcpy's. It is sitting on the strciphr branch.

The code tested Ok for me on x86_64. That's where HIGHT bock cipher had problems. ARMv7 had problems on Linux, and I it also tested Ok. Can you guys test it on your platforms, please?

You can fetch the code with:

$ git fetch origin
$ git checkout strciphr -f && git pull

We announced the testing branch on the mailing list at strciphr.cpp updates. If I don't get any complaints over the next week or so, I will merge the changes.

Thanks in advance.

@clementmartin971
Copy link
Author

Hello,
Sorry for being late... but I'm also on x86_64

noloader added a commit that referenced this issue Feb 14, 2022
This commit attempts to restore performance while taming the optimizer.

Also see GH #683, GH #1010, GH #1088, GH #1103.
@dgm3333
Copy link

dgm3333 commented Feb 14, 2022

Thanks for this. I've been a bit swamped with another coding push but I should have a chance to rewrite the encryption routines to test impact of multithreading on timing later in the week. However for what it's worth an initial "single thread" (ie any multithreaded processing would be automatic compiler/operating system initiated) encryption/decryption loop of ~150GB / 15k files of data completed without triggering any warnings. (Test PC is running Win 10 pro 64 running on an AMD Ryzen 9 = 12 cores).

@noloader
Copy link
Collaborator

@dgm3333,

I merged the changes into master last night. You should test master now.

Jeff

noloader referenced this issue Sep 30, 2023
It turns out we went down a rabbit hole when we added the volatile cast gyrations in an attempt to change the compiler behavior. We are seeing the same failures from AES, Rabbit, HIGHT, HC-128 and HC-256 with and without the gyrations.
We were able to work out the problems with Rabbit, HIGHT, HC-128 and HC-256. See GH #1231 and GH #1234.
We are also not able to successfully cut-in Cryptogams AES on ARMv7, so it is now disabled. See GH #1236.
Since the volatile casts were not a solution, we are backing it out along with associated comments.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants
@dgm3333 @noloader @clementmartin971 and others