Now, before we dive in, a few notes:

If you've had a go at solving these puzzles (properly, or otherwise) I'd love to hear about them in the comments.

1) This whole endeavour was completely pointless, as Google indexed all the pages anyway. After sniffing around GCHQ-related things on Friday night, this popped up in my Google Now news feed the next morning - before I had solved anything: (nice one GCHQ - ever heard of robots.txt?)

2) Possibly, applying some brainpower would have been quicker in solving some of these things. I wanted to see if brute force was feasible.

3) The only reason it was possible to get as far as I did is because GCHQ were sloppy - they allowed thousands of page requests from the same client, and they left their hashing algorithms in the page source code.

4) "Part 5" is un-brute-forceable. It's a bunch of horribly hard brain teasers. You're on your own for that one (I haven't even tried it yet).

There will be spoilers on this page. I have linked to the actual answers instead of displaying them, but I'm leaving the methods in the clear.


Part 1

This is part 1:

And it comes with instructions:

In this type of grid-shading puzzle, each square is either black or white. Some of the black squares have already been filled in for you.
Each row or column is labelled with a string of numbers. The numbers indicate the length of all consecutive runs of black squares, and are displayed in the order that the runs appear in that line. For example, a label "2 1 6" indicates sets of two, one and six black squares, each of which will have at least one white square separating them.
So, the method is going to be somewhat like a Sudoku, except each square has only 2 values, and the possible values are different for each row/column. This should be easily crunchable - the basic plan is:
  1. For every row (and column), calculate all possible arrangements.
  2. If the row (column) has a fixed pattern given, then prune any arrangements which don't with with it
  3. If any rows (columns) have only one possible arrangement now, then treat them as fixed patterns
  4. Goto 2

We can treat a row as a binary number, 25 bits long with a 1 representing a black square and a 0 representing a white one. This makes the code dead simple (and fast).

Calculating each possible arrangement is a great candidate for a recursive algorithm. We'll basically start at the left and place the first black block (I call them 'marks') in each possible position which still leaves enough space for all the ones after it. In each position, we recurse - placing the next 'mark' in each possible position after the one we just placed... do this for every 'mark' and you get all possibilities. Here's some pseudo-code for the recursion:

/*
 * row_info stores things like the number of marks, their width,
 * and all the possible candidate patterns
 */

function place_mark(row_info, pattern, start_pos, mark_num) {
	/* Recursion termination clause */
	if (mark_num == row_info.num_marks) {
		/* We are done with this line. Store the pattern */
		row_info.possible_patterns.append(pattern);
		return;
	}

	/* Recursion reduce cause */
	/*
	 * space_required(row, mark_num) computes the amount of space required
	 * for all the marks in row_info after the mark_num'th one.
	 */
	req_space = space_required(row_info, mark_num);
	for all start positions < (25 - req_space) {
		/*
		 * mark_bits(width, start) generates a binary number representing
		 * a mark of the given width at the given starting position
		 */
		mark = mark_bits(row_info.marks[mark_num], start);
		place_mark(row_info, pattern | mark, start, mark_num + 1);
	}
}
At each step along the way, if the row has a fixed mask (either because we calculated one from only having one possibility, or because there was some marks in the original picture) we can test each pattern against it, and discard those that don't match.

Once we've computed all of these, we "fix" any lines with only 1 possible candidate, and then use that to update the masks on all other rows and columns....

Read more »