TripleDES in CFB mode using Crypto++ and .NET

2019-07-17 21:52发布

问题:

I am trying to get same result using TripleDES using C++ app which has Crypto++ and .NET app which uses TripleDESCryptoServiceProvider. I tried setting Key and IV the same but I am getting different results.

This question was already asked here, but there is no clear answer.

Here is C++ example

#include <stdio.h>
#include <cstdlib>
#include <string>
#include <iostream>


#include "dll.h"
#include "mybase64.h"

using namespace std;

USING_NAMESPACE(CryptoPP)



int main()
{
std::cout << "Crypto++ Example" << endl;

std:cout << "TEST" << endl;


const int textSize = 4;
const int keySize = 24; 

byte iv[] = { 240, 4, 37, 12, 167, 153, 233, 177 };
byte key[] = {191, 231, 220, 196, 173, 36, 92, 125, 146, 210, 117, 220, 95, 104, 154, 69, 180, 113, 146, 19, 124, 62, 60, 79};


byte encryptedText[textSize];

char cText[] = {'T', 'E', 'S', 'T'};

byte* text = new byte[textSize];

for (int ndx = 0; ndx<4; ndx++)
{
    text[ndx] = (byte)cText[ndx];
}


CFB_FIPS_Mode<DES_EDE3>::Encryption encryption;

encryption.SetKeyWithIV(key, keySize, iv);

encryption.ProcessString(encryptedText, text, 4);

string encoded;

encoded = base64_encode(encryptedText, 4);

cout << encoded << endl;

system("pause");

return 0;
 }

which produces following result:

K3zUUA==

Here is C# example:

using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace TripleDESExample
{
   class Program
   {
       static void Main(string[] args)
       {
           string message = "TEST";
           byte[] iv = { 240, 4, 37, 12, 167, 153, 233, 177 };
           byte[] key = { 191, 231, 220, 196, 173, 36, 92, 125, 146, 210, 117, 220, 95, 104, 154, 69, 180, 113, 146, 19, 124, 62, 60, 79 };

           byte[] data = Encoding.ASCII.GetBytes(message);

           using (var tdes = new TripleDESCryptoServiceProvider())
           {
               tdes.Mode = CipherMode.CFB;
               tdes.Padding = PaddingMode.Zeros;

               tdes.IV = iv;
               tdes.Key = key;

               using (var ms = new MemoryStream())
               {
                   using (var crypto = new CryptoStream(ms, tdes.CreateEncryptor(), CryptoStreamMode.Write))
                   {
                       crypto.Write(data, 0, data.Length);
                       crypto.Close();

                    }


                    Array.Copy(ms.ToArray(), data, data.Length);

                    Console.WriteLine(string.Format("Encrypted: {0}", Convert.ToBase64String(data)));

               }
           }

           Console.WriteLine("Press any key...");
           Console.ReadKey();
       }
   }
 }

Which produces following result:

K7nXyg==

So you can see that they produce different result.

K7nXyg==  
K3zUUA==

Can anyone point what could be the issue for them showing different result.

If possible please provide example code.

---------------------UPDATE 4/27/2017-----------------------------------------

Now tried using a little differently implementation of .NET giving me different result as well...

using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace TripleDESExample
{
   class Program
   {
       static void Main(string[] args)
       {
           string message = "TEST";


        byte[] iv = { 240, 4, 37, 12, 167, 153, 233, 177 };
        byte[] key = { 191, 231, 220, 196, 173, 36, 92, 125, 146, 210, 117, 220, 95, 104, 154, 69, 180, 113, 146, 19, 124, 62, 60, 79 };



        byte[] bytes = Encoding.ASCII.GetBytes(message);

        TripleDESCryptoServiceProvider cryptoServiceProvider1 = new TripleDESCryptoServiceProvider();
        cryptoServiceProvider1.Key = key;
        cryptoServiceProvider1.IV = iv;
        cryptoServiceProvider1.Mode = CipherMode.CFB;
        cryptoServiceProvider1.Padding = PaddingMode.Zeros;
        TripleDESCryptoServiceProvider cryptoServiceProvider2 = cryptoServiceProvider1;


        byte[] inArray = cryptoServiceProvider2.CreateEncryptor().TransformFinalBlock(bytes, 0, bytes.Length);
        cryptoServiceProvider2.Clear();

        Console.WriteLine(string.Format("Encrypted: {0}", Convert.ToBase64String(inArray, 0, inArray.Length)));



        Console.WriteLine("Press any key...");
        Console.ReadKey();
       }
   }
 }

which gives me:

K7nXyp+x9kY=

Why?

------UPDATE 4/28/2017-----------

This article describes very well Crypto++ implementation.

When I try to increment BlockSize and FeedbackSize I get following error:


