singrdk/base/Libraries/Crypto/MD4.cs

453 lines
16 KiB
C#

/*
This file contains an implementation of the MD4 hash algorithm.
The implementation was derived from the example source code that
is provided in RFC 1320 - "The MD4 Message-Digest Algorithm".
The code has been ported from C to C# by Arlie Davis (arlied), June 2006.
The original copyright is preserved here.
Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
rights reserved.
License to copy and use this software is granted provided that it
is identified as the "RSA Data Security, Inc. MD4 Message-Digest
Algorithm" in all material mentioning or referencing this software
or this function.
License is also granted to make and use derivative works provided
that such works are identified as "derived from the RSA Data
Security, Inc. MD4 Message-Digest Algorithm" in all material
mentioning or referencing the derived work.
RSA Data Security, Inc. makes no representations concerning either
the merchantability of this software or the suitability of this
software for any particular purpose. It is provided "as is"
without express or implied warranty of any kind.
These notices must be retained in any copies of any part of this
documentation and/or software.
*/
using System;
using System.Diagnostics;
using System.Text;
namespace System.Security.Cryptography
{
/// <summary>
/// This class implements the MD4 message digest algorithm.
/// The implementation is a C# port of the original RSA C implementation that is
/// provided in RFC 1320.
/// </summary>
public class MD4Context
{
public MD4Context()
{
Reset();
}
public const int DigestLength = 16;
/// <summary>
/// This field is "true" if GetDigest has been called.
/// It cannot be called again, unless Reset is called.
/// </summary>
bool _done;
public void Clear()
{
_state0 = 0;
_state1 = 0;
_state2 = 0;
_state3 = 0;
_count = 0;
for (int i = 0; i < 64; i++)
_buffer[i] = 0;
}
/// <summary>
/// This method can be used to reset the state of the MD4 context. After returning, the
/// state of the MD4Context is equivalent to its state immediately after the constructor
/// finished.
/// </summary>
public void Reset()
{
// Load magic initialization constants.
_count = 0;
_state0 = 0x67452301;
_state1 = 0xefcdab89;
_state2 = 0x98badcfe;
_state3 = 0x10325476;
_done = false;
}
/*
MD4 basic transformation. Transforms state based on block.
*/
void Transform(byte[]/*!*/ block, int offset)
{
if (offset < 0)
throw new ArgumentException("offset cannot be negative.");
Decode(_block, 0, block, offset, 64);
Transform(_block);
// Zeroize sensitive information.
// MD4_memset ((POINTER)x, 0, sizeof (x));
}
#region Constants for MD4Transform routine.
const int S11 = 3;
const int S12 = 7;
const int S13 = 11;
const int S14 = 19;
const int S21 = 3;
const int S22 = 5;
const int S23 = 9;
const int S24 = 13;
const int S31 = 3;
const int S32 = 9;
const int S33 = 11;
const int S34 = 15;
#endregion
/// <summary>
/// This routine transforms the current MD4 context, using a block of input.
/// The input is 16 words, where each word is 32 unsigned bits. This method
/// is the core of the MD4 algorithm.
/// </summary>
/// <param name="x">The message data to use to transform the context.</param>
void Transform(uint[] x)
{
uint a = _state0;
uint b = _state1;
uint c = _state2;
uint d = _state3;
/* Round 1 */
a = FF(a, b, c, d, x[00], S11); /* 1 */
d = FF(d, a, b, c, x[01], S12); /* 2 */
c = FF(c, d, a, b, x[02], S13); /* 3 */
b = FF(b, c, d, a, x[03], S14); /* 4 */
a = FF(a, b, c, d, x[04], S11); /* 5 */
d = FF(d, a, b, c, x[05], S12); /* 6 */
c = FF(c, d, a, b, x[06], S13); /* 7 */
b = FF(b, c, d, a, x[07], S14); /* 8 */
a = FF(a, b, c, d, x[08], S11); /* 9 */
d = FF(d, a, b, c, x[09], S12); /* 10 */
c = FF(c, d, a, b, x[10], S13); /* 11 */
b = FF(b, c, d, a, x[11], S14); /* 12 */
a = FF(a, b, c, d, x[12], S11); /* 13 */
d = FF(d, a, b, c, x[13], S12); /* 14 */
c = FF(c, d, a, b, x[14], S13); /* 15 */
b = FF(b, c, d, a, x[15], S14); /* 16 */
/* Round 2 */
a = GG(a, b, c, d, x[00], S21); /* 17 */
d = GG(d, a, b, c, x[04], S22); /* 18 */
c = GG(c, d, a, b, x[08], S23); /* 19 */
b = GG(b, c, d, a, x[12], S24); /* 20 */
a = GG(a, b, c, d, x[01], S21); /* 21 */
d = GG(d, a, b, c, x[05], S22); /* 22 */
c = GG(c, d, a, b, x[09], S23); /* 23 */
b = GG(b, c, d, a, x[13], S24); /* 24 */
a = GG(a, b, c, d, x[02], S21); /* 25 */
d = GG(d, a, b, c, x[06], S22); /* 26 */
c = GG(c, d, a, b, x[10], S23); /* 27 */
b = GG(b, c, d, a, x[14], S24); /* 28 */
a = GG(a, b, c, d, x[03], S21); /* 29 */
d = GG(d, a, b, c, x[07], S22); /* 30 */
c = GG(c, d, a, b, x[11], S23); /* 31 */
b = GG(b, c, d, a, x[15], S24); /* 32 */
/* Round 3 */
a = HH(a, b, c, d, x[00], S31); /* 33 */
d = HH(d, a, b, c, x[08], S32); /* 34 */
c = HH(c, d, a, b, x[04], S33); /* 35 */
b = HH(b, c, d, a, x[12], S34); /* 36 */
a = HH(a, b, c, d, x[02], S31); /* 37 */
d = HH(d, a, b, c, x[10], S32); /* 38 */
c = HH(c, d, a, b, x[06], S33); /* 39 */
b = HH(b, c, d, a, x[14], S34); /* 40 */
a = HH(a, b, c, d, x[01], S31); /* 41 */
d = HH(d, a, b, c, x[09], S32); /* 42 */
c = HH(c, d, a, b, x[05], S33); /* 43 */
b = HH(b, c, d, a, x[13], S34); /* 44 */
a = HH(a, b, c, d, x[03], S31); /* 45 */
d = HH(d, a, b, c, x[11], S32); /* 46 */
c = HH(c, d, a, b, x[07], S33); /* 47 */
b = HH(b, c, d, a, x[15], S34); /* 48 */
_state0 = unchecked(_state0 + a);
_state1 = unchecked(_state1 + b);
_state2 = unchecked(_state2 + c);
_state3 = unchecked(_state3 + d);
}
public const int BytesPerTransform = 64;
public const int WordsPerTransform = 16;
uint _state0; // state A
uint _state1; // state B
uint _state2; // state C
uint _state3; // state D
Int64 _count; // number of bits, modulo 2^64
/// <summary>
/// This buffer holds data that was submitted using the Update method, but which
/// was too short to complete a full transform block (64 bytes).
/// </summary>
readonly byte[]/*!*/ _buffer = new byte[BytesPerTransform];
/// <summary>
/// This buffer contains the current block (of message data) that is being transformed.
/// </summary>
readonly uint[]/*!*/ _block = new uint[WordsPerTransform];
static MD4Context()
{
PADDING = new byte[BytesPerTransform];
Array.Clear(PADDING, 0, 64);
PADDING[0] = 0x80;
}
static readonly byte[]/*!*/ PADDING;
/*
{
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
*/
/* F, G and H are basic MD4 functions.
*/
static uint F(uint x, uint y, uint z) { return (x & y) | ((~x) & z); }
static uint G(uint x, uint y, uint z) { return (x & y) | (x & z) | (y & z); }
static uint H(uint x, uint y, uint z) { return x ^ y ^ z; }
/* ROTATE_LEFT rotates x left n bits.
*/
static uint RotateLeft(uint x, int n)
{
return (x << n) | (x >> (32 - n));
}
/* FF, GG and HH are transformations for rounds 1, 2 and 3 */
/* Rotation is separate from addition to prevent recomputation */
/* (Or at least it used to be, when these were C macros. */
static uint FF(uint a, uint b, uint c, uint d, uint x, int s)
{
a = unchecked(a + F(b, c, d) + x);
return RotateLeft(a, s);
}
static uint GG(uint a, uint b, uint c, uint d, uint x, int s)
{
a = unchecked(a + G(b, c, d) + x + (uint)0x5a827999);
return RotateLeft(a, s);
}
static uint HH(uint a, uint b, uint c, uint d, uint x, int s)
{
a = unchecked(a + H(b, c, d) + x + (uint)0x6ed9eba1);
return RotateLeft(a, s);
}
/* Decodes input (unsigned char) into output (uint). Assumes len is
a multiple of 4.
*/
/// <summary>
///
/// </summary>
/// <param name="output"></param>
/// <param name="outputoffset"></param>
/// <param name="input"></param>
/// <param name="inputoffset"></param>
/// <param name="length">number of BYTES to transfer</param>
static void Decode(uint[]/*!*/ output, int outputoffset, byte[]/*!*/ input, int inputoffset, int length)
{
Debug.Assert(length % 4 == 0);
int outpos = outputoffset;
for (int j = 0; j < length; j += 4)
{
uint value = ((uint)input[inputoffset + j])
| (((uint)input[inputoffset + j + 1]) << 8)
| (((uint)input[inputoffset + j + 2]) << 16)
| (((uint)input[inputoffset + j + 3]) << 24);
output[outpos] = value;
outpos++;
}
}
static void EncodeLe(uint value, byte[]/*!*/ output, int pos)
{
output[pos] = (byte)(value & 0xff);
output[pos + 1] = (byte)((value >> 8) & 0xff);
output[pos + 2] = (byte)((value >> 16) & 0xff);
output[pos + 3] = (byte)((value >> 24) & 0xff);
}
/// <summary>
/// MD4 block update operation. Continues an MD4 message-digest operation,
/// processing another message block, and updating the context.
/// </summary>
/// <param name="input">The buffer containing data to process.</param>
/// <param name="offset">The offset within the buffer where the data begins.</param>
/// <param name="length">The length of the data.</param>
public void Update(byte[]/*!*/ input, int offset, int length)
{
if (_done)
throw new InvalidOperationException("The hash context has been closed (GetDigest has been called). It cannot be reused until Reset is called.");
if (length == 0)
return;
/* Compute number of bytes mod 64 */
int index = (int)((_count >> 3) & 0x3F);
_count += length << 3;
int partLen = 64 - index;
/* Transform as many times as possible.
*/
int i;
if (length >= partLen)
{
// MD4_memcpy((POINTER)&buffer_pinned[index], (POINTER)&input[offset], partLen);
Array.Copy(input, offset, _buffer, index, partLen);
Transform(_buffer, 0);
for (i = partLen; i + 63 < length; i += 64)
Transform(input, offset + i);
index = 0;
}
else
i = 0;
/* Buffer remaining input */
// MD4_memcpy((POINTER)&_buffer[index], (POINTER)&input[i], inputLen-i);
// MD4_memcpy((POINTER)&buffer_pinned[index], (POINTER)&input[offset + i], inputLen-i);
Array.Copy(input, offset + i, _buffer, index, length - i);
}
public static MD4Digest GetDigest(byte[]/*!*/ buffer)
{
return GetDigest(buffer, 0, buffer.Length);
}
public static MD4Digest GetDigest(byte[]/*!*/ buffer, int offset, int length)
{
MD4Context context = new MD4Context();
context.Update(buffer, offset, length);
return context.GetDigest();
}
/* MD4 finalization. Ends an MD4 message-digest operation, writing the
the message digest and zeroizing the context.
*/
public MD4Digest GetDigest()
{
if (_done)
throw new InvalidOperationException("GetDigest cannot be called twice for a single hash sequence. Call Reset() to reset the context.");
byte[]/*!*/ bits = new byte[8];
/* Save number of bits */
EncodeLe((uint)(_count & 0xffffffffu), bits, 0);
EncodeLe((uint)(_count >> 32), bits, 4);
// Pad out to 56 mod 64.
int index = (int)((_count >> 3) & 0x3f);
int padLen = (index < 56) ? (56 - index) : (120 - index);
Update(PADDING, 0, padLen);
// Append length (before padding)
Update(bits, 0, 8);
// Store state in digest
MD4Digest digest;
digest.state0 = _state0;
digest.state1 = _state1;
digest.state2 = _state2;
digest.state3 = _state3;
Clear();
_done = true;
return digest;
}
}
public struct MD4Digest
{
public uint state0;
public uint state1;
public uint state2;
public uint state3;
static void PackLe(byte[]/*!*/ dest, int offset, uint value)
{
dest[offset + 0] = (byte)((value) & 0xff);
dest[offset + 1] = (byte)((value >> 8) & 0xff);
dest[offset + 2] = (byte)((value >> 16) & 0xff);
dest[offset + 3] = (byte)((value >> 24) & 0xff);
}
public const int DigestLength = 16;
public byte[]/*!*/ ToArray()
{
byte[] result = new byte[DigestLength];
ToArray(result);
return result;
}
public void ToArray(byte[]/*!*/ result)
{
if (result.Length < 0x10)
throw new ArgumentException("Input array is too short.");
PackLe(result, 0, state0);
PackLe(result, 4, state1);
PackLe(result, 8, state2);
PackLe(result, 12, state3);
}
public static implicit operator byte[]/*!*/(MD4Digest digest)
{
return digest.ToArray();
}
override public string/*!*/ ToString()
{
string hex = "0123456789abcdef";
byte[] arr = ToArray();
StringBuilder buffer = new StringBuilder(DigestLength * 2);
for (int i = 0; i < 0x10; i++)
{
byte b = arr[i];
buffer.Append(hex[b >> 4]);
buffer.Append(hex[b & 0xf]);
}
return buffer.ToString();
}
}
}