Python Challenge - Level 18

Problem

The image shows two balloon pictures side-by-side. The difference is their brightness. Navigating to brightness.html reveals an HTML comment:

<!-- maybe consider deltas.gz -->

Downloading and decompressing deltas.gz yields a text file with two side-by-side hex dumps separated by whitespace.

Solution

The two columns represent two versions of data. By computing the diff between column 1 and column 2 using difflib.Differ:

  • Lines present only in column 1 (-): Deletions form one file.
  • Lines present only in column 2 (+): Additions form a second file.
  • Lines present in both ( ): Common lines form a third file.

Parsing the hex bytes in each category yields three PNG images:

  1. Common ( ): shows ../hex/bin.html
  2. Additions (+): shows butter
  3. Deletions (-): shows fly

Together, they give the new credentials: username butter, password fly.

Python

import gzip
import difflib

with gzip.open("deltas.gz", "rt") as f:
    lines = f.readlines()

col1 = [line[:53] + "\n" for line in lines]
col2 = [line[56:] for line in lines]

diff = list(difflib.Differ().compare(col1, col2))

f_same = open("same.png", "wb")
f_add = open("add.png", "wb")
f_del = open("del.png", "wb")

for line in diff:
    tag = line[:2]
    hex_data = line[2:].strip().split()
    if not hex_data:
        continue
    data = bytes([int(x, 16) for x in hex_data])

    if tag == "  ":
        f_same.write(data)
    elif tag == "+ ":
        f_add.write(data)
    elif tag == "- ":
        f_del.write(data)

f_same.close()
f_add.close()
f_del.close()

Go

In Go, we decompress with compress/gzip. To compute the diff, we can use line equality for identical runs, or an LCS / Myers diff algorithm:

package main

import (
	"bufio"
	"compress/gzip"
	"encoding/hex"
	"os"
	"strings"
)

func parseHexLine(s string) []byte {
	fields := strings.Fields(s)
	out := make([]byte, 0, len(fields))
	for _, f := range fields {
		b, err := hex.DecodeString(f)
		if err == nil && len(b) > 0 {
			out = append(out, b[0])
		}
	}
	return out
}

func main() {
	f, err := os.Open("deltas.gz")
	if err != nil {
		panic(err)
	}
	defer f.Close()

	gz, err := gzip.NewReader(f)
	if err != nil {
		panic(err)
	}
	defer gz.Close()

	scanner := bufio.NewScanner(gz)
	var col1, col2 []string

	for scanner.Scan() {
		line := scanner.Text()
		if len(line) >= 56 {
			col1 = append(col1, strings.TrimRight(line[:53], " "))
			col2 = append(col2, strings.TrimRight(line[56:], " "))
		}
	}

	fSame, _ := os.Create("same.png")
	fAdd, _ := os.Create("add.png")
	fDel, _ := os.Create("del.png")
	defer fSame.Close()
	defer fAdd.Close()
	defer fDel.Close()

	// Compare parallel rows directly
	for i := 0; i < len(col1) && i < len(col2); i++ {
		l1, l2 := col1[i], col2[i]
		if l1 == l2 {
			fSame.Write(parseHexLine(l1))
		} else {
			fDel.Write(parseHexLine(l1))
			fAdd.Write(parseHexLine(l2))
		}
	}
}

Rust

use flate2::read::GzDecoder;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};

fn parse_hex_line(s: &str) -> Vec<u8> {
    s.split_whitespace()
        .filter_map(|token| u8::from_str_radix(token, 16).ok())
        .collect()
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open("deltas.gz")?;
    let decoder = GzDecoder::new(file);
    let reader = BufReader::new(decoder);

    let mut col1 = Vec::new();
    let mut col2 = Vec::new();

    for line in reader.lines() {
        let l = line?;
        if l.len() >= 56 {
            col1.push(l[..53].trim_end().to_string());
            col2.push(l[56..].trim_end().to_string());
        }
    }

    let mut f_same = File::create("same.png")?;
    let mut f_add = File::create("add.png")?;
    let mut f_del = File::create("del.png")?;

    for (c1, c2) in col1.iter().zip(col2.iter()) {
        if c1 == c2 {
            f_same.write_all(&parse_hex_line(c1))?;
        } else {
            f_del.write_all(&parse_hex_line(c1))?;
            f_add.write_all(&parse_hex_line(c2))?;
        }
    }

    Ok(())
}

Language Comparison

Feature Python Go Rust
Diff Algorithm difflib.Differ in stdlib computes detailed character/line delta tags (+, -, ) No diff in stdlib; parallel comparison or third-party sergi/go-diff No diff in stdlib; parallel comparison or similar crate
Gzip Streaming gzip.open() stdlib compress/gzip.NewReader() stdlib flate2::read::GzDecoder crate
Hex Parsing int(x, 16) built-in encoding/hex.DecodeString() stdlib u8::from_str_radix(x, 16) stdlib

Key insight: Python's difflib provides built-in Myers/Ratcliff-Obershelp diffing in the standard library. When rows align line-by-line, Go and Rust's straightforward parallel iteration over line slices is significantly faster and uses minimal memory.

Next Level

Access the protected path:

http://www.pythonchallenge.com/pc/hex/bin.html

  • Username: butter
  • Password: fly