Introduction
I was doing a lot of small cryptographic operations on a slow CPU, and wasn't happy with the performance. Since I was only working with a few dozen bytes, I suspected that the problem was the .NET cryptographic service instantiation overhead, and that turned out to be the case.
Instead of learning the CyptoAPI and hoping that it would not have the same costs, I ported a C++ implementation by Christophe Devine (included in the zip) that would have no overhead costs.
Using the Code
I have included a demo that uses the DES
class and the .NET crypto classes. It shows that the outputs are identical. There are also a couple benchmark buttons.
Using the class is very simple. First, create the class:
Dim myDes As New DES()
Then call the sub:
myDes.encrypt_des(key8, input, input.Length, output, outputLength)
The mode is CBC (Cipher Block Chaining).
For the demo, copy the output back to the input after encryption to decrypt. The input must be a multiple of 8 (no padding is added for you).
No exception handling is done by the DES
class. If there were no errors, there will be no exception and it will put the number of bytes processed into the outputLength
integer, which would be the same as input.Length
.
There was no use of TransformFinalBlock
for my purpose.
The DES
class also works fine on the Compact Framework.
Demo Screenshots

Points of Interest
As you can see by the benchmarks, in a release configuration, the instantiation benchmark (a lot of small ops) is about 14 times faster than .NET (25 times faster on some systems), but the data benchmark (16 megs) is about 4 times slower, so this class is only useful for a lot of small ops.
VB.NET makes all the math stuff quite unsightly because of the whitespace enforcement and the removing of leading 0s from the hex constants. (Update: Found the option in Visual Studio to remove automatic formatting (Tools > Options > Text Editor > Basic > VB Specific.)
Two main things tripped me up:
- Why wasn't &HFFFFFFFF a valid
UInt32
? Because hex constants are treated as Int32
by default, unless you add UI to the end: &HFFFFFFFFUI. - I needed to convert bytes to
UInt32
before left shifting them into a UInt32
(GET_UINT32
sub). This CUInt
bug cost me about 4 hours.
Since VB.NET doesn't have a concept of #define
, I modified those translated subs to accept another variable as the inherited variable. And because of the different ways of working with arrays, I added an nSK
enumerator to some subs.
This is GPL because Christophe's license is GPL.
Porting Example
People who are interested in porting C++ to VB.NET should also find this code useful. I can't write C++ very well but I can understand it.
Here are a couple porting examples:
#define DES_ROUND(X,Y) \
{ \
T = *SK++ ^ X; \
Y ^= SB8[ (T ) & 0x3F ] ^ \
SB6[ (T >> 8) & 0x3F ] ^ \
SB4[ (T >> 16) & 0x3F ] ^ \
SB2[ (T >> 24) & 0x3F ]; \
\
T = *SK++ ^ ((X << 28) | (X >> 4)); \
Y ^= SB7[ (T ) & 0x3F ] ^ \
SB5[ (T >> 8) & 0x3F ] ^ \
SB3[ (T >> 16) & 0x3F ] ^ \
SB1[ (T >> 24) & 0x3F ]; \
}
Private Sub DES_ROUND(ByRef X As UInt32, ByRef Y As UInt32, _
ByRef T As UInt32, ByRef SK() As UInt32, ByRef nSK As Integer)
nSK += 1
T = SK(nSK) Xor X
Y = Y Xor SB8((T ) And &H3FUI) Xor _
SB6((T >> 8) And &H3FUI) Xor _
SB4((T >> 16) And &H3FUI) Xor _
SB2((T >> 24) And &H3FUI)
nSK += 1
T = SK(nSK) Xor ((X << 28) Or (X >> 4))
Y = Y Xor SB7((T ) And &H3FUI) Xor _
SB5((T >> 8) And &H3FUI) Xor _
SB3((T >> 16) And &H3FUI) Xor _
SB1((T >> 24) And &H3FUI)
End Sub
void des_crypt( uint32 SK[32], uint8 input[8], uint8 output[8] )
{
uint32 X, Y, T;
GET_UINT32( X, input, 0 );
GET_UINT32( Y, input, 4 );
DES_IP( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_ROUND( Y, X ); DES_ROUND( X, Y );
DES_FP( Y, X );
PUT_UINT32( Y, output, 0 );
PUT_UINT32( X, output, 4 );
}
Private Sub des_crypt(ByRef SK() As UInt32, _
ByRef input() As Byte, ByRef output() As Byte)
Dim X, Y, T As UInt32
GET_UINT32(X, input, 0)
GET_UINT32(Y, input, 4)
DES_IP(X, Y, T)
Dim nSK As Integer = -1
DES_ROUND(Y, X, T, SK, nSK) : DES_ROUND(X, Y, T, SK, nSK)
DES_ROUND(Y, X, T, SK, nSK) : DES_ROUND(X, Y, T, SK, nSK)
DES_ROUND(Y, X, T, SK, nSK) : DES_ROUND(X, Y, T, SK, nSK)
DES_ROUND(Y, X, T, SK, nSK) : DES_ROUND(X, Y, T, SK, nSK)
DES_ROUND(Y, X, T, SK, nSK) : DES_ROUND(X, Y, T, SK, nSK)
DES_ROUND(Y, X, T, SK, nSK) : DES_ROUND(X, Y, T, SK, nSK)
DES_ROUND(Y, X, T, SK, nSK) : DES_ROUND(X, Y, T, SK, nSK)
DES_ROUND(Y, X, T, SK, nSK) : DES_ROUND(X, Y, T, SK, nSK)
DES_FP(Y, X, T)
PUT_UINT32(Y, output, 0)
PUT_UINT32(X, output, 4)
End Sub
History
- 2010.01.27
- 2010.01.27
- Prettied up code by adding whitespace and leading 0s
- Added porting example to article