
Introduction
RC6 is an evolutionary improvement of RC5, designed to meet the
requirements of the
Advanced Encryption Standard (AES). Like RC5, RC6 makes essential use
of data-dependent rotations.
New features of RC6 include the use of four working registers instead
of two, and the inclusion
of integer multiplication as an additional primitive operation. The
use of multiplication greatly
increases the diffusion achieved per round, allowing for greater
security, fewer rounds, and increased throughput.
I found an article about it online and fulfilled the algorithm using
C++ for fun. Hope it'd be helpful
to some interested people.
Details of RC6
Like RC5, RC6 is a fully parameterized family of encryption
algorithms. A version of
RC6 is more accurately specified as RC6-w/r/b where the word size is
w bits, encryption consists of
a nonnegative number of rounds r, and b denotes the length of the
encryption key
in bytes. Since the AES submission is targeted at w = 32 and r = 20,
we shall use
RC6 as shorthand to refer to such versions. When any other value of w
or r is intended in the text, the parameter values will be specified
as RC6-w/r. Of particular relevance to the AES effort will be the
versions of RC6 with 16-, 24-, and 32-byte keys.
For all variants, RC6-w/r/b operates on units of four w-bit words
using the following six basic operations. The base-two logarithm of w
will be denoted by lgw.
- a + b: integer addition modulo 2^w
- a - b: integer subtraction modulo 2^w
- a @ b: bitwise exclusive-or of w-bit words
- a * b: integer multiplication modulo 2^w
- a <<< b: rotate the w-bit word a to the left by the amount given
by the least significant lgw bits of b
- a >>> b: rotate the w-bit word a to the right by the amount
given by the least significant lgw bits of b
Note that in the description of RC6 the term "round" is somewhat
analogous to the usual DES-like idea of a round: half of the data is
updated by the other half; and the two are then swapped. In RC5, the
term "half-round" was used to describe this style of action, and an
RC5 round was deemed to consist of two half-rounds. This seems to
have become a potential cause of confusion, and so RC6 reverts to
using the term "round" in the more established way.
To get the detailed algorithm description of RC6-w/r/b. Please read
the article "The RC6 Block Cipher" by
Ronald L. Rivest, M.J.B. Robshaw, R. Sidney and, Y.L. Yin.
Details of Code
In my program, I fulfilled RC6-32/16. Since the integer addition,
subtraction and multiplication don't exceed 2^32 in my program, I
don't let their results modulo 2^32 like the operations described
above. Anyway, the encryption and decryption go well.
I wrapped the bits rotation operations in two functions DWORD
CHexDoc::LeftRotate(DWORD dwVar, DWORD dwOffset)
and
DWORD CHexDoc::RightRotate(DWORD dwVar, DWORD dwOffset)
.
DWORD CHexDoc::LeftRotate(DWORD dwVar, DWORD dwOffset)
{
DWORD temp1, temp2;
temp1 = dwVar >> (W - dwOffset);
temp2 = dwVar << dwOffset;
temp2 = temp2 | temp1;
return temp2;
}
DWORD CHexDoc::RightRotate(DWORD dwVar, DWORD dwOffset)
{
DWORD temp1, temp2;
temp1 = dwVar << (W - dwOffset);
temp2 = dwVar >> dwOffset;
temp2 = temp2 | temp1;
return temp2;
}
The key generation part is like
void CHexDoc::KeyGen(DWORD dwKey)
{
DWORD P32 = 0xB7E15163;
DWORD Q32 = 0x9E3779B9;
DWORD i, A, B;
DWORD dwByteOne, dwByteTwo, dwByteThree, dwByteFour;
dwByteOne = dwKey >> 24;
dwByteTwo = dwKey >> 8;
dwByteTwo = dwByteTwo & 0x0010;
dwByteThree = dwKey << 8;
dwByteThree = dwByteThree & 0x0100;
dwByteFour = dwKey << 24;
dwKey = dwByteOne | dwByteTwo | dwByteThree
| dwByteFour;
m_dwS[0] = P32;
for(i = 1; i < 2 * R + 4; i++)
m_dwS[i] = m_dwS[i - 1] + Q32;
i = A = B = 0;
int v = 3 * max(1, 2 * R + 4);
for(int s = 1; s <= v; s++)
{
A = m_dwS[i] = LeftRotate(m_dwS[i] + A + B,
OffsetAmount(3));
B = dwKey = LeftRotate(dwKey + A + B,
OffsetAmount(A + B));
i = (i + 1) % (2 * R + 4);
}
}
Finally, the core parts of encryption and decryption are as following:
void CHexDoc::EncodeFile()
{
DWORD* pdwTemp;
for(UINT i = 0; i < m_nDocLength; i += 16)
{
pdwTemp = (DWORD*)&m_pFileData[i];
pdwTemp[0] = (pdwTemp[0] - m_dwS[2 * R + 2]);
pdwTemp[2] = (pdwTemp[2] - m_dwS[2 * R + 3]);
for(int j = R; j >= 1; j--)
{
DWORD temp = pdwTemp[3];
pdwTemp[3] = pdwTemp[2];
pdwTemp[2] = pdwTemp[1];
pdwTemp[1] = pdwTemp[0];
pdwTemp[0] = temp;
DWORD t =
LeftRotate((pdwTemp[1] * (2 * pdwTemp[1] + 1)),
OffsetAmount((DWORD)(log((double)W)/log(2.0))));
DWORD u =
LeftRotate((pdwTemp[3] * (2 * pdwTemp[3] + 1)),
OffsetAmount((DWORD)(log((double)W)/log(2.0))));
pdwTemp[0] =
(RightRotate((pdwTemp[0] - m_dwS[2 * j]),
OffsetAmount(u))) ^ t;
pdwTemp[2] =
(RightRotate((pdwTemp[2] - m_dwS[2 * j + 1]),
OffsetAmount(t))) ^ u;
}
pdwTemp[1] = (pdwTemp[1] - m_dwS[0]);
pdwTemp[3] = (pdwTemp[3] - m_dwS[1]);
}
pdwTemp = NULL;
SetModifiedFlag(TRUE);
POSITION pos = GetFirstViewPosition();
while(pos != NULL)
{
CView* pView = GetNextView(pos);
pView->RedrawWindow();
}
}
void CHexDoc::DecodeFile()
{
DWORD* pdwTemp;
for(UINT i = 0; i < m_nDocLength; i += 16)
{
pdwTemp = (DWORD*)&m_pFileData[i];
pdwTemp[1] = (pdwTemp[1] + m_dwS[0]);
pdwTemp[3] = (pdwTemp[3] + m_dwS[1]);
for(int j = 1; j <= R; j++)
{
DWORD t =
LeftRotate((pdwTemp[1] * (2 * pdwTemp[1] + 1)),
OffsetAmount((DWORD)(log((double)W)/log(2.0))));
DWORD u =
LeftRotate((pdwTemp[3] * (2 * pdwTemp[3] + 1)),
OffsetAmount((DWORD)(log((double)W)/log(2.0))));
pdwTemp[0] =
(LeftRotate(pdwTemp[0] ^ t, OffsetAmount(u)) +
m_dwS[2 * j]);
pdwTemp[2] =
(LeftRotate(pdwTemp[2] ^ u, OffsetAmount(t)) +
m_dwS[2 * j + 1]);
DWORD temp = pdwTemp[0];
pdwTemp[0] = pdwTemp[1];
pdwTemp[1] = pdwTemp[2];
pdwTemp[2] = pdwTemp[3];
pdwTemp[3] = temp;
}
pdwTemp[0] = (pdwTemp[0] + m_dwS[2 * R + 2]);
pdwTemp[2] = (pdwTemp[2] + m_dwS[2 * R + 3]);
}
pdwTemp = NULL;
SetModifiedFlag(TRUE);
POSITION pos = GetFirstViewPosition();
while(pos != NULL)
{
CView* pView = GetNextView(pos);
pView->RedrawWindow();
}
}
Conclusion
In the view window, I showed the Hex and Char content of the loaded
file and their addresses. You can see the changes every time you
encrypt/decrypt it. Thanks!