// Arup Guha
// 11/23/2020
// Goal: Write code to find two different strings that give the same
//       hash value when using the function hashFunction.
//       Output of hashFunction is a 48 bit integer stored in a long.

import java.util.*;

public class hash {
	
	public static Random r;

    public static void main(String[] args) {
		
		/* First match we found!!!
		char[] s1 = "HCFOLRXQCGMRT".toCharArray();
		char[] s2 = "XMOKETOQNKGQI".toCharArray();
		System.out.println(hashFunction(s1));
		System.out.println(hashFunction(s2));
		*/

		int cnt = 0;
		r = new Random();
		
		HashMap<Long,String> map = new HashMap<Long,String>();
		
		// Keep going till we find a match.
		while (true) {
		
			char[] tmp = randomString(13);
			long output = hashFunction(tmp);
			cnt++;
			
			// Got a match!!!
			if (map.containsKey(output)) {
				System.out.println("Found a match.");
				System.out.println("Strings "+map.get(output)+" and "+(new String(tmp))+" are matches.");
				System.out.println("They both hash to "+output);
				break;
			}
			
			// Add to our map, this is a string that has output as its hash value.
			map.put(output, new String(tmp));
		
		}

		System.out.println("Generated "+cnt+" random strings to get the match.");
    }
	
	public static char[] randomString(int n) {
		char[] res = new char[n];
		for (int i=0; i<n; i++)
			res[i] = (char)('A'+r.nextInt(26));
		return res;
	}
   
   	// This is the hash function you must use for homework #6.
    public static long hashFunction(char[] input) {

        int i = 0, n = input.length;
        long buffer = 0x3f290785c16eL;
        while (i < n) {
            long temp = 0L;
            int j;
            for (j=0; j<6 && i+j<n; j++)
            	if (j%2 == 0)
                	temp |= ( ( (long)(f((int)input[i+j])) ) << (8*j)  );
                else
                	temp |=( ( (long)input[i+j])  << (8*j)  );
            i += 6;

            buffer = f2(buffer ^ temp);
        }
        return buffer;
    }

    // value must be 8 bits. Swaps the left and the right halves and runs
    // each through an s-box.
    public static long f(long value) {
        value = reverse(value);
        long left = (value & (0xf0)) >> 4;
        long right = value & (0xf);
        return (long)((box((int)left) << 4) | box((int)right));
    }

    // value must be 8 bits. It's reverse (in binary) is returned.
    public static long reverse(long value) {
        int i;
        long ans = 0L;
        for (i=0; i<8; i++) {
            ans = (ans << 1L) | (value & 1L);
            value = value >> 1;
        }
        return ans;
    }

    // Random s-box of values. 
    public static int box(int value) {
        int ans[] = {3, 9, 2, 0, 12, 14, 15, 1, 11, 5, 7, 8, 4, 10, 13, 6};
        return ans[value];
    }

    // A second random s-box of values.
    public static int box2(int value) {
        return (53*value)%64;
    }

    // A second function based on the sbox. Value is once again reversed, with
    // another substitution into a different sbox.
    public static long f2(long value) {

        int i;
        long ans = 0L;
        for (i=0; i<8; i++) {
            long index = value & 0x3fL;
            ans = (ans << 6) | box2((int)index);
            value = value >> 6;
        }
        return ans;
    }

}