Subject | RC4 |
---|---|
Author | Jim Starkey |
Post date | 2010-11-13T23:01:59Z |
I coded up an RC4 transform for a performance test. Running a 4M
record TPCC load, encrypting all client communications with RC4 added
about 7% (64 bit gcc on Linux) to elapsed time. Compiled for debug, the
difference was 12%. This is a huge improvement over AES.
RC4 is a stream, not block cipher. RC4 generates a random keystream
that is XORed bytewise with the plaintext. The basic foundation is a
256 byte array of scrambled value from 0 to 255. Each step swaps a pair
of entries.
RC4 got a bad rep over the WEP fiasco, but the problem was really with
how they used RC4, not RC4 itself. If you want more detail, see
http://www.rsa.com/rsalabs/node.asp?id=2009
Attached are the RC4 transform, an abstract "cipher" transform for
polymorphic ciphers, and an update of the AES transform with optional
CBC (cipher block chaining) to make Geoff happy.
--
Jim Starkey
Founder, NimbusDB, Inc.
978 526-1376
----------
// copyright (c) 2010 by NimbusDB, Inc.
#include "Cloud.h"
#include "AESTransform.h"
#include "TransformException.h"
#include "tomcrypt.h"
#define SETUP rijndael_setup
#define ECB_ENC rijndael_ecb_encrypt
#define ECB_DEC rijndael_ecb_decrypt
#define ECB_DONE rijndael_done
#define ECB_TEST rijndael_test
#define ECB_KS rijndael_keysize
extern int SETUP(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey);
extern int ECB_ENC(const unsigned char *pt, unsigned char *ct, symmetric_key *skey);
extern int ECB_DEC(const unsigned char *pt, unsigned char *ct, symmetric_key *skey);
#ifdef _DEBUG
#undef THIS_FILE
static const char THIS_FILE[]=__FILE__;
#endif
AESTransform::AESTransform(int operatingMode, Transform *src)
{
mode = operatingMode;
source = src;
schedule = new symmetric_key;
blockSize = AES_blocksize;
blockChaining = false;
reset();
}
AESTransform::~AESTransform(void)
{
delete schedule;
}
unsigned int AESTransform::get(unsigned int bufferLength, UCHAR* buffer)
{
UCHAR *p = buffer;
UCHAR *endBuffer = buffer + bufferLength;
UCHAR input[AES_blocksize];
// If there is residual data from the last call, move it to output buffer
if (ptr < end)
{
int len = MIN(bufferLength, (unsigned int)(end - ptr));
memcpy (p, ptr, len);
ptr += len;
p += len;
}
// loop until output buffer is full or we run out of data
while (p < endBuffer && !done)
{
int len = source->get(sizeof(input), input);
// If we got less than a full block, we've run the input source dry
if (len < sizeof(input))
{
done = true;
if (len == 0)
break;
memset(input + len, 0, AES_blocksize - len);
}
// Encode or decode a block
switch(mode)
{
case AES_encrypt:
if (blockChaining)
{
for (uint n = 0; n < AES_blocksize; ++n)
chain[n] ^= input[n];
ECB_ENC(chain, block, schedule);
memcpy(chain, block, AES_blocksize);
}
else
ECB_ENC(input, block, schedule);
break;
case AES_decrypt:
if (len < sizeof(input))
throw TransformException("partial AES block");
ECB_DEC(input, block, schedule);
if (blockChaining)
{
for (uint n = 0; n < AES_blocksize; ++n)
block[n] ^= chain[n];
memcpy(chain, input, AES_blocksize);
}
break;
}
// Figure out how much of the block fits in the output buffer and more than amount of data
len = MIN(endBuffer - p, AES_blocksize);
memcpy(p, block, len);
p += len;
// If there's residual data, set up for the next call
if (len < sizeof(block))
{
ptr = block + len;
break;
}
}
return p - buffer;
}
unsigned int AESTransform::getLength(void)
{
int len = source->getLength();
return ROUNDUP(len, AES_blocksize);
}
void AESTransform::reset(void)
{
end = block + sizeof(block);
ptr = end;
done = false;
if (source)
source->reset();
}
void AESTransform::setKey(uint keyLength, const UCHAR* key)
{
int rounds = 0;
switch (keyLength)
{
case 16:
rounds = 10;
break;
case 24:
rounds = 12;
break;
case 32:
rounds = 14;
break;
default:
throw TransformException("Invalid AES keysize");
}
SETUP(key, keyLength, rounds, schedule);
}
void AESTransform::setInitialVector(uint vectorLength, const UCHAR* vector)
{
blockChaining = true;
if (vectorLength)
for (uint pos = 0; pos < sizeof(chain);)
{
int l = MIN(vectorLength, (sizeof(chain) - pos));
memcpy(chain + pos, vector, l);
pos += l;
}
else
for (uint pos = 0; pos < sizeof(chain); ++pos)
chain[pos] = pos;
}
uint AESTransform::getBlockSize(void)
{
return AES_blocksize;
}
----------
// copyright (c) 2010 by NimbusDB, Inc.
#ifndef _AES_TRANSFORM_H_
#define _AES_TRANSFORM_H_
#include "CipherTransform.h"
static const int AES_encrypt = 0;
static const int AES_decrypt = 1;
static const int AES_blocksize = 16;
union Symmetric_key;
class AESTransform : public CipherTransform
{
public:
AESTransform(int operatingMode, Transform *src);
virtual ~AESTransform(void);
virtual unsigned int get(unsigned int bufferLength, UCHAR* buffer);
virtual unsigned int getLength(void);
virtual void reset(void);
virtual void setInitialVector(uint vectorLength, const UCHAR* vector);
virtual uint getBlockSize(void);
void setKey(uint keyLength, const UCHAR* key);
int mode;
uint blockSize;
bool done;
bool blockChaining;
Transform *source;
UCHAR block[AES_blocksize];
UCHAR chain[AES_blocksize];
UCHAR *ptr;
UCHAR *end;
Symmetric_key *schedule;
};
#endif
----------
// copyright (c) 2010 by NimbusDB, Inc.
#ifndef _CIPHER_TRANSFORM_H_
#define _CIPHER_TRANSFORM_H_
#include "Transform.h"
class CipherTransform : public Transform
{
public:
virtual void setKey(uint keyLength, const unsigned char *key) = 0;
virtual void setInitialVector(uint vectorLength, const unsigned char *vector) = 0;
virtual uint getBlockSize() = 0;
};
#endif
----------
// copyright (c) 2010 by NimbusDB, Inc.
#include "Cloud.h"
#include "RC4Transform.h"
#include "TransformException.h"
//#define SWAP(i,j) { state[i] ^= state[j]; state[j] ^= state[i]; state[i] ^= state[j]; }
#define SWAP(i,j) { UCHAR c = state[i]; state[i] = state[j]; state[j] = c; }
RC4Transform::RC4Transform(Transform *src)
{
source = src;
}
RC4Transform::~RC4Transform(void)
{
}
void RC4Transform::setKey(uint keyLength, const UCHAR* key)
{
for (uint n = 0; n < sizeof(state); ++n)
state[n] = n;
for (uint k1 = 0, k2 = 0; k1 < 256; ++k1)
{
k2 = (k2 + key[k1 % keyLength] + state[k1]) & 0xff;
SWAP(k1, k2);
}
s1 = s2 = 0;
}
uint RC4Transform::get(uint bufferLength, UCHAR* buffer)
{
uint length = source->get(bufferLength, buffer);
for (UCHAR *p = buffer, *end = buffer + length; p < end;)
{
s1 = (s1 + 1) & 0xff;
s2 = (s2 + state[s1]) & 0xff;
SWAP(s1, s2);
UCHAR b = state[(state[s1] + state[s2]) & 0xff];
*p++ ^= b;
}
return length;
}
void RC4Transform::reset(void)
{
if (source)
source->reset();
}
uint RC4Transform::getLength(void)
{
if (source)
return source->getLength();
return 0;
}
uint RC4Transform::getBlockSize(void)
{
return 1;
}
void RC4Transform::setInitialVector(uint vectorLength, const UCHAR* vector)
{
}
----------
// copyright (c) 2010 by NimbusDB, Inc.
#ifndef _RC4_TRANSFORM_H_
#define _RC4_TRANSFORM_H_
#include "CipherTransform.h"
class RC4Transform : public CipherTransform
{
public:
RC4Transform(Transform *source);
~RC4Transform(void);
virtual uint get(uint bufferLength, UCHAR* buffer);
virtual void reset(void);
virtual uint getLength(void);
virtual uint getBlockSize(void);
virtual void setKey(uint keyLength, const UCHAR* key);
virtual void setInitialVector(uint vectorLength, const UCHAR* vector);
Transform *source;
uint s1, s2;
UCHAR state[256];
};
#endif
[Non-text portions of this message have been removed]
record TPCC load, encrypting all client communications with RC4 added
about 7% (64 bit gcc on Linux) to elapsed time. Compiled for debug, the
difference was 12%. This is a huge improvement over AES.
RC4 is a stream, not block cipher. RC4 generates a random keystream
that is XORed bytewise with the plaintext. The basic foundation is a
256 byte array of scrambled value from 0 to 255. Each step swaps a pair
of entries.
RC4 got a bad rep over the WEP fiasco, but the problem was really with
how they used RC4, not RC4 itself. If you want more detail, see
http://www.rsa.com/rsalabs/node.asp?id=2009
Attached are the RC4 transform, an abstract "cipher" transform for
polymorphic ciphers, and an update of the AES transform with optional
CBC (cipher block chaining) to make Geoff happy.
--
Jim Starkey
Founder, NimbusDB, Inc.
978 526-1376
----------
// copyright (c) 2010 by NimbusDB, Inc.
#include "Cloud.h"
#include "AESTransform.h"
#include "TransformException.h"
#include "tomcrypt.h"
#define SETUP rijndael_setup
#define ECB_ENC rijndael_ecb_encrypt
#define ECB_DEC rijndael_ecb_decrypt
#define ECB_DONE rijndael_done
#define ECB_TEST rijndael_test
#define ECB_KS rijndael_keysize
extern int SETUP(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey);
extern int ECB_ENC(const unsigned char *pt, unsigned char *ct, symmetric_key *skey);
extern int ECB_DEC(const unsigned char *pt, unsigned char *ct, symmetric_key *skey);
#ifdef _DEBUG
#undef THIS_FILE
static const char THIS_FILE[]=__FILE__;
#endif
AESTransform::AESTransform(int operatingMode, Transform *src)
{
mode = operatingMode;
source = src;
schedule = new symmetric_key;
blockSize = AES_blocksize;
blockChaining = false;
reset();
}
AESTransform::~AESTransform(void)
{
delete schedule;
}
unsigned int AESTransform::get(unsigned int bufferLength, UCHAR* buffer)
{
UCHAR *p = buffer;
UCHAR *endBuffer = buffer + bufferLength;
UCHAR input[AES_blocksize];
// If there is residual data from the last call, move it to output buffer
if (ptr < end)
{
int len = MIN(bufferLength, (unsigned int)(end - ptr));
memcpy (p, ptr, len);
ptr += len;
p += len;
}
// loop until output buffer is full or we run out of data
while (p < endBuffer && !done)
{
int len = source->get(sizeof(input), input);
// If we got less than a full block, we've run the input source dry
if (len < sizeof(input))
{
done = true;
if (len == 0)
break;
memset(input + len, 0, AES_blocksize - len);
}
// Encode or decode a block
switch(mode)
{
case AES_encrypt:
if (blockChaining)
{
for (uint n = 0; n < AES_blocksize; ++n)
chain[n] ^= input[n];
ECB_ENC(chain, block, schedule);
memcpy(chain, block, AES_blocksize);
}
else
ECB_ENC(input, block, schedule);
break;
case AES_decrypt:
if (len < sizeof(input))
throw TransformException("partial AES block");
ECB_DEC(input, block, schedule);
if (blockChaining)
{
for (uint n = 0; n < AES_blocksize; ++n)
block[n] ^= chain[n];
memcpy(chain, input, AES_blocksize);
}
break;
}
// Figure out how much of the block fits in the output buffer and more than amount of data
len = MIN(endBuffer - p, AES_blocksize);
memcpy(p, block, len);
p += len;
// If there's residual data, set up for the next call
if (len < sizeof(block))
{
ptr = block + len;
break;
}
}
return p - buffer;
}
unsigned int AESTransform::getLength(void)
{
int len = source->getLength();
return ROUNDUP(len, AES_blocksize);
}
void AESTransform::reset(void)
{
end = block + sizeof(block);
ptr = end;
done = false;
if (source)
source->reset();
}
void AESTransform::setKey(uint keyLength, const UCHAR* key)
{
int rounds = 0;
switch (keyLength)
{
case 16:
rounds = 10;
break;
case 24:
rounds = 12;
break;
case 32:
rounds = 14;
break;
default:
throw TransformException("Invalid AES keysize");
}
SETUP(key, keyLength, rounds, schedule);
}
void AESTransform::setInitialVector(uint vectorLength, const UCHAR* vector)
{
blockChaining = true;
if (vectorLength)
for (uint pos = 0; pos < sizeof(chain);)
{
int l = MIN(vectorLength, (sizeof(chain) - pos));
memcpy(chain + pos, vector, l);
pos += l;
}
else
for (uint pos = 0; pos < sizeof(chain); ++pos)
chain[pos] = pos;
}
uint AESTransform::getBlockSize(void)
{
return AES_blocksize;
}
----------
// copyright (c) 2010 by NimbusDB, Inc.
#ifndef _AES_TRANSFORM_H_
#define _AES_TRANSFORM_H_
#include "CipherTransform.h"
static const int AES_encrypt = 0;
static const int AES_decrypt = 1;
static const int AES_blocksize = 16;
union Symmetric_key;
class AESTransform : public CipherTransform
{
public:
AESTransform(int operatingMode, Transform *src);
virtual ~AESTransform(void);
virtual unsigned int get(unsigned int bufferLength, UCHAR* buffer);
virtual unsigned int getLength(void);
virtual void reset(void);
virtual void setInitialVector(uint vectorLength, const UCHAR* vector);
virtual uint getBlockSize(void);
void setKey(uint keyLength, const UCHAR* key);
int mode;
uint blockSize;
bool done;
bool blockChaining;
Transform *source;
UCHAR block[AES_blocksize];
UCHAR chain[AES_blocksize];
UCHAR *ptr;
UCHAR *end;
Symmetric_key *schedule;
};
#endif
----------
// copyright (c) 2010 by NimbusDB, Inc.
#ifndef _CIPHER_TRANSFORM_H_
#define _CIPHER_TRANSFORM_H_
#include "Transform.h"
class CipherTransform : public Transform
{
public:
virtual void setKey(uint keyLength, const unsigned char *key) = 0;
virtual void setInitialVector(uint vectorLength, const unsigned char *vector) = 0;
virtual uint getBlockSize() = 0;
};
#endif
----------
// copyright (c) 2010 by NimbusDB, Inc.
#include "Cloud.h"
#include "RC4Transform.h"
#include "TransformException.h"
//#define SWAP(i,j) { state[i] ^= state[j]; state[j] ^= state[i]; state[i] ^= state[j]; }
#define SWAP(i,j) { UCHAR c = state[i]; state[i] = state[j]; state[j] = c; }
RC4Transform::RC4Transform(Transform *src)
{
source = src;
}
RC4Transform::~RC4Transform(void)
{
}
void RC4Transform::setKey(uint keyLength, const UCHAR* key)
{
for (uint n = 0; n < sizeof(state); ++n)
state[n] = n;
for (uint k1 = 0, k2 = 0; k1 < 256; ++k1)
{
k2 = (k2 + key[k1 % keyLength] + state[k1]) & 0xff;
SWAP(k1, k2);
}
s1 = s2 = 0;
}
uint RC4Transform::get(uint bufferLength, UCHAR* buffer)
{
uint length = source->get(bufferLength, buffer);
for (UCHAR *p = buffer, *end = buffer + length; p < end;)
{
s1 = (s1 + 1) & 0xff;
s2 = (s2 + state[s1]) & 0xff;
SWAP(s1, s2);
UCHAR b = state[(state[s1] + state[s2]) & 0xff];
*p++ ^= b;
}
return length;
}
void RC4Transform::reset(void)
{
if (source)
source->reset();
}
uint RC4Transform::getLength(void)
{
if (source)
return source->getLength();
return 0;
}
uint RC4Transform::getBlockSize(void)
{
return 1;
}
void RC4Transform::setInitialVector(uint vectorLength, const UCHAR* vector)
{
}
----------
// copyright (c) 2010 by NimbusDB, Inc.
#ifndef _RC4_TRANSFORM_H_
#define _RC4_TRANSFORM_H_
#include "CipherTransform.h"
class RC4Transform : public CipherTransform
{
public:
RC4Transform(Transform *source);
~RC4Transform(void);
virtual uint get(uint bufferLength, UCHAR* buffer);
virtual void reset(void);
virtual uint getLength(void);
virtual uint getBlockSize(void);
virtual void setKey(uint keyLength, const UCHAR* key);
virtual void setInitialVector(uint vectorLength, const UCHAR* vector);
Transform *source;
uint s1, s2;
UCHAR state[256];
};
#endif
[Non-text portions of this message have been removed]