📄 helix.cpp
字号:
/*
Module : Helix.CPP
Purpose: C++ implementation for the Helix Encryption and Authentication algorithm as presented in a November 2003
Dr. Dobb's Journal article by Niels Ferguson and Bruce Schneier. For further information please refer to
the article and / or the web site for it at http://www.macfergus.com/helix.
Features of the algorithm / Implementation
* Encrypts / Decrypts and produces a hash in one operation.
* Fast. Using just this C++ code on an AMD Athlon XP 2700+ and Visual Studio 6, I was able to
get an encryption speed of 160+ MB / second with just the C++ code below. This corresponds to
c. 11 clock ticks per byte. An even faster implementation could be achieved by re-implementing the "Block"
function in assembler and / or recompiling in VC.NET 2003.
* The test program verifies the encryption and decryption using the supplied test vectors from the Helix web
site.
* Encryption and Decryption can be done in place. To achieve this simply set the OUT parameter in the Encrypt
or Decrypt functions to point to the buffer which is being encrypted / decrypted. For an example of this
check out the last test in the TestHelix.cpp module.
Created: PJN / 29-11-2003
History: PJN / 30-11-2003 Following an email update from Niels Ferguson, the following changes were made:
1. Removed the need for the doBlk function
2. Made the Block function inline.
3. Removed the unneeded code "m_i8 >> 31"
4. Removed the unneeded local variable tag in the Finish method.
5. Removed the switch statement in preference for a lookup array in Decrypt.
6. plaintext is now not released from Decrypt if the MAC's do not match up.
7. Optimized the key method CHelix::Block by using register local variables. This now
gets the throughput on my machine up to 191 MB / second
PJN / 01-12-2003 1. Optimized the for loop in Finish. Now it is only called 3 times instead of 4.
PJN / 01-12-2003 1. Further optimizations as suggested by Serhiy Pavlov boost the speed to 208 MB / second
on my home machine. This corresponds to a speed of 10.4 clock ticks per byte!,
2. Also produced a DLL version of the algorithm, so that client applications which do
not or cannot (e.g. Visual Basic) muck around with the C++ source code can use the code.
PJN / 18-06-2004 1. Fixed a bug in the CHelix constructor. Thanks to Richard Gyger for reporting this
problem.
PJN / 17-09-2004 1. Fixed a number of warnings when the code is compiled in VC.Net 2003
PJN / 28-10-2004 1. Fixed a problem in the decryption where the Helix_Decrypt_Masks bit masks were incorrectly
defined. Thanks to Dwain Skinner for reporting and providing a fix for this problem.
2. Removed the declaration of #pragma intrinsic(_rotl) as recent Platform SDK's already
have this defined.
3. Sample app provided with download, now uses console I/O for reporting its results.
Copyright (c) 2003 - 2004 by PJ Naughter. (Web: www.naughter.com, Email: pjna@naughter.com)
All rights reserved.
Copyright / Usage Details:
You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise)
when your product is released in binary form. You are allowed to modify the source code in any way you want
except you cannot modify the copyright details at the top of each module. If you want to distribute source
code with your application, then you are only allowed to distribute versions released by the author. This is
to maintain a single distribution point for the source code.
*/
///////////////////////////////// Includes //////////////////////////////////
#include "stdafx.h"
#include "Helix.h"
//////////////// Macros / Defines ////////////////////////////////////
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
DWORD g_Helix_Decrypt_Masks[] = { 0x0, 0xFF, 0xFFFF, 0xFFFFFF };
//////////////// Implementation //////////////////////////////////////
CHelixNonce::CHelixNonce()
{
memset(&m_Data, 0, sizeof(m_Data));
}
CHelixMAC::CHelixMAC()
{
memset(&m_Data, 0, sizeof(m_Data));
}
operator==(const CHelixMAC& mac1, const CHelixMAC& mac2)
{
BOOL bSuccess = TRUE;
for (int i=0; (i<16) && bSuccess; i++)
bSuccess = (mac1.m_Data[i] == mac2.m_Data[i]);
return bSuccess;
}
CHelix::CHelix()
{
memset(&m_Z, 0, sizeof(m_Z));
memset(&m_K, 0, sizeof(m_K));
memset(&m_X1, 0, sizeof(m_X1));
m_dwKLen = 0;
m_i8 = 0;
//By default set the key to be an empty string
SetKey(NULL, 0);
}
BOOL CHelix::SetKey(const BYTE* pbyKey, DWORD dwKeyLength)
{
//Validate our parameters
if ((pbyKey != NULL) && (dwKeyLength > 32))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
if ((pbyKey == NULL) && (dwKeyLength))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
//Hive away in a member variable
m_dwKLen = dwKeyLength;
//Form the key (represented as a DWORD array internally)
BYTE key[32];
memset(&key, 0, sizeof(key));
memcpy(&key, pbyKey, m_dwKLen);
DWORD* pdwKey = (DWORD*) &key;
int i;
for (i=0; i<8; i++)
m_K[i] = pdwKey[i];
//Perform the key mixing
for (i=0; i<8; i++)
{
m_Z[0] = m_K[0];
m_Z[1] = m_K[1];
m_Z[2] = m_K[2];
m_Z[3] = m_K[3];
m_Z[4] = m_dwKLen + 64;
Block(0, 0, 0);
DWORD K0 = m_K[0];
DWORD K1 = m_K[1];
DWORD K2 = m_K[2];
DWORD K3 = m_K[3];
m_K[0] = m_K[4] ^ m_Z[0];
m_K[1] = m_K[5] ^ m_Z[1];
m_K[2] = m_K[6] ^ m_Z[2];
m_K[3] = m_K[7] ^ m_Z[3];
m_K[4] = K0;
m_K[5] = K1;
m_K[6] = K2;
m_K[7] = K3;
}
return TRUE;
}
void CHelix::Start(const CHelixNonce& nonce)
{
//convert nonce to a array of 4 DWORD's
DWORD N1[8];
DWORD* pdwN1 = (DWORD*) &nonce.m_Data;
int i;
for (i=0; i<4; i++)
N1[i] = pdwN1[i];
//Extend the nonce to an array of 8 DWORD's
for (i=0; i<4; i++)
N1[i+4] = (i - N1[i]);
//Make array of X1 values
for (i=0; i<8; i++)
m_X1[i] = m_K[(i+4)%8] + N1[i] + (((i%4)==1)*4*m_dwKLen);
//Initialize state and run first 8 blocks for nonce mixing.
m_Z[0] = m_K[3] ^ N1[0];
m_Z[1] = m_K[4] ^ N1[1];
m_Z[2] = m_K[5] ^ N1[2];
m_Z[3] = m_K[6] ^ N1[3];
m_Z[4] = m_K[7];
m_i8 = 0;
int locali;
for (i=0; i<8; i++)
{
locali = m_i8 & 0x7;
Block(m_K[locali], 0, m_X1[locali] + m_i8);
++m_i8;
}
}
void CHelix::Finish(DWORD lnm4, CHelixMAC& Mac)
{
//Tweak the internal state as specified
m_Z[0] ^= 0x912d94f1;
int locali;
//Apply 8 block function with len(P) mod 4 as plaintext
int i;
for (i=0; i<8; i++)
{
locali = m_i8 & 0x7;
Block(m_K[locali], lnm4, m_X1[locali] + m_i8);
++m_i8;
}
//And generate the tag
DWORD* pdwMac = (DWORD*) &Mac.m_Data;
for (i=0; i<3; i++)
{
pdwMac[i] = m_Z[0];
locali = m_i8 & 0x7;
Block(m_K[locali], lnm4, m_X1[locali] + m_i8);
++m_i8;
}
pdwMac[3] = m_Z[0];
//Cleanup
memset(&m_Z, 0, sizeof(m_Z));
memset(&m_X1, 0, sizeof(m_X1));
m_i8 = 0;
}
void CHelix::Block(DWORD X0, DWORD P, DWORD X1)
{
//Prod the compiler to use the registers by using local variables with the "register" keyword
register DWORD Z0 = m_Z[0];
register DWORD Z1 = m_Z[1];
register DWORD Z2 = m_Z[2];
register DWORD Z3 = m_Z[3];
register DWORD Z4 = m_Z[4];
Z0 += Z3;
Z3 = _rotl(Z3, 15);
Z1 += Z4;
Z4 = _rotl(Z4, 25);
Z2 ^= Z0;
Z0 = _rotl(Z0, 9);
Z3 ^= Z1;
Z1 = _rotl(Z1, 10);
Z4 += Z2;
Z2 = _rotl(Z2, 17);
Z0 ^= (Z3 + X0);
Z3 = _rotl(Z3, 30);
Z1 ^= Z4;
Z4 = _rotl(Z4, 13);
Z2 += Z0;
Z0 = _rotl(Z0, 20);
Z3 += Z1;
Z1 = _rotl(Z1, 11);
Z4 ^= Z2;
Z2 = _rotl(Z2, 5);
Z0 += (Z3 ^ P);
Z3 = _rotl(Z3, 15);
Z1 += Z4;
Z4 = _rotl(Z4, 25);
Z2 ^= Z0;
Z0 = _rotl(Z0, 9);
Z3 ^= Z1;
Z1 = _rotl(Z1, 10);
Z4 += Z2;
Z2 = _rotl(Z2, 17);
Z0 ^= (Z3 + X1);
Z3 = _rotl(Z3, 30);
Z1 ^= Z4;
Z4 = _rotl(Z4, 13);
Z2 += Z0;
Z0 = _rotl(Z0, 20);
Z3 += Z1;
Z1 = _rotl(Z1, 11);
Z4 ^= Z2;
Z2 = _rotl(Z2, 5);
//Move the local variable values back to the member variables
m_Z[0] = Z0;
m_Z[1] = Z1;
m_Z[2] = Z2;
m_Z[3] = Z3;
m_Z[4] = Z4;
}
BOOL CHelix::Encrypt(const BYTE* pbyPlainText, DWORD dwPlainTextSize, const CHelixNonce& nonce, BYTE* pbyCipherText, CHelixMAC& mac)
{
//Validate our parameters
if ((pbyPlainText == NULL) || (dwPlainTextSize == 0) || (pbyCipherText == NULL))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
DWORD dwLoops = dwPlainTextSize / 4;
DWORD dwExtraBytes = (dwPlainTextSize % 4);
DWORD* pdwPlainText = (DWORD*) pbyPlainText;
DWORD* pdwCipherText = (DWORD*) pbyCipherText;
//Encrypt the plaintext "pByPlainText" with the nonce "nonce".
//Returns a ciphertext "pByCipherText" and the authentication tag "Mac"
Start(nonce);
DWORD dwPlainText;
int locali;
DWORD i;
for (i=0; i<dwLoops; i++)
{
dwPlainText = pdwPlainText[i];
pdwCipherText[i] = dwPlainText ^ m_Z[0];
locali = m_i8 & 0x7;
Block(m_K[locali], dwPlainText, m_X1[locali] + m_i8);
++m_i8;
}
if (dwExtraBytes)
{
dwPlainText = 0;
BYTE* pbyTempPlainText = (BYTE*) &dwPlainText;
BYTE* pbyExtraPlainText = (BYTE*) &pdwPlainText[dwLoops];
for (i=0; i<dwExtraBytes; i++)
pbyTempPlainText[i] = pbyExtraPlainText[i];
DWORD dwTempCipherText = dwPlainText ^ m_Z[0];
locali = m_i8 & 0x7;
Block(m_K[locali], dwPlainText, m_X1[locali] + m_i8);
++m_i8;
BYTE* pbyTempCipherText = (BYTE*) &dwTempCipherText;
BYTE* pbyExtraCipherText = (BYTE*) &pdwCipherText[dwLoops];
for (i=0; i<dwExtraBytes; i++)
pbyExtraCipherText[i] = pbyTempCipherText[i];
}
//Compute the authentication tag
Finish(dwPlainTextSize%4, mac);
return TRUE;
}
BOOL CHelix::Decrypt(const BYTE* pbyCipherText, DWORD dwCipherTextSize, const CHelixNonce& nonce, const CHelixMAC& mac, BYTE* pbyPlainText)
{
//Validate our parameters
if ((pbyCipherText == NULL) || (dwCipherTextSize == 0) || (pbyPlainText == NULL))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
DWORD dwLoops = dwCipherTextSize / 4;
DWORD dwExtraBytes = (dwCipherTextSize % 4);
DWORD* pdwPlainText = (DWORD*) pbyPlainText;
DWORD* pdwCipherText = (DWORD*) pbyCipherText;
//Decrypt the plaintext "pByCipherText" with the nonce "nonce".
//Returns FALSE if the Authentication fails, or TRUE if authentication succeeds and the plaintext is returned in "pbyPlainText"
Start(nonce);
int locali;
DWORD i;
for (i=0; i<dwLoops; i++)
{
pdwPlainText[i] = pdwCipherText[i] ^ m_Z[0];
locali = m_i8 & 0x7;
Block(m_K[locali], pdwPlainText[i], m_X1[locali] + m_i8);
++m_i8;
}
if (dwExtraBytes)
{
DWORD dwTempCipherText = 0;
BYTE* pbyTempCipherText = (BYTE*) &dwTempCipherText;
BYTE* pbyExtraCipherText = (BYTE*) &pdwCipherText[dwLoops];
for (i=0; i<dwExtraBytes; i++)
pbyTempCipherText[i] = pbyExtraCipherText[i];
DWORD dwTempPlainText = (dwTempCipherText ^ m_Z[0]) & g_Helix_Decrypt_Masks[dwExtraBytes];
locali = m_i8 & 0x7;
Block(m_K[locali], dwTempPlainText, m_X1[locali] + m_i8);
++m_i8;
BYTE* pbyTempPlainText = (BYTE*) &dwTempPlainText;
BYTE* pbyExtraPlainText = (BYTE*) &pdwPlainText[dwLoops];
for (i=0; i<dwExtraBytes; i++)
pbyExtraPlainText[i] = pbyTempPlainText[i];
}
//Compute the authentication tag
CHelixMAC CalculatedMac;
Finish(dwCipherTextSize%4, CalculatedMac);
//And check to see if it is the same as that passed to us
BOOL bSuccess = (CalculatedMac == mac);
//Do not release the plain text if the MAC's do not match up./
//Do do this by setting all its contents to 0
if (!bSuccess)
memset(pbyPlainText, 0, dwCipherTextSize);
return bSuccess;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -