Theoretical Presentation
In this article I am presenting a new Block Ciphering encryption method which is an adaptation of some results I presented in a
previous article with
www.codeproject.com "Some Stream Cipher Encryption/Decryption Algorithms"
for the Stream Ciphering case. Some of the advantages of this method are:
- It is easy to understand and implement;
- It can be applied for any block size;
- It can be applied for a sufficient large range in key size;
- The strength can be increased simply by increasing the number of rounds, or the block size, or the key size;
- It is fast enough;
- It is in my opinion enough secure;
It remain to see the opinions of other people especially about the security claim.
Details
This method has two variants, each one based on the a corresponding stream ciphering variant of the XOR256 method presented in my
previous article for the stream ciphering case. The method for generating the seeds for the
pseudo-random generators from the key is the same. The difference is that the sequences of pseudo-random generators are now calculated only once
during the initialization phase and after that are considered as method constants and applied to any block. Let's denote
by no_rounds
the number of rounds, block_size
the size of the blocks and
no_random
the number of pseudo-random generators (which
can be determined from the key size by the formula no_random = (key_size+1)/2). Then the number of constants is
2*no_rounds*block_size
for the
first variant of the XOR256 block ciphering method and no_rounds*block_size*no_random for the second variant of the XOR256 block ciphering method. For example
for blocks of size 8, keys of size 8 and 3 rounds we will have 48 method constants for the first variant and 96 method constants for the second variant,
and for blocks of size 16, keys of size 16 and 3 rounds we will have 96 method constants for the first variant and 384 method constants for the second variant.
You can see that the space of the constants is much larger than the key space or (seed space which is the same size), so that an decryption approach to determine
the constants is less practical than an approach to determine the key. The constants are determined from the pseudo-random generators by:
for(k=0; k<no_rounds*block_size; k++)
{
cxor[k] = random[0][k]^random[1][k]^...^random
[no_random-1][k]
c256[k] = (random[0][k]+random[1][k]+...+random
[no_random-1][k])%256
}
for the First Variant and
for(k=0; k<no_rounds*block_size; k++)
for(i=0; i<no_random; i++)
c[k][i] = random[i][k]%256;
for the Second Variant.
The core of the method consists in applying the stream cipher XOR256 procedure a number of rounds scanning the block in
succession in order and reverse order. For example for a three rounds encryption, first time the block is scanned directly, second time is scanned
in reverse and
last time is scanned again directly. For the first no_rounds-1
rounds the previous encrypted character is involved in the encryption. It is
ensuring the dispersion of small differences in blocks. Otherwise if two plaintext blocks differ only in one place (one byte) then the
cipher texts will also differ only in one place, and it could help the decryption attacks to easily identify similar blocks. In the particular
case of the first character of the block for a direct scanning (or last character of the block for a
reverse scanning) the previous encrypted
character is considered 0. Let's denote by prev_cipher
the result of the previous encryption round (for the first round it is the same as the plain text)
and by cur_cipher
the result of the current encryption round. We have for the block cipher XOR256 First Variant:
cur_cipher[k] = (cur_cipher[k-1]^prev_cipher[k]^cxor[k] + c256[k]) % 256
For the last round the previous unencrypted character is involved in the encryption. It is somewhat strengthening the method because to correctly
decrypt a character you should have correctly decrypted the previous character. In the particular case of the first character in the block for a
direct scanning (or last character in the block for a reverse scanning) the previous unencrypted character is considered 0. We have (a direct
scanning of the block is assumed):
cur_cipher[k] = (prev_cipher[k-1]^prev_cipher[k]^cxor[k] + c256[k]) % 256
For the block cipher XOR256 Second Variant we have recursively (a direct scanning of the block is assumed):
cur_cipher[k] = (cur_cipher[k-1]^prev_cipher[k]^c[k][0] + c[k][0]) % 256
cur_cipher[k] = (cur_cipher[k]^c[k][1] + c[k][1]) % 256
...
cur_cipher[k] = (cur_cipher[k]^c[k][no_random-1] + c[k][no_random-1]) % 256
For the last round the previous unencrypted character is involved in the encryption:
cur_cipher[k] = (prev_cipher[k-1]^prev_cipher[k]^c[k][0] + c[k][0]) % 256
cur_cipher[k] = (cur_cipher[k]^c[k][1] + c[k][1]) % 256
...
cur_cipher[k] = (cur_cipher[k]^c[k][no_random-1] + c[k][no_random-1]) % 256
Implementation
The Pseudo-Random Number generator is implemented in the CRand
class and is identical to the one presented in my
previous article . The common interface for the XOR256 classes is:
class IXOR256
{
public:
IXOR256() : m_iBlockSize(0), m_iSize(0), m_pucChain0(NULL),
m_pucChain(NULL), m_pucTemp(NULL)
{
}
virtual ~IXOR256()
{
if(m_pucChain0 != NULL)
delete [] m_pucChain0;
if(m_pucChain != NULL)
delete [] m_pucChain;
if(m_pucTemp != NULL)
delete [] m_pucTemp;
};
enum { KEY_MAX = 217 };
enum { ECB=0, CBC=1, CFB=2 };
virtual bool Initialize(unsigned int iBlockSize, unsigned int iRounds,
unsigned char* pucChain, string const& rostrKey) = 0;
virtual bool EncryptBlock(unsigned char* pucBlock) = 0;
virtual bool DecryptBlock(unsigned char* pucBlock) = 0;
bool Encrypt(unsigned char const* pucIn, unsigned char* pucOut,
size_t n, int iMode=ECB);
bool Decrypt(unsigned char const* pucIn, unsigned char* pucOut,
size_t n, int iMode=ECB);
void ResetChain();
protected:
void Xor(unsigned char* pucBuff, unsigned char const* pucChain);
protected:
int m_iBlockSize;
int m_iRounds;
int m_iSize;
unsigned char* m_pucChain0;
unsigned char* m_pucChain;
unsigned char* m_pucTemp;
};
The method's variants interfaces are derived from the common interface. The interface for the First Variant is:
class CXOR256_0 : public IXOR256
{
public:
CXOR256_0() : m_pucXOR(NULL), m_puc256(NULL)
{
}
virtual ~CXOR256_0()
{
if(m_pucXOR != NULL)
delete [] m_pucXOR;
if(m_puc256 != NULL)
delete [] m_puc256;
};
virtual bool Initialize(unsigned int iBlockSize, unsigned int iRounds,
unsigned char* pucChain, string const& rostrKey);
virtual bool EncryptBlock(unsigned char* pucBlock);
virtual bool DecryptBlock(unsigned char* pucBlock);
void EncryptDirect(unsigned char* pucBlock, unsigned char const* pucXOR,
unsigned char const* puc256);
void EncryptReverse(unsigned char* pucBlock, unsigned char const* pucXOR,
unsigned char const* puc256);
void EncryptDirect1(unsigned char* pucBlock, unsigned char const* pucXOR,
unsigned char const* puc256);
void EncryptReverse1(unsigned char* pucBlock, unsigned char const* pucXOR,
unsigned char const* puc256);
void DecryptDirect(unsigned char* pucBlock, unsigned char const* pucXOR,
unsigned char const* puc256);
void DecryptReverse(unsigned char* pucBlock, unsigned char const* pucXOR,
unsigned char const* puc256);
void DecryptDirect1(unsigned char* pucBlock, unsigned char const* pucXOR,
unsigned char const* puc256);
void DecryptReverse1(unsigned char* pucBlock, unsigned char const* pucXOR,
unsigned char const* puc256);
private:
unsigned char* m_pucXOR;
unsigned char* m_puc256;
};
For the Second Variant the interface is:
class CXOR256_1 : public IXOR256
{
public:
CXOR256_1() : m_pucXOR256(NULL)
{
}
virtual ~CXOR256_1()
{
if(m_pucXOR256 != NULL)
delete [] m_pucXOR256;
};
virtual bool Initialize(unsigned int iBlockSize, unsigned int iRounds,
unsigned char* pucChain, string const& rostrKey);
virtual bool EncryptBlock(unsigned char* pucBlock);
virtual bool DecryptBlock(unsigned char* pucBlock);
void EncryptDirect(unsigned char* pucBlock,
unsigned char const* pucXOR256);
void EncryptReverse(unsigned char* pucBlock,
unsigned char const* pucXOR256);
void EncryptDirect1(unsigned char* pucBlock,
unsigned char const* pucXOR256);
void EncryptReverse1(unsigned char* pucBlock,
unsigned char const* pucXOR256);
void DecryptDirect(unsigned char* pucBlock,
unsigned char const* pucXOR256);
void DecryptReverse(unsigned char* pucBlock,
unsigned char const* pucXOR256);
void DecryptDirect1(unsigned char* pucBlock,
unsigned char const* pucXOR256);
void DecryptReverse1(unsigned char* pucBlock,
unsigned char const* pucXOR256);
private:
unsigned char* m_pucXOR256;
};
The method's constants are calculated for a given key in the Initialize()
method.
For the First Variant:
bool CXOR256_0::Initialize(unsigned int iBlockSize, unsigned int iRounds,
unsigned char* pucChain, string const& rostrKey)
{
if(0 == iBlockSize || 0 == iRounds)
return false;
m_iBlockSize = iBlockSize;
m_iRounds = iRounds;
string ostrKey = rostrKey.substr(0, KEY_MAX);
int iSize = ostrKey.size()>>1;
if(0 == iSize)
return false;
bool bOdd = false;
if(1 == (ostrKey.size()&1))
{
iSize++;
bOdd = true;
}
CRand* paoRand = new CRand[iSize];
if(true == bOdd)
iSize--;
int i, j;
int iSum = 0;
for(i=0; i<iSize; i++)
{
iSum += ostrKey[i<<1] + ((ostrKey[(i<<1)+1]+1)<<8) + 1;
paoRand[i].SetSeed(iSum);
}
if(true == bOdd)
{
iSum += ostrKey[iSize<<1]+1;
paoRand[iSize].SetSeed(iSum);
iSize++;
}
if(m_pucChain0 != NULL)
delete [] m_pucChain0;
if(m_pucChain != NULL)
delete [] m_pucChain;
m_pucChain0 = new unsigned char[m_iBlockSize];
m_pucChain = new unsigned char[m_iBlockSize];
memcpy(m_pucChain0, pucChain, m_iBlockSize);
memcpy(m_pucChain, pucChain, m_iBlockSize);
if(m_pucXOR != NULL)
delete [] m_pucXOR;
int iSize1 = m_iRounds*m_iBlockSize;
m_pucXOR = new unsigned char[iSize1];
if(m_puc256 != NULL)
delete [] m_puc256;
m_puc256 = new unsigned char[iSize1];
for(i=0; i<iSize1; i++)
{
m_pucXOR[i] = 0;
m_puc256[i] = 0;
unsigned int uiRand;
for(j=0; j<iSize; j++)
{
uiRand = paoRand[j].Rand();
m_pucXOR[i] ^= uiRand;
m_puc256[i] += uiRand;
}
}
delete [] paoRand;
m_pucTemp = new unsigned char[m_iBlockSize];
return true;
}
For the Second Variant:
bool CXOR256_1::Initialize(unsigned int iBlockSize, unsigned int iRounds,
unsigned char* pucChain, string const& rostrKey)
{
if(0 == iBlockSize || 0 == iRounds)
return false;
m_iBlockSize = iBlockSize;
m_iRounds = iRounds;
string ostrKey = rostrKey.substr(0, KEY_MAX);
int iSize = (ostrKey.size()+1)>>1;
if(0 == iSize)
return false;
bool bOdd = false;
if(1 == (ostrKey.size()&1))
{
iSize++;
bOdd = true;
}
m_iSize = iSize;
CRand* paoRand = new CRand[m_iSize];
if(true == bOdd)
iSize--;
int i, j;
int iSum = 0;
for(i=0; i<iSize; i++)
{
iSum += ostrKey[i<<1] + ((ostrKey[(i<<1)+1]+1)<<8) + 1;
paoRand[i].SetSeed(iSum);
}
if(true == bOdd)
{
iSum += ostrKey[iSize<<1]+1;
paoRand[iSize].SetSeed(iSum);
iSize++;
}
if(m_pucChain0 != NULL)
delete [] m_pucChain0;
if(m_pucChain != NULL)
delete [] m_pucChain;
m_pucChain0 = new unsigned char[m_iBlockSize];
m_pucChain = new unsigned char[m_iBlockSize];
memcpy(m_pucChain0, pucChain, m_iBlockSize);
memcpy(m_pucChain, pucChain, m_iBlockSize);
if(m_pucXOR256 != NULL)
delete [] m_pucXOR256;
m_pucXOR256 = new unsigned char[m_iRounds*m_iBlockSize*m_iSize];
unsigned char* pucXOR256 = &m_pucXOR256[0];
for(i=0; i<m_iRounds*m_iBlockSize; i++)
for(j=0; j<m_iSize; j++,pucXOR256++)
*pucXOR256 = paoRand[j].Rand();
delete [] paoRand;
m_pucTemp = new unsigned char[m_iBlockSize];
return true;
}
The code for one round encryption is in the methods EncryptDirect()
, EncryptReverse()
, EncryptDirect1()
and EncryptReverse1()
specialized for the cases of direct and respectively reverse block scanning and the consideration of the previous encrypted
character or respectively previous unencrypted character.
Direct block scanning considering the previous encrypted character, XOR256 First Variant:
void CXOR256_0::EncryptDirect(unsigned char* pucBlock, unsigned char const* pucXOR,
unsigned char const* puc256)
{
unsigned char ucPrev = 0;
for(int i=0; i<m_iBlockSize; i++,pucBlock++,pucXOR++,puc256++)
{
*pucBlock ^= ucPrev^*pucXOR;
*pucBlock += *puc256;
ucPrev = *pucBlock;
}
}
Reverse block scanning considering the previous encrypted character, XOR256 First Variant:
void CXOR256_0::EncryptReverse(unsigned char* pucBlock,
unsigned char const* pucXOR, unsigned char const* puc256)
{
unsigned char ucPrev = 0;
pucBlock += m_iBlockSize-1;
for(int i=0; i<m_iBlockSize; i++,pucBlock--,pucXOR++,puc256++)
{
*pucBlock ^= ucPrev^*pucXOR;
*pucBlock += *puc256;
ucPrev = *pucBlock;
}
}
Direct block scanning considering the previous unencrypted character, XOR256 First Variant:
void CXOR256_0::EncryptDirect1(unsigned char* pucBlock,
unsigned char const* pucXOR, unsigned char const* puc256)
{
unsigned char ucTemp;
unsigned char ucPrev = 0;
for(int i=0; i<m_iBlockSize; i++,pucBlock++,pucXOR++,puc256++)
{
ucTemp = *pucBlock;
*pucBlock ^= ucPrev^*pucXOR;
*pucBlock += *puc256;
ucPrev = ucTemp;
}
}
Reverse block scanning considering the previous unencrypted character, XOR256 First Variant:
void CXOR256_0::EncryptReverse1(unsigned char* pucBlock,
unsigned char const* pucXOR, unsigned char const* puc256)
{
unsigned char ucTemp;
unsigned char ucPrev = 0;
pucBlock += m_iBlockSize-1;
for(int i=0; i<m_iBlockSize; i++,pucBlock--,pucXOR++,puc256++)
{
ucTemp = *pucBlock;
*pucBlock ^= ucPrev^*pucXOR;
*pucBlock += *puc256;
ucPrev = ucTemp;
}
}
Direct block scanning considering the previous encrypted character, XOR256 Second Variant:
void CXOR256_1::EncryptDirect(unsigned char* pucBlock,
unsigned char const* pucXOR256)
{
unsigned char ucPrev = 0;
for(int i=0; i<m_iBlockSize; i++,pucBlock++)
{
*pucBlock = (ucPrev ^ *pucBlock ^ *pucXOR256) + *pucXOR256;
pucXOR256++;
for(int j=1; j<m_iSize; j++,pucXOR256++)
{
*pucBlock ^= *pucXOR256;
*pucBlock += *pucXOR256;
}
ucPrev = *pucBlock;
}
}
Reverse block scanning considering the previous encrypted character, XOR256 Second Variant:
void CXOR256_1::EncryptReverse(unsigned char* pucBlock,
unsigned char const* pucXOR256)
{
unsigned char ucPrev = 0;
pucBlock += m_iBlockSize-1;
for(int i=0; i<m_iBlockSize; i++,pucBlock--)
{
*pucBlock = (ucPrev ^ *pucBlock ^ *pucXOR256) + *pucXOR256;
pucXOR256++;
for(int j=1; j<m_iSize; j++,pucXOR256++)
{
*pucBlock ^= *pucXOR256;
*pucBlock += *pucXOR256;
}
ucPrev = *pucBlock;
}
}
Direct block scanning considering the previous unencrypted character, XOR256 Second Variant:
void CXOR256_1::EncryptDirect1(unsigned char* pucBlock,
unsigned char const* pucXOR256)
{
unsigned char ucTemp;
unsigned char ucPrev = 0;
for(int i=0; i<m_iBlockSize; i++,pucBlock++)
{
ucTemp = *pucBlock;
*pucBlock = (ucPrev ^ *pucBlock ^ *pucXOR256) + *pucXOR256;
pucXOR256++;
for(int j=1; j<m_iSize; j++,pucXOR256++)
{
*pucBlock ^= *pucXOR256;
*pucBlock += *pucXOR256;
}
ucPrev = ucTemp;
}
}
Reverse block scanning considering the previous unencrypted character, XOR256 Second Variant:
void CXOR256_1::EncryptReverse1(unsigned char* pucBlock,
unsigned char const* pucXOR256)
{
unsigned char ucTemp;
unsigned char ucPrev = 0;
pucBlock += m_iBlockSize-1;
for(int i=0; i<m_iBlockSize; i++,pucBlock--)
{
ucTemp = *pucBlock;
*pucBlock = (ucPrev ^ *pucBlock ^ *pucXOR256) + *pucXOR256;
pucXOR256++;
for(int j=1; j<m_iSize; j++,pucXOR256++)
{
*pucBlock ^= *pucXOR256;
*pucBlock += *pucXOR256;
}
ucPrev = ucTemp;
}
}
The above presented methods are called in the EncryptBlock()
method which is doing the real block encryption.
First Variant:
bool CXOR256_0::EncryptBlock(unsigned char* pucBlock)
{
if(0 == m_iBlockSize)
return false;
unsigned char const* pucXOR = m_pucXOR;
unsigned char const* puc256 = m_puc256;
bool bDir = true;
for(int i=0; i<m_iRounds-1; i++,pucXOR+=m_iBlockSize,
puc256+=m_iBlockSize)
{
if(true == bDir)
EncryptDirect(pucBlock, pucXOR, puc256);
else
EncryptReverse(pucBlock, pucXOR, puc256);
bDir = !bDir;
}
if(true == bDir)
EncryptDirect1(pucBlock, pucXOR, puc256);
else
EncryptReverse1(pucBlock, pucXOR, puc256);
return true;
}
Second Variant:
bool CXOR256_1::EncryptBlock(unsigned char* pucBlock)
{
if(0 == m_iBlockSize)
return false;
unsigned char const* pucXOR256 = m_pucXOR256;
bool bDir = true;
for(int i=0; i<m_iRounds-1; i++,pucXOR256+=m_iBlockSize*m_iSize)
{
if(true == bDir)
EncryptDirect(pucBlock, pucXOR256);
else
EncryptReverse(pucBlock, pucXOR256);
bDir = !bDir;
}
if(true == bDir)
EncryptDirect1(pucBlock, pucXOR256);
else
EncryptReverse1(pucBlock, pucXOR256);
return true;
}
The methods for the decryption case are corresponding to the above presented methods for the encryption case.
Direct block scanning considering the previous encrypted character, XOR256 First Variant:
void CXOR256_0::DecryptDirect(unsigned char* pucBlock,
unsigned char const* pucXOR, unsigned char const* puc256)
{
unsigned char ucTemp;
unsigned char ucPrev = 0;
for(int i=0; i<m_iBlockSize; i++,pucBlock++,pucXOR++,puc256++)
{
ucTemp = *pucBlock;
if(*puc256 <= *pucBlock)
*pucBlock -= *puc256;
else
(*pucBlock += ~*puc256)++;
*pucBlock ^= ucPrev^*pucXOR;
ucPrev = ucTemp;
}
}
Reverse block scanning considering the previous encrypted character, XOR256 First Variant:
void CXOR256_0::DecryptReverse(unsigned char* pucBlock,
unsigned char const* pucXOR, unsigned char const* puc256)
{
unsigned char ucTemp;
unsigned char ucPrev = 0;
pucBlock += m_iBlockSize-1;
for(int i=0; i<m_iBlockSize; i++,pucBlock--,pucXOR++,puc256++)
{
ucTemp = *pucBlock;
if(*puc256 <= *pucBlock)
*pucBlock -= *puc256;
else
(*pucBlock += ~*puc256)++;
*pucBlock ^= ucPrev^*pucXOR;
ucPrev = ucTemp;
}
}
Direct block scanning considering the previous unencrypted character, XOR256 First Variant:
void CXOR256_0::DecryptDirect1(unsigned char* pucBlock,
unsigned char const* pucXOR, unsigned char const* puc256)
{
unsigned char ucPrev = 0;
for(int i=0; i<m_iBlockSize; i++,pucBlock++,pucXOR++,puc256++)
{
if(*puc256 <= *pucBlock)
*pucBlock -= *puc256;
else
(*pucBlock += ~*puc256)++;
*pucBlock ^= ucPrev^*pucXOR;
ucPrev = *pucBlock;
}
}
Reverse block scanning considering the previous unencrypted character, XOR256 First Variant:
void CXOR256_0::DecryptReverse1(unsigned char* pucBlock,
unsigned char const* pucXOR, unsigned char const* puc256)
{
unsigned char ucPrev = 0;
pucBlock += m_iBlockSize-1;
for(int i=0; i<m_iBlockSize; i++,pucBlock--,pucXOR++,puc256++)
{
if(*puc256 <= *pucBlock)
*pucBlock -= *puc256;
else
(*pucBlock += ~*puc256)++;
*pucBlock ^= ucPrev^*pucXOR;
ucPrev = *pucBlock;
}
}
Direct block scanning considering the previous encrypted character, XOR256 Second Variant:
void CXOR256_1::DecryptDirect(unsigned char* pucBlock,
unsigned char const* pucXOR256)
{
unsigned char ucTemp;
unsigned char ucPrev = 0;
unsigned char const* pucXOR256_1;
for(int i=0; i<m_iBlockSize; i++,pucBlock++)
{
pucXOR256 += m_iSize;
pucXOR256_1 = pucXOR256 - 1;
ucTemp = *pucBlock;
for(int j=m_iSize-1; j>0; j--,pucXOR256_1--)
{
if(*pucXOR256_1 <= *pucBlock)
*pucBlock -= *pucXOR256_1;
else
(*pucBlock += ~(*pucXOR256_1))++;
*pucBlock ^= *pucXOR256_1;
}
if(*pucXOR256_1 <= *pucBlock)
*pucBlock -= *pucXOR256_1;
else
(*pucBlock += ~(*pucXOR256_1))++;
*pucBlock ^= ucPrev ^ *pucXOR256_1;
ucPrev = ucTemp;
}
}
Reverse block scanning considering the previous encrypted character, Second Variant:
void CXOR256_1::DecryptReverse(unsigned char* pucBlock,
unsigned char const* pucXOR256)
{
unsigned char ucTemp;
unsigned char ucPrev = 0;
pucBlock += m_iBlockSize-1;
unsigned char const* pucXOR256_1;
for(int i=0; i<m_iBlockSize; i++,pucBlock--)
{
pucXOR256 += m_iSize;
pucXOR256_1 = pucXOR256 - 1;
ucTemp = *pucBlock;
for(int j=m_iSize-1; j>0; j--,pucXOR256_1--)
{
if(*pucXOR256_1 <= *pucBlock)
*pucBlock -= *pucXOR256_1;
else
(*pucBlock += ~(*pucXOR256_1))++;
*pucBlock ^= *pucXOR256_1;
}
if(*pucXOR256_1 <= *pucBlock)
*pucBlock -= *pucXOR256_1;
else
(*pucBlock += ~(*pucXOR256_1))++;
*pucBlock ^= ucPrev ^ *pucXOR256_1;
ucPrev = ucTemp;
}
}
Direct block scanning considering the previous unencrypted character, Second Variant:
void CXOR256_1::DecryptDirect1(unsigned char* pucBlock,
unsigned char const* pucXOR256)
{
unsigned char ucPrev = 0;
unsigned char const* pucXOR256_1;
for(int i=0; i<m_iBlockSize; i++,pucBlock++)
{
pucXOR256 += m_iSize;
pucXOR256_1 = pucXOR256 - 1;
for(int j=m_iSize-1; j>0; j--,pucXOR256_1--)
{
if(*pucXOR256_1 <= *pucBlock)
*pucBlock -= *pucXOR256_1;
else
(*pucBlock += ~(*pucXOR256_1))++;
*pucBlock ^= *pucXOR256_1;
}
if(*pucXOR256_1 <= *pucBlock)
*pucBlock -= *pucXOR256_1;
else
(*pucBlock += ~(*pucXOR256_1))++;
*pucBlock ^= ucPrev ^ *pucXOR256_1;
ucPrev = *pucBlock;
}
}
Reverse block scanning considering the previous unencrypted character, Second Variant:
void CXOR256_1::DecryptReverse1(unsigned char* pucBlock,
unsigned char const* pucXOR256)
{
unsigned char ucPrev = 0;
pucBlock += m_iBlockSize-1;
unsigned char const* pucXOR256_1;
for(int i=0; i<m_iBlockSize; i++,pucBlock--)
{
pucXOR256 += m_iSize;
pucXOR256_1 = pucXOR256 - 1;
for(int j=m_iSize-1; j>0; j--,pucXOR256_1--)
{
if(*pucXOR256_1 <= *pucBlock)
*pucBlock -= *pucXOR256_1;
else
(*pucBlock += ~(*pucXOR256_1))++;
*pucBlock ^= *pucXOR256_1;
}
if(*pucXOR256_1 <= *pucBlock)
*pucBlock -= *pucXOR256_1;
else
(*pucBlock += ~(*pucXOR256_1))++;
*pucBlock ^= ucPrev ^ *pucXOR256_1;
ucPrev = *pucBlock;
}
}
Block Decryption, First Variant:
bool CXOR256_0::DecryptBlock(unsigned char* pucBlock)
{
if(0 == m_iBlockSize)
return false;
unsigned char const* pucXOR = m_pucXOR+(m_iRounds-1)*m_iBlockSize;
unsigned char const* puc256 = m_puc256+(m_iRounds-1)*m_iBlockSize;
bool bDir;
if(1 == m_iRounds%2)
{
DecryptDirect1(pucBlock, pucXOR, puc256);
bDir = false;
}
else
{
DecryptReverse1(pucBlock, pucXOR, puc256);
bDir = true;
}
pucXOR-=m_iBlockSize;
puc256-=m_iBlockSize;
for(int i=0; i<m_iRounds-1; i++,pucXOR-=m_iBlockSize,
puc256-=m_iBlockSize)
{
if(true == bDir)
DecryptDirect(pucBlock, pucXOR, puc256);
else
DecryptReverse(pucBlock, pucXOR, puc256);
bDir = !bDir;
}
return true;
}
Block Decryption, Second Variant:
bool CXOR256_1::Decrypt(unsigned char* pucBlock)
{
if(0 == m_iBlockSize)
return false;
unsigned char const* pucXOR256 =
m_pucXOR256+(m_iRounds-1)*m_iBlockSize*m_iSize;
bool bDir;
if(1 == m_iRounds%2)
{
DecryptDirect1(pucBlock, pucXOR256);
bDir = false;
}
else
{
DecryptReverse1(pucBlock, pucXOR256);
bDir = true;
}
pucXOR256-=m_iBlockSize*m_iSize;
for(int i=0; i<m_iRounds-1; i++,pucXOR256-=m_iBlockSize*m_iSize)
{
if(true == bDir)
DecryptDirect(pucBlock, pucXOR256);
else
DecryptReverse(pucBlock, pucXOR256);
bDir = !bDir;
}
return true;
}
Tee Encrypt()
and Decrypt()
methods for larger blocks of text are common and implemented in the IXOR256
abstract class. The size of the large block should be a multiple of the block size. Three operation modes are implemented: Electronic Code Book (ECB), Cipher Block Chaining (CBC) and Cipher Feedback Block (CFB) modes
are implemented. In ECB mode if the same block is encrypted twice with the same key, the resulting
cipher text blocks are
the same. In CBC Mode a cipher text block is obtained by first xoring the plaintext block with the previous
cipher text block,
and encrypting the resulting value. In CFB mode a cipher text block is obtained by encrypting the previous
cipher text block
and xoring the resulting value with the plaintext.
Large block encryption:
bool IXOR256::Encrypt(unsigned char const* pucIn,
unsigned char* pucOut, size_t n, int iMode)
{
if(0==m_iBlockSize || 0==n || n%m_iBlockSize!=0)
return false;
unsigned int ui;
unsigned char const* pucCurrentIn;
unsigned char* pucCurrentOut;
if(iMode == CBC)
{
for(ui=0,pucCurrentIn=pucIn,pucCurrentOut=pucOut;
ui<n/m_iBlockSize; ui++)
{
memcpy(pucCurrentOut, pucCurrentIn, m_iBlockSize);
Xor(pucCurrentOut, m_pucChain);
Encrypt(pucCurrentOut);
memcpy(m_pucChain, pucCurrentOut, m_iBlockSize);
pucCurrentIn += m_iBlockSize;
pucCurrentOut += m_iBlockSize;
}
}
else if(iMode == CFB)
{
for(ui=0,pucCurrentIn=pucIn,pucCurrentOut=pucOut;
ui<n/m_iBlockSize; ui++)
{
Encrypt(m_pucChain);
memcpy(pucCurrentOut, pucCurrentIn, m_iBlockSize);
Xor(pucCurrentOut, m_pucChain);
memcpy(m_pucChain, pucCurrentOut, m_iBlockSize);
pucCurrentIn += m_iBlockSize;
pucCurrentOut += m_iBlockSize;
}
}
else
{
for(ui=0,pucCurrentIn=pucIn,pucCurrentOut=pucOut;
ui<n/m_iBlockSize; ui++)
{
memcpy(pucCurrentOut, pucCurrentIn, m_iBlockSize);
Encrypt(pucCurrentOut);
pucCurrentIn += m_iBlockSize;
pucCurrentOut += m_iBlockSize;
}
}
return true;
}
Large block decryption:
bool IXOR256::Decrypt(unsigned char const* pucIn,
unsigned char* pucOut, size_t n, int iMode)
{
if(0==m_iBlockSize || 0==n || n%m_iBlockSize!=0)
return false;
unsigned int ui;
unsigned char const* pucCurrentIn;
unsigned char* pucCurrentOut;
if(iMode == CBC)
{
for(ui=0,pucCurrentIn=pucIn,pucCurrentOut=pucOut;
ui<n/m_iBlockSize; ui++)
{
memcpy(pucCurrentOut, pucCurrentIn, m_iBlockSize);
memcpy(m_pucTemp, pucCurrentOut, m_iBlockSize);
Decrypt(pucCurrentOut);
Xor(pucCurrentOut, m_pucChain);
memcpy(m_pucChain, m_pucTemp, m_iBlockSize);
pucCurrentIn += m_iBlockSize;
pucCurrentOut += m_iBlockSize;
}
}
else if(iMode == CFB)
{
for(ui=0,pucCurrentIn=pucIn,pucCurrentOut=pucOut;
ui<n/m_iBlockSize; ui++)
{
Encrypt(m_pucChain);
memcpy(pucCurrentOut, pucCurrentIn, m_iBlockSize);
memcpy(m_pucTemp, pucCurrentOut, m_iBlockSize);
Xor(pucCurrentOut, m_pucChain);
memcpy(m_pucChain, m_pucTemp, m_iBlockSize);
pucCurrentIn += m_iBlockSize;
pucCurrentOut += m_iBlockSize;
}
}
else
{
for(ui=0,pucCurrentIn=pucIn,pucCurrentOut=pucOut;
ui<n/m_iBlockSize; ui++)
{
memcpy(pucCurrentOut, pucCurrentIn, m_iBlockSize);
Decrypt(pucCurrentOut);
pucCurrentIn += m_iBlockSize;
pucCurrentOut += m_iBlockSize;
}
}
return true;
}
Some testing code showing how to use the CXOR256
class is presented bellow.
One block testing three rounds:
CXOR256_1 oXOR256_1;
oXOR256_1.Initialize(8, 3, (unsigned char*)"\0\0\0\0\0\0\0\0","xxxxxxxx");
char szData1[] = "aaaaaaaa";
oXOR256_1.Encrypt((unsigned char*)szData1);
oXOR256_1.Decrypt((unsigned char*)szData1);
char szData2[] = "aaaaaaab";
oXOR256_1.Encrypt((unsigned char*)szData2);
oXOR256_1.Decrypt((unsigned char*)szData2);
oXOR256_1.Initialize(8, 3, (unsigned char*)"\0\0\0\0\0\0\0\0","xxxxxxxy");
char szData3[] = "aaaaaaaa";
oXOR256_1.Encrypt((unsigned char*)szData3);
oXOR256_1.Decrypt((unsigned char*)szData3);
char szData4[] = "aaaaaaab";
oXOR256_1.Encrypt((unsigned char*)szData4);
oXOR256_1.Decrypt((unsigned char*)szData4);
Larger block testing three rounds:
CXOR256_1 oXOR256_1;
oXOR256_1.Initialize(8, 3, (unsigned char*)"\0\0\0\0\0\0\0\0","xxxxxyyyyy");
char szDataIn1[25] = "ababababababababcccccccc";
char szDataIn[25];
szDataIn[24] = 0;
char szDataOut[25];
szDataOut[24] = 0;
//Test ECB
strcpy(szDataIn, szDataIn1);
memset(szDataOut, 0, 24);
oXOR256_1.Encrypt((unsigned char*)szDataIn,
(unsigned char*)szDataOut, 24, IXOR256::ECB);
memset(szDataIn, 0, 24);
oXOR256_1.Decrypt((unsigned char*)szDataOut,
(unsigned char*)szDataIn, 24, IXOR256::ECB);
//Test CBC
oXOR256_1.ResetChain();
strcpy(szDataIn, szDataIn1);
memset(szDataOut, 0, 24);
oXOR256_1.Encrypt((unsigned char*)szDataIn,
(unsigned char*)szDataOut, 24, IXOR256::CBC);
memset(szDataIn, 0, 24);
oXOR256_1.ResetChain();
oXOR256_1.Decrypt((unsigned char*)szDataOut,
(unsigned char*)szDataIn, 24, IXOR256::CBC);
//Test CFB
oXOR256_1.ResetChain();
strcpy(szDataIn, szDataIn1);
memset(szDataOut, 0, 24);
oXOR256_1.Encrypt((unsigned char*)szDataIn,
(unsigned char*)szDataOut, 24, IXOR256::CFB);
memset(szDataIn, 0, 24);
oXOR256_1.ResetChain();
oXOR256_1.Decrypt((unsigned char*)szDataOut,
(unsigned char*)szDataIn, 24, IXOR256::CFB);
Final Thoughts
Some analysis of the mathematical complexity of decrypting the XOR256 method is given in my previous article, but it was done for the stream
ciphering case which is equivalent to applying just one round of the block ciphering. I really don't know how could I approach a decryption for many rounds
without increasing the mathematical complexity of the problem to unpractical values. I am interested in any opinions and new ideas about how to attack this
encryption method. In the project XOR256.zip attached to this article a test program of the XOR256 encryption/decryption method is implemented.
All the source files of the presented classes are included in the project.