Search code examples
crypto++cbc-mode

Manual CBC encryption handing with Crypto++


I am trying to play around with a manual encryption in CBC mode but still use Crypto++, just to know can I do it manually. The CBC algorithm is (AFAIK):

Presume we have n block K[1]....k[n]
0. cipher = empty;
1. xor(IV, K1) -> t1
2. encrypt(t1) -> r1
3. cipher += r1
4. xor (r1, K2) -> t2
5. encrypt(t2) -> r2
6. cipher += r2
7. xor(r2, K3)->t3
8. ...

So I tried to implement it with Crypto++. I have a text file with alphanumeric characters only. Test 1 is read file chunk by chunk (16 byte) and encrypt them using CBC mode manually, then sum up the cipher. Test 2 is use Crypto++ built-in CBC mode.
Test 1

char* key;
char* iv;
//Iterate in K[n] array of n blocks
BSIZE = 16;

std::string vectorToString(vector<char> v){
    string s ="";
    for (int i = 0; i < v.size(); i++){
        s[i] = v[i];
    }
    return s;
}

vector<char> xor( vector<char> s1, vector<char> s2, int len){

    vector<char> r;
    for (int i = 0; i < len; i++){
        int u = s1[i] ^ s2[i];
        r.push_back(u);
    }
    return r;
}

vector<char> byteToVector(byte *b, int len){
    vector<char> v;
    for (int i = 0; i < len; i++){
        v.push_back( b[i]);
    }
    return v;
}

string cbc_manual(byte [n]){
    int i = 0;
    //Open a file and read from it, buffer size = 16
    // , equal to DEFAULT_BLOCK_SIZE
    std::ifstream fin(fileName, std::ios::binary | std::ios::in);
    const int BSIZE = 16;

    vector<char> encryptBefore;
    //This function will return cpc
    string cpc ="";
    while (!fin.eof()){

        char buffer[BSIZE];
        //Read a chunk of file 
        fin.read(buffer, BSIZE);
        int sb = sizeof(buffer);
        if (i == 0){
            encryptBefore = byteToVector( iv, BSIZE);
        }

        //If i == 0, xor IV with current buffer
        //else, xor encryptBefore with current buffer

        vector<char> t1 = xor(encryptBefore, byteToVector((byte*) buffer, BSIZE), BSIZE);
            //After xored, encrypt the xor result, it will be current step cipher
        string r1= encrypt(t1, BSIZE).c_str();
        cpc += r1;
        const char* end = r1.c_str() ;

        encryptBefore = stringToVector( r1);


        i++;
    }
    return cpc;
}

This is my encrypt() function, because we have only one block so I use ECB (?) mode

string encrypt(string s, int size){
    ECB_Mode< AES >::Encryption e;
    e.SetKey(key, size);

    string cipher;
    StringSource ss1(s, true,
        new StreamTransformationFilter(e,
            new StringSink(cipher)
        ) // StreamTransformationFilter      
        ); // StringSource
    return cipher;
}


And this is 100% Crypto++ made solution:
Test 2

encryptCBC(char * plain){
    CBC_Mode < AES >::Encryption encryption(key, sizeof(key), iv);
    StreamTransformationFilter encryptor(encryption, NULL);
    for (size_t j = 0; j < plain.size(); j++)
        encryptor.Put((byte)plain[j]);

    encryptor.MessageEnd();
    size_t ready = encryptor.MaxRetrievable();

    string cipher(ready, 0x00);
    encryptor.Get((byte*)&cipher[0], cipher.size());
}

Result of Test 1 and Test 2 are different. In the fact, ciphered text from Test 1 is contain the result of Test 2. Example:

Test 1's result aaa[....]bbb[....]ccc[...]...

Test 2 (Crypto++ built-in CBC)'s result: aaabbbccc...

I know the xor() function may cause a problem relate to "sameChar ^ sameChar = 0", but is there any problem relate to algorithm in my code?

This is my Test 2.1 after the 1st solution of jww.

static string auto_cbc2(string plain, long size){
    CBC_Mode< AES >::Encryption e;
    e.SetKeyWithIV(key, sizeof(key), iv, sizeof(iv));
    string cipherText;

    CryptoPP::StringSource ss(plain, true,
        new CryptoPP::StreamTransformationFilter(e,
        new CryptoPP::StringSink(cipherText)
        , BlockPaddingSchemeDef::NO_PADDING
        ) // StreamTransformationFilter
        ); // StringSource
    return cipherText;
}

It throw an error:

Unhandled exception at 0x7407A6F2 in AES-CRPP.exe: Microsoft C++ exception: CryptoPP::InvalidDataFormat at memory location 0x00EFEA74

I only got this error when use BlockPaddingSchemeDef::NO_PADDING, tried to remove BlockPaddingSchemeDef or using BlockPaddingSchemeDef::DEFAULT_PADDING, I got no error . :?


Solution

  • StringSource ss1(s, true,
        new StreamTransformationFilter(e,
            new StringSink(cipher)));
    

    This uses PKCS padding by default. It takes a 16-byte input and produces a 32-byte output due to padding. You should do one of two things.

    First, you can use BlockPaddingScheme::NO_PADDING. Something like:

    StringSource ss1(s, true,
        new StreamTransformationFilter(e,
            new StringSink(cipher)
        BlockPaddingScheme::NO_PADDING));
    

    Second, you can process blocks manually, 16 bytes at a time. Something like:

    AES::Encryption encryptor(key, keySize);
    
    byte ibuff[<some size>] = ...;
    byte obuff[<some size>];
    ASSERT(<some size> % AES::BLOCKSIZE == 0);
    
    unsigned int BLOCKS = <some size>/AES::BLOCKSIZE;
    for (unsigned int i=0; i<BLOCKS; i==)
    {
        encryptor.ProcessBlock(&ibuff[i*16], &obuff[i*16]);
        // Do the CBC XOR thing...
    }
    

    You may be able to call ProcessAndXorBlock from the BlockCipher base class and do it in one shot.