Based on the discussion here it seems like .NET TripleDESCryptoServiceProvider uses CipherMode.CFB in 8-bit while Crypto++ uses it with 128-bit. When trying to set FeedbackSize for .NET higher it throws exception.

Does anyone know how to resolve this issue?

回答1:

From the comments:

The issue is likely the feedback size. I believe .Net uses a small feedback size, like 8-bits, for CFB mode. Crypto++ uses the full block size for CFB mode. I'd recommend getting a baseline using CBC mode. Once you arrive at the same result in .Net and Crypto++, then switch to CFB mode and turn knobs on the feedback size.

Do you have example how to accomplish this?

You can find examples of CBC Mode on the Crypto++ wiki. Other wiki pages of interest may be TripleDES and CFB Mode.

You can also find test vectors for these modes of operation on the NIST website.

You really need to get to a baseline. You should not use random messages and random keys and ivs until you achieve your baseline.


Here's an example of using a less-than-blocksize feedback size in Crypto++. The example is available at CFB Mode on the Crypto++ wiki (we added it for this answer). You will have to dial in your parameters random parameters (but I suggest you baseline first with something like the NIST test vectors).

You should be wary of using a feedback size that is smaller than the block size because it can reduce the security of the block cipher. If given a choice, you should increase the feedback size for Mcrypt or .Net; and not reduce the feedback size for Crypto++.

SecByteBlock key(AES::DEFAULT_KEYLENGTH), iv(AES::BLOCKSIZE);
memset(key, 0x00, key.size());
memset(iv, 0x00, iv.size());

AlgorithmParameters params = MakeParameters(Name::FeedbackSize(), 1 /*8-bits*/)
                                      (Name::IV(), ConstByteArrayParameter(iv));

string plain = "CFB Mode Test";
string cipher, encoded, recovered;

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

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

   CFB_Mode< AES >::Encryption enc;
   enc.SetKey( key, key.size(), params );

   StringSource ss1( plain, true, 
      new StreamTransformationFilter( enc,
         new StringSink( cipher )
      ) // StreamTransformationFilter      
   ); // StringSource
}
catch( CryptoPP::Exception& ex )
{
   cerr << ex.what() << endl;
   exit(1);
}

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

// Pretty print cipher text
StringSource ss2( cipher, true,
   new HexEncoder(
      new StringSink( encoded )
   ) // HexEncoder
); // StringSource
cout << "cipher text: " << encoded << endl;

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

try
{
   CFB_Mode< AES >::Decryption dec;
   dec.SetKey( key, key.size(), params );

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

   cout << "recovered text: " << recovered << endl;
}
catch( CryptoPP::Exception& ex )
{
   cerr << ex.what() << endl;
   exit(1);
}

It produces the following output:

$ ./test.exe
plain text: CFB Mode Test
cipher text: 2506FBCA6F97DC7653B414C291
recovered text: CFB Mode Test

So you can see that they produce different result.

K7nXyg==
K3zUUA==

The following reproduces K7nXyg==, but its not clear to me that's what you want. You really should get to your baseline. Then you can tell us things like a key with no parity and an 8-bit feedback size.

const byte key[] = { 191, 231, 220, 196, 173, 36, 92, 125,
                     146, 210, 117, 220, 95, 104, 154, 69,
                     180, 113, 146, 19, 124, 62, 60, 79 };
const byte  iv[] = { 240, 4, 37, 12, 167, 153, 233, 177 };

ConstByteArrayParameter cb(iv, sizeof(iv));
AlgorithmParameters params = MakeParameters(Name::FeedbackSize(), 1 /*8-bits*/)
                                           (Name::IV(), ConstByteArrayParameter(iv, sizeof(iv)));

string plain = "TEST";
string cipher, encoded, recovered;

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

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

   CFB_Mode< DES_EDE3 >::Encryption enc;
   enc.SetKey( key, sizeof(key), params );

   StringSource ss1( plain, true, 
      new StreamTransformationFilter( enc,
         new StringSink( cipher )
      ) // StreamTransformationFilter      
   ); // StringSource
}
catch( CryptoPP::Exception& ex )
{
   cerr << ex.what() << endl;
   exit(1);
}

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

// Pretty print cipher text
StringSource ss2( cipher, true,
   new Base64Encoder(
      new StringSink( encoded )
   ) // HexEncoder
); // StringSource
cout << "cipher text: " << encoded << endl;

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

try
{
   CFB_Mode< DES_EDE3 >::Decryption dec;
   dec.SetKey( key, sizeof(key), params );

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

   cout << "recovered text: " << recovered << endl;
}
catch( CryptoPP::Exception& ex )
{
   cerr << ex.what() << endl;
   exit(1);
}