/****************************************************************************** * This program implements the MD5 cryptographic hash algorithm. * * Copyright © 2021 Richard Lesh. All rights reserved. *****************************************************************************/ import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.pureprogrammer.Utils; public class OneWayHash { // s specifies the per-round shift amounts static List SHIFT_BY = Utils.listFromIntegers(new int[]{7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}); /****************************************************************************** * Use binary integer part of the sines of integers (Radians) as constants: * for i from 0 to 63 do * K[i] := floor(232 × abs (sin(i + 1))) * end for * * Or just use the following precomputed table: *****************************************************************************/ static List K = Utils.listFromIntegers(new int[]{-0x28955B88, -0x173848AA, 0x242070DB, -0x3E423112, -0xA83F051, 0x4787C62A, -0x57CFB9ED, -0x2B96AFF, 0x698098D8, -0x74BB0851, -0xA44F, -0x76A32842, 0x6B901122, -0x2678E6D, -0x5986BC72, 0x49B40821, -0x9E1DA9E, -0x3FBF4CC0, 0x265E5A51, -0x16493856, -0x29D0EFA3, 0x2441453, -0x275E197F, -0x182C0438, 0x21E1CDE6, -0x3CC8F82A, -0xB2AF279, 0x455A14ED, -0x561C16FB, -0x3105C08, 0x676F02D9, -0x72D5B376, -0x5C6BE, -0x788E097F, 0x6D9D6122, -0x21AC7F4, -0x5B4115BC, 0x4BDECFA9, -0x944B4A0, -0x41404390, 0x289B7EC6, -0x155ED806, -0x2B10CF7B, 0x4881D05, -0x262B2FC7, -0x1924661B, 0x1FA27CF8, -0x3B53A99B, -0xBD6DDBC, 0x432AFF97, -0x546BDC59, -0x36C5FC7, 0x655B59C3, -0x70F3336E, -0x100B83, -0x7A7BA22F, 0x6FA87E4F, -0x1D31920, -0x5CFEBCEC, 0x4E0811A1, -0x8AC817E, -0x42C50DCB, 0x2AD7D2BB, -0x14792C6F}); static String toHexadecimal(int i, int count) { final String PADDED_HEX = Utils.join("", "00000000", Utils.toHex(i)); return Utils.cpSubstring(PADDED_HEX, Utils.cpLength(PADDED_HEX) - count); } static String toHexadecimalLe(int i, int count) { String paddedHex = Utils.join("", "00000000", Utils.toHex(i)); paddedHex = Utils.cpSubstring(paddedHex, Utils.cpLength(paddedHex) - count); String result = ""; for (int j = 0; j < count / 2; ++j) { final int p = count - (j + 1) * 2; result += Utils.cpSubstring(paddedHex, p, p + 2); } return result; } static String printMessageBlock(final List block) { String result = ""; for (int i : block) { result += toHexadecimal(i, 2); } return result; } static int leftRotate(int x, int i) { return x << i | (x >>> (32 - i)); } static void computeMd5Stage(List md5State, final List messageBlock) { int A = md5State.get(0); int B = md5State.get(1); int C = md5State.get(2); int D = md5State.get(3); for (int i = 0; i < 64; ++i) { int F = 0, g = 0; if (i < 16) { F = (B & C) | (~B & D); g = i; } else if (i < 32) { F = (B & D) | (C & ~D); g = (5 * i + 1) % 16; } else if (i < 48) { F = B ^ C ^ D; g = (3 * i + 5) % 16; } else { F = C ^ (B | ~D); g = (7 * i) % 16; } // Words are in little-endian g *= 4; int messageWord = 0; for (int j = 0; j < 4; ++j) { messageWord |= (messageBlock.get(j + g) & 0xFF) << (8 * j); } F = (F + A + K.get(i) + messageWord) & 0xffffffff; A = D; D = C; C = B; B = (B + leftRotate(F, SHIFT_BY.get(i))) & 0xffffffff; } md5State.set(0, md5State[0] + A); md5State.set(1, md5State[1] + B); md5State.set(2, md5State[2] + C); md5State.set(3, md5State[3] + D); } static List computeMd5(String filespec) throws IOException { BufferedInputStream ifh = new BufferedInputStream(new FileInputStream(filespec)); int c; long count = 0; List md5State = Utils.listFromIntegers(new int[]{0x67452301, -0x10325477, -0x67452302, 0x10325476}); List messageBlock = new ArrayList<>(); while ((c = Utils.getbyte(ifh)) != -1) { // Bytes are in little-endian messageBlock.add(c); ++count; if (count % 64 == 0) { computeMd5Stage(md5State, messageBlock); messageBlock.clear(); } } /****************************************************************************** * TODO * append "1" bit to message * Notice: the input bytes are considered as bits strings, * where the first bit is the most significant bit of the byte.[51] * append "0" bit until message length in bits ≡ 448 (mod 512) * append original length in bits mod 264 to message *****************************************************************************/ final long DATA_LENGTH = count * 8; final int DATA_LENGTH_LOW = (int)(DATA_LENGTH % 4294967296L); final int DATA_LENGTH_HIGH = (int)(DATA_LENGTH / 4294967296L); int paddByte = 0x80; if ((int)(count % 64) >= 56) { for (int i = (int)(count % 64); i < 64; ++i) { messageBlock.add(paddByte); paddByte = 0; ++count; } computeMd5Stage(md5State, messageBlock); messageBlock.clear(); } for (int i = (int)(count % 64); i < 64; ++i) { if (count % 64 >= 56) { // Put it in little endian if (count % 64 < 60) { paddByte |= (DATA_LENGTH_LOW >> ((count % 8) * 8)) & 0xFF; } else { paddByte |= (DATA_LENGTH_HIGH >> ((count % 4) * 8)) & 0xFF; } } messageBlock.add(paddByte); paddByte = 0; ++count; } computeMd5Stage(md5State, messageBlock); try {ifh.close();} catch (IOException ex) {}; return md5State; } public static void main(String[] args) { if (args.length != 1) { System.out.println(Utils.join("", "Syntax: ", "OneWayHash", " {filename}")); System.exit(1); } String filespec = args[0]; try { List result = new ArrayList<>(); result = computeMd5(filespec); for (int i : result) { System.out.print(toHexadecimalLe(i, 8)); } System.out.println(); } catch (IOException ex) { System.out.println(Utils.join("", "Error: ", Utils.exceptionMessage(ex))); } } }