Python Challenge - Level 18
- Link: http://www.pythonchallenge.com/pc/return/balloons.html
- Username: huge
- Password: file
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:
- Common (
): shows../hex/bin.html - Additions (
+): showsbutter - Deletions (
-): showsfly
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

