/// <summary> /// Goes one step forward by either restoring a previously calculated state from the history or calculating the next state /// </summary> public void NextStep() { if (!IsInitialized) { return; } if (IsInFinishedState) { return; } if (HistoryHasMoreStates) { CurrentStateNumber++; CurrentState = StateHistory[CurrentStateNumber]; OnStatusChanged(); } else { PresentableMD5State previousState = CurrentState; AddNewState(); PerformStep(previousState, CurrentState); OnStatusChanged(); } if (!IsInFinishedState && skippedStates.Contains(CurrentState.State)) { NextStep(); } }
/// <summary> /// Reads from the data source /// </summary> /// <param name="newState">Algorithm state to modify</param> private void ReadData(PresentableMD5State newState) { // Fetch up to 64 bytes of data newState.Data = new byte[128]; newState.DataLength = (uint)DataStream.Read(newState.Data, 0, 64); //fully newState.DataOffset = 0; }
/// <summary> /// Performs the steps necessary after the individual compression function steps have run /// </summary> /// <param name="newState">Algorithm state to modify</param> private void FinishCompression(PresentableMD5State newState) { // Add compression function results to accumulators newState.H1 += newState.A; newState.H2 += newState.B; newState.H3 += newState.C; newState.H4 += newState.D; // Increment the number of bytes hashed so far newState.BytesHashed += DATA_BLOCK_SIZE; }
/// <summary> /// Clears the state history and adds the "uninitialized" state /// </summary> private void SetUninitializedState() { StateHistory.Clear(); PresentableMD5State uninitializedState = new PresentableMD5State(); uninitializedState.State = MD5StateDescription.UNINITIALIZED; StateHistory.Add(uninitializedState); CurrentState = uninitializedState; CurrentStateNumber = 0; }
/// <summary> /// Executes the round function and modifies algorithm state to reflect results /// </summary> /// <param name="state">Algorithm state to modify</param> /// <param name="function">The inner round function to execute</param> /// <param name="W">The part of the data to use in the round function</param> /// <param name="i">Index of this step (range 0 - 63)</param> protected static void ExecRoundFunction(PresentableMD5State state, RoundFunction function, uint W, uint i) { // Apply central compression function state.A = state.B + RotateLeft((state.A + function(state.A, state.B, state.C, state.D) + W + AdditionConstantTable[i]), ShiftConstantTable[i]); // Right-rotate the 4 compression result accumulators uint oldD = state.D; state.D = state.C; state.C = state.B; state.B = state.A; state.A = oldD; }
/// <summary> /// Performs one step of the compression function /// </summary> /// <param name="newState">Algorithm state to modify</param> private void PerformRoundStep(PresentableMD5State newState) { // Determine which round function to use RoundFunction roundFunction = ROUND_FUNCTION[newState.RoundIndex]; // Determine which step in the compression function this is uint stepIndex = newState.RoundIndex * 16 + newState.RoundStepIndex; // Determine which part of the data to use in this step int wordIndex = GetWordIndex(newState.RoundIndex, stepIndex); // Execute the chosen round function ExecRoundFunction(newState, roundFunction, newState.DataAsIntegers[wordIndex], stepIndex); }
/// <summary> /// Adds a new state to the history and sets it as the current state /// </summary> protected void AddNewState() { if (CurrentStateNumber == -1) { CurrentState = new PresentableMD5State(); } else { CurrentState = new PresentableMD5State(StateHistory[CurrentStateNumber]); } StateHistory.Add(CurrentState); CurrentStateNumber = StateHistory.Count - 1; }
/// <summary> /// Adds padding bytes to the data /// </summary> /// <param name="newState">Algorithm state to modify</param> private void AddPaddingBytes(PresentableMD5State newState) { // Save length of data in bit newState.LengthInBit = (newState.BytesHashed + newState.DataLength) * 8; // Add '1' bit to end of data newState.Data[newState.DataLength] = 0x80; newState.DataLength++; // Add zero bytes until 8 bytes short of next 64-byte block while (newState.DataLength % 64 != 56) { newState.Data[newState.DataLength++] = 0; } }
/// <summary> /// Copy constructor /// </summary> /// <param name="other">State to copy</param> public PresentableMD5State(PresentableMD5State other) { foreach (FieldInfo fi in GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) { fi.SetValue(this, fi.GetValue(other)); } foreach (PropertyInfo pi in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (pi.CanWrite && pi.CanRead) { pi.SetValue(this, pi.GetValue(other, null), null); } } }
/// <summary> /// Adds the data length part of the padding /// </summary> /// <param name="newState">Algorithm state to modify</param> private void AddLength(PresentableMD5State newState) { // Determine offset behind last written byte uint lengthOffset = newState.DataLength + 8; // Write the length in bit as 8 byte little-endian integer for (int i = 8; i > 0; i--) { newState.Data[lengthOffset - i] = (byte)(newState.LengthInBit >> ((8 - i) * 8) & 0xff); } // Remember that padding is done now newState.IsPaddingDone = true; // Update data length newState.DataLength += 8; }
/// <summary> /// Performs initialization required before running compression function steps /// </summary> /// <param name="newState">Algorithm state to modify</param> private void StartCompression(PresentableMD5State newState) { // Read data into unsigned 32 bit integers newState.DataAsIntegers = new uint[16]; for (uint j = 0; j < 64; j += 4) { newState.DataAsIntegers[j / 4] = (((uint)newState.Data[newState.DataOffset + (j + 3)]) << 24) | (((uint)newState.Data[newState.DataOffset + (j + 2)]) << 16) | (((uint)newState.Data[newState.DataOffset + (j + 1)]) << 8) | (((uint)newState.Data[newState.DataOffset + (j)])); } // Reset round counter newState.RoundIndex = 0; // Initialize A, B, C, D with accumulated values newState.A = newState.H1; newState.B = newState.H2; newState.C = newState.H3; newState.D = newState.H4; }
/// <summary> /// Performs initialization before a round /// </summary> /// <param name="newState">Algorithm state to modify</param> private void StartRound(PresentableMD5State newState) { // Reset round step counter newState.RoundStepIndex = 0; }
/// <summary> /// Performs the next step in the algorithm /// </summary> /// <param name="previousState">Previous state</param> /// <param name="newState">The new state which is to be determined</param> public void PerformStep(PresentableMD5State previousState, PresentableMD5State newState) { switch (previousState.State) { case MD5StateDescription.INITIALIZED: // Start by reading data newState.State = MD5StateDescription.READING_DATA; break; case MD5StateDescription.READING_DATA: // Fetch next data block and enter "data read" state ReadData(newState); newState.State = MD5StateDescription.READ_DATA; break; case MD5StateDescription.READ_DATA: // If an underfull buffer was read, enter "starting padding" state // If a full buffer was read, enter "starting compression" state if (previousState.DataLength < DATA_BLOCK_SIZE) { newState.State = MD5StateDescription.STARTING_PADDING; } else { newState.State = MD5StateDescription.STARTING_COMPRESSION; } break; case MD5StateDescription.STARTING_PADDING: // First step of padding is adding the padding bytes newState.State = MD5StateDescription.ADDING_PADDING_BYTES; break; case MD5StateDescription.ADDING_PADDING_BYTES: // Add necessary number of bytes and enter "added padding bytes" state AddPaddingBytes(newState); newState.State = MD5StateDescription.ADDED_PADDING_BYTES; break; case MD5StateDescription.ADDED_PADDING_BYTES: // Next step for padding is adding the data length newState.State = MD5StateDescription.ADDING_LENGTH; break; case MD5StateDescription.ADDING_LENGTH: // Add the length of the data and enter "added length" state AddLength(newState); newState.State = MD5StateDescription.ADDED_LENGTH; break; case MD5StateDescription.ADDED_LENGTH: // Padding is done after adding data length, so enter "finished padding" state newState.State = MD5StateDescription.FINISHED_PADDING; break; case MD5StateDescription.FINISHED_PADDING: // If padding is finished, call compression function for the last (two) time(s) newState.State = MD5StateDescription.STARTING_COMPRESSION; break; case MD5StateDescription.STARTING_COMPRESSION: // Perform pre-compression initialization and continue by starting the first round StartCompression(newState); newState.State = MD5StateDescription.STARTING_ROUND; break; case MD5StateDescription.STARTING_ROUND: // Start the round and continue with the first round step StartRound(newState); newState.State = MD5StateDescription.STARTING_ROUND_STEP; break; case MD5StateDescription.STARTING_ROUND_STEP: // Perform the step and go into finished state PerformRoundStep(newState); newState.State = MD5StateDescription.FINISHED_ROUND_STEP; break; case MD5StateDescription.FINISHED_ROUND_STEP: // If last step, go into 'finished round' state, else continue with next step if (previousState.IsLastStepInRound) { newState.State = MD5StateDescription.FINISHED_ROUND; } else { newState.RoundStepIndex++; newState.State = MD5StateDescription.STARTING_ROUND_STEP; } break; case MD5StateDescription.FINISHED_ROUND: // If last step, go into "finishing compression" state, else continue with next round if (previousState.IsLastRound) { newState.State = MD5StateDescription.FINISHING_COMPRESSION; } else { newState.RoundIndex++; newState.State = MD5StateDescription.STARTING_ROUND; } break; case MD5StateDescription.FINISHING_COMPRESSION: // Perform finishing actions and go into "finished compression" state FinishCompression(newState); newState.State = MD5StateDescription.FINISHED_COMPRESSION; break; case MD5StateDescription.FINISHED_COMPRESSION: // If there's more data left in buffer, reenter compression function with offset if (previousState.DataLength - previousState.DataOffset > DATA_BLOCK_SIZE) { // Still some data left in buffer, rerun compression with offset newState.DataOffset += DATA_BLOCK_SIZE; newState.State = MD5StateDescription.STARTING_COMPRESSION; } else { // No data left in buffer if (previousState.IsPaddingDone) { // If padding was already added, we're done DataStream.Close(); newState.State = MD5StateDescription.FINISHED; } else { // Read more data newState.State = MD5StateDescription.READING_DATA; } } break; } }