Skip to content

Commit 0677638

Browse files
authored
Add MD2 Hashing (TheAlgorithms#415)
1 parent 15748ea commit 0677638

File tree

3 files changed

+259
-1
lines changed

3 files changed

+259
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Text;
3+
using Algorithms.Crypto.Digests;
4+
using FluentAssertions;
5+
using NUnit.Framework;
6+
7+
namespace Algorithms.Tests.Crypto.Digesters;
8+
9+
[NonParallelizable]
10+
public class Md2DigestTests
11+
{
12+
private readonly Md2Digest digest = new Md2Digest();
13+
14+
[TestCase("", "8350E5A3E24C153DF2275C9F80692773")]
15+
[TestCase("a", "32EC01EC4A6DAC72C0AB96FB34C0B5D1")]
16+
[TestCase("abc", "DA853B0D3F88D99B30283A69E6DED6BB")]
17+
[TestCase("message digest", "AB4F496BFB2A530B219FF33031FE06B0")]
18+
[TestCase("abcdefghijklmnopqrstuvwxyz", "4E8DDFF3650292AB5A4108C3AA47940B")]
19+
[TestCase("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "DA33DEF2A42DF13975352846C30338CD")]
20+
[TestCase("12345678901234567890123456789012345678901234567890123456789012345678901234567890", "D5976F79D83D3A0DC9806C3C66F3EFD8")]
21+
[TestCase("123456789012345678901234567890123456789012345678901234567890123456789012345678901", "6FAD0685C4A3D03E3D352D12BBAD6BE3")]
22+
public void Digest_ReturnsCorrectValue(string input, string expected)
23+
{
24+
var inputBytes = Encoding.ASCII.GetBytes(input);
25+
26+
var result = digest.Digest(inputBytes);
27+
28+
var output = Convert.ToHexString(result);
29+
30+
output.Should().Be(expected);
31+
}
32+
}
+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
using System;
2+
3+
namespace Algorithms.Crypto.Digests;
4+
5+
/// <summary>
6+
/// MD2 is a cryptographic hash function that takes an input message and produces a 128-bit output, also called a message
7+
/// digest or a hash.
8+
/// <para>
9+
/// A hash function has two main properties: it is easy to compute the hash from the input, but it is hard to find the
10+
/// input from the hash or to find two different inputs that produce the same hash.
11+
/// </para>
12+
/// <para>
13+
/// MD2 works by first padding the input message to a multiple of 16 bytes and adding a 16-byte checksum to it. Then, it
14+
/// uses a 48-byte auxiliary block and a 256-byte S-table (a fixed permutation of the numbers 0 to 255) to process the
15+
/// message in 16-byte blocks.
16+
/// </para>
17+
/// <para>
18+
/// For each block, it updates the auxiliary block by XORing it with the message block and then applying the S-table 18
19+
/// times. After all blocks are processed, the first 16 bytes of the auxiliary block become the hash value.
20+
/// </para>
21+
/// </summary>
22+
public class Md2Digest
23+
{
24+
// The S-table is a set of constants generated by shuffling the integers 0 through 255 using a variant of
25+
// Durstenfeld's algorithm with a pseudorandom number generator based on decimal digits of pi.
26+
private static readonly byte[] STable =
27+
{
28+
41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, 19,
29+
98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, 76, 130, 202,
30+
30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, 138, 23, 229, 18,
31+
190, 78, 196, 214, 218, 158, 222, 73, 160, 251, 245, 142, 187, 47, 238, 122,
32+
169, 104, 121, 145, 21, 178, 7, 63, 148, 194, 16, 137, 11, 34, 95, 33,
33+
128, 127, 93, 154, 90, 144, 50, 39, 53, 62, 204, 231, 191, 247, 151, 3,
34+
255, 25, 48, 179, 72, 165, 181, 209, 215, 94, 146, 42, 172, 86, 170, 198,
35+
79, 184, 56, 210, 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241,
36+
69, 157, 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2,
37+
27, 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15,
38+
85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, 234, 38,
39+
44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, 129, 77, 82,
40+
106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, 8, 12, 189, 177, 74,
41+
120, 136, 149, 139, 227, 99, 232, 109, 233, 203, 213, 254, 59, 0, 29, 57,
42+
242, 239, 183, 14, 102, 88, 208, 228, 166, 119, 114, 248, 235, 117, 75, 10,
43+
49, 68, 80, 180, 143, 237, 31, 26, 219, 153, 141, 51, 159, 17, 131, 20,
44+
};
45+
46+
// The X buffer is a 48-byte auxiliary block used to compute the message digest.
47+
private readonly byte[] xBuffer = new byte[48];
48+
49+
// The M buffer is a 16-byte auxiliary block that keeps 16 byte blocks from the input data.
50+
private readonly byte[] mBuffer = new byte[16];
51+
52+
// The checksum buffer
53+
private readonly byte[] checkSum = new byte[16];
54+
55+
private int xBufferOffset;
56+
private int mBufferOffset;
57+
58+
/// <summary>
59+
/// Computes the MD2 hash of the input byte array.
60+
/// </summary>
61+
/// <param name="input">The input byte array to be hashed.</param>
62+
/// <returns>The MD2 hash as a byte array.</returns>
63+
public byte[] Digest(byte[] input)
64+
{
65+
Update(input, 0, input.Length);
66+
67+
// Pad the input to a multiple of 16 bytes.
68+
var paddingByte = (byte)(mBuffer.Length - mBufferOffset);
69+
70+
for (var i = mBufferOffset; i < mBuffer.Length; i++)
71+
{
72+
mBuffer[i] = paddingByte;
73+
}
74+
75+
// Process the checksum of the padded input.
76+
ProcessCheckSum(mBuffer);
77+
78+
// Process the first block of the padded input.
79+
ProcessBlock(mBuffer);
80+
81+
// Process the second block of the padded input, which is the checksum.
82+
ProcessBlock(checkSum);
83+
84+
// Copy the first 16 bytes of the auxiliary block to the output.
85+
var digest = new byte[16];
86+
87+
xBuffer.AsSpan(xBufferOffset, 16).CopyTo(digest);
88+
89+
// Reset the internal state for reuse.
90+
Reset();
91+
return digest;
92+
}
93+
94+
/// <summary>
95+
/// Resets the engine to its initial state.
96+
/// </summary>
97+
private void Reset()
98+
{
99+
xBufferOffset = 0;
100+
for (var i = 0; i != xBuffer.Length; i++)
101+
{
102+
xBuffer[i] = 0;
103+
}
104+
105+
mBufferOffset = 0;
106+
for (var i = 0; i != mBuffer.Length; i++)
107+
{
108+
mBuffer[i] = 0;
109+
}
110+
111+
for (var i = 0; i != checkSum.Length; i++)
112+
{
113+
checkSum[i] = 0;
114+
}
115+
}
116+
117+
/// <summary>
118+
/// Performs the compression step of MD2 hash algorithm.
119+
/// </summary>
120+
/// <param name="block">The 16 bytes block to be compressed.</param>
121+
/// <remarks>
122+
/// the compression step is designed to achieve diffusion and confusion, two properties that make it hard to reverse
123+
/// or analyze the hash function. Diffusion means that changing one bit of the input affects many bits of the output,
124+
/// and confusion means that there is no apparent relation between the input and the output.
125+
/// </remarks>
126+
private void ProcessBlock(byte[] block)
127+
{
128+
// Copying and XORing: The input block is copied to the second and third parts of the internal state, while XORing
129+
// the input block with the first part of the internal state.
130+
// By copying the input block to the second and third parts of the internal state, the compression step ensures
131+
// that each input block contributes to the final output digest.
132+
// By XORing the input block with the first part of the internal state, the compression step introduces a non-linear
133+
// transformation that depends on both the input and the previous state. This makes it difficult to deduce the input
134+
// or the state from the output, or vice versa.
135+
for (var i = 0; i < 16; i++)
136+
{
137+
xBuffer[i + 16] = block[i];
138+
xBuffer[i + 32] = (byte)(block[i] ^ xBuffer[i]);
139+
}
140+
141+
var tmp = 0;
142+
143+
// Mixing: The internal state is mixed using the substitution table for 18 rounds. Each round consists of looping
144+
// over the 48 bytes of the internal state and updating each byte by XORing it with a value from the substitution table.
145+
// The mixing process ensures that each byte of the internal state is affected by every byte of the input block and
146+
// every byte of the substitution table. This creates a high degree of diffusion and confusion, which makes it hard
147+
// to find collisions or preimages for the hash function.
148+
for (var j = 0; j < 18; j++)
149+
{
150+
for (var k = 0; k < 48; k++)
151+
{
152+
tmp = xBuffer[k] ^= STable[tmp];
153+
tmp &= 0xff;
154+
}
155+
156+
tmp = (tmp + j) % 256;
157+
}
158+
}
159+
160+
/// <summary>
161+
/// Performs the checksum step of MD2 hash algorithm.
162+
/// </summary>
163+
/// <param name="block">The 16 bytes block to calculate the checksum.</param>
164+
/// <remarks>
165+
/// The checksum step ensures that changing any bit of the input message will change about half of the bits of the
166+
/// checksum, making it harder to find collisions or preimages.
167+
/// </remarks>
168+
private void ProcessCheckSum(byte[] block)
169+
{
170+
// Assign the last element of checksum to the variable last. This is the initial value of the checksum.
171+
var last = checkSum[15];
172+
for (var i = 0; i < 16; i++)
173+
{
174+
// Compute the XOR of the current element of the mBuffer array and the last value, and uses it as an index
175+
// to access an element of STable. This is a substitution operation that maps each byte to another byte using
176+
// the STable.
177+
var map = STable[(mBuffer[i] ^ last) & 0xff];
178+
179+
// Compute the XOR of the current element of checkSum and the substituted byte, and stores it back to the
180+
// checksum. This is a mixing operation that updates the checksum value with the input data.
181+
checkSum[i] ^= map;
182+
183+
// Assign the updated element of checksum to last. This is to keep track of the last checksum value for the
184+
// next iteration.
185+
last = checkSum[i];
186+
}
187+
}
188+
189+
/// <summary>
190+
/// Update the message digest with a single byte.
191+
/// </summary>
192+
/// <param name="input">The input byte to digest.</param>
193+
private void Update(byte input)
194+
{
195+
mBuffer[mBufferOffset++] = input;
196+
}
197+
198+
/// <summary>
199+
/// Update the message digest with a block of bytes.
200+
/// </summary>
201+
/// <param name="input">The byte array containing the data.</param>
202+
/// <param name="inputOffset">The offset into the byte array where the data starts.</param>
203+
/// <param name="length">The length of the data.</param>
204+
private void Update(byte[] input, int inputOffset, int length)
205+
{
206+
// process whole words
207+
while (length >= 16)
208+
{
209+
Array.Copy(input, inputOffset, mBuffer, 0, 16);
210+
ProcessCheckSum(mBuffer);
211+
ProcessBlock(mBuffer);
212+
213+
length -= 16;
214+
inputOffset += 16;
215+
}
216+
217+
while (length > 0)
218+
{
219+
Update(input[inputOffset]);
220+
inputOffset++;
221+
length--;
222+
}
223+
}
224+
}

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ find more than one implementation for the same objective but using different alg
2525
* [ISO 7816-4 Padding](./Algorithms/Crypto/Paddings/ISO7816d4Padding.cs)
2626
* [X9.32 Padding](./Algorithms/Crypto/Paddings/X932Padding.cs)
2727
* [TBC Padding](./Algorithms/Crypto/Paddings/TbcPadding.cs)
28-
* [PKCS7 Padding](./Algorithms/Crypto/Paddings/PKCS7Padding.cs)
28+
* [PKCS7 Padding](./Algorithms/Crypto/Paddings/PKCS7Padding.cs)
29+
* [Digests](./Algorithms/Crypto/Digests/)
30+
* [MD2 Digest](./Algorithms/Crypto/Digests/Md2Digest.cs)
2931
* [Data Compression](./Algorithms/DataCompression)
3032
* [Burrows-Wheeler transform](./Algorithms/DataCompression/BurrowsWheelerTransform.cs)
3133
* [Huffman Compressor](./Algorithms/DataCompression/HuffmanCompressor.cs)

0 commit comments

Comments
 (0)