Skip to content

Instantly share code, notes, and snippets.

@zdimension
Last active August 24, 2023 16:02
Show Gist options
  • Save zdimension/0f2a308fa9960b1644e81c12dca94b87 to your computer and use it in GitHub Desktop.
Save zdimension/0f2a308fa9960b1644e81c12dca94b87 to your computer and use it in GitHub Desktop.
Pycto color spectrum puzzle solver
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
use itertools::Itertools;
use pbkdf2::pbkdf2_hmac_array;
use rayon::iter::ParallelBridge;
use rayon::iter::ParallelIterator;
use std::process::exit;
use chrono::Local;
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
use sha1::Sha1;
const WIDTH: usize = 15;
const HEIGHT: usize = 10;
const ORDER: [u8; WIDTH * HEIGHT] = [
35, 54, 110, 102, 104, 30, 9, 56, 10, 62, 7, 15, 22, 31, 114, 83, 137, 106, 126, 141, 113, 27,
84, 145, 41, 88, 125, 94, 4, 14, 13, 49, 90, 38, 69, 2, 111, 45, 132, 112, 140, 77, 53, 115,
103, 128, 121, 120, 101, 117, 134, 123, 70, 47, 61, 136, 82, 67, 1, 118, 74, 107, 148, 81, 131,
66, 11, 3, 68, 142, 92, 19, 16, 99, 97, 85, 130, 28, 21, 135, 119, 60, 57, 59, 147, 95, 71, 65,
50, 46, 144, 105, 32, 98, 80, 55, 39, 75, 23, 43, 109, 42, 108, 12, 8, 26, 122, 127, 40, 24,
143, 63, 146, 133, 18, 87, 37, 34, 25, 86, 6, 48, 58, 96, 89, 36, 78, 64, 51, 124, 72, 138, 93,
73, 149, 44, 29, 17, 5, 20, 129, 0, 76, 33, 52, 100, 139, 79, 91, 116,
];
fn main() {
let last_column = ORDER
.into_iter()
.skip(WIDTH - 1)
.step_by(WIDTH)
.collect::<Vec<_>>();
assert_eq!(last_column.len(), HEIGHT);
println!("{}", Local::now().format("%Y-%m-%d][%H:%M:%S"));
last_column
.into_iter()
.permutations(HEIGHT)
.par_bridge()
.for_each(|x| {
decrypt(&x);
});
}
const IV: [u8; 16] = [
229, 80, 165, 57, 239, 85, 230, 153, 40, 0, 162, 94, 26, 163, 141, 6,
];
const MSG: [u8; 32] = [
192, 184, 200, 107, 68, 1, 232, 245, 130, 212, 16, 103, 25, 126, 189, 236, 46, 25, 217, 226,
58, 6, 11, 223, 172, 142, 216, 64, 72, 25, 179, 42,
];
const SUCCESS: &[u8] = b"success";
fn decrypt(order: &[u8]) {
let mut new_order = ORDER;
for (i, &n) in order.iter().enumerate() {
new_order[i * WIDTH + WIDTH - 1] = n;
}
let password = new_order
.iter()
.rev()
.map(|n| format!("{:02}", n))
.collect::<String>();
let key = pbkdf2_hmac_array::<Sha1, 16>(password.as_bytes(), &IV, 1000);
let mut buf = MSG;
let decipher = Aes128CbcDec::new(&key.into(), &IV.into()).decrypt_padded_mut::<Pkcs7>(&mut buf);
if let Ok(dec) = decipher {
if dec.starts_with(SUCCESS) {
println!("{}", String::from_utf8_lossy(&buf));
println!("{:?}", order);
println!("{}", Local::now().format("%Y-%m-%d][%H:%M:%S"));
exit(0);
}
}
}
# coding: utf-8
import base64
import colorsys
import hashlib
from io import BytesIO
import imagehash
import numpy as np
import requests
from Crypto.Cipher import AES
from PIL import Image
URL_ROOT = "https://pycto.io/api/"
HASH = "da5hW53W-FxVETWQC_taHA"
ORIG_IMG_PATH = r"D:\Téléchargements\F3lWdv4WoAA_nCH.jpg"
orig_img = Image.open(ORIG_IMG_PATH)
def get(endpoint):
return requests.get(URL_ROOT + endpoint)
data = get(f"getpycto?hash={HASH}").json()["pycto_data"]
width, height = data["parts"]
part_size = (orig_img.width // width, orig_img.height // height)
orig_parts = [
orig_img.crop((x * part_size[0], y * part_size[1], (x + 1) * part_size[0], (y + 1) * part_size[1]))
for y in range(height) for x in range(width)
]
def rgb2hls(img):
""" note: elements in img is a float number less than 1.0 and greater than 0.
:param img: an numpy ndarray with shape NHWC
:return:
"""
assert len(img.shape) == 3
hue = np.zeros_like(img[:, :, 0])
luminance = np.zeros_like(img[:, :, 0])
saturation = np.zeros_like(img[:, :, 0])
for x in range(height):
for y in range(width):
r, g, b = img[x, y]
h, l, s = colorsys.rgb_to_hls(r, g, b)
hue[x, y] = h
luminance[x, y] = l
saturation[x, y] = s
return hue, luminance, saturation
def process(image):
image.hash = imagehash.average_hash(image)
image_rgb = np.asarray(image.convert("RGB")) / 255.0
image_rgb = image_rgb[3:, 3:, :]
average_color = np.average(image_rgb, axis=(0, 1))
hue_average_color = colorsys.rgb_to_hls(*average_color)[0]
left_half = image_rgb[:, :3, :]
image.left_average_h = colorsys.rgb_to_hls(*np.average(left_half, axis=(0, 1)))[0]
image.vect = np.array([hue_average_color, *average_color, image.left_average_h])
for i, part in enumerate(orig_parts):
part.id = i
process(part)
def b64toimg(b64):
image = Image.open(BytesIO(base64.b64decode(b64)))
image = image.resize((orig_img.width // width, orig_img.height // height))
return image
image_parts = list(map(b64toimg, data["image_parts"]))
for i, image in enumerate(image_parts):
image.id = i
process(image)
def pilImageToSurface(pilImage):
return pygame.image.fromstring(
pilImage.tobytes(), pilImage.size, pilImage.mode).convert()
def iteration():
global image_parts
old_parts = list(image_parts)
new_parts = []
for img in orig_parts:
best = min(old_parts, key=lambda img2: img.hash - img2.hash)
new_parts.append(best)
old_parts.remove(best)
image_parts = new_parts
def match_by_closest_vec():
global image_parts
old_parts = list(image_parts)
new_parts = []
for img in orig_parts:
best = min(old_parts, key=lambda img2: np.linalg.norm(img.vect - img2.vect))
new_parts.append(best)
old_parts.remove(best)
image_parts = new_parts
match_by_closest_vec()
iv = base64.b64decode(data["iv"])
message = base64.b64decode(data["message"])
def decrypt(order):
password = "".join("%02d" % n for n in reversed(order))
key = hashlib.pbkdf2_hmac("sha1", password.encode(), iv, 1000, 16)
decipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = decipher.decrypt(message)
return decrypted
def check(order, force=False):
decrypted = decrypt(order)
res = decrypted.startswith(b"success")
if res:
print("ok")
if res or force:
decrypt_data = {
"message": base64.b64encode(message).decode(),
"iv": base64.b64encode(iv).decode(),
"permutation": list(map(str, order)),
}
r = requests.post(URL_ROOT + "decryptmessage/", json=decrypt_data)
try:
print(r.json())
except:
print(r)
if res:
print(order)
print(decrypted)
new_data = data.copy()
del new_data["image_parts"]
print(new_data)
exit()
return True
# try permutations for last column
# test_order = [i.id for i in image_parts]
# last_column = test_order[width-1::width]
# for i, order in enumerate(itertools.permutations(last_column)):
# if i % 5000 == 0:
# print(i)
# test_order[width-1::width] = order
# check(test_order)
#
# exit()
import pygame
pygame.init()
disp = pygame.display.set_mode((orig_img.width, orig_img.height))
font = pygame.font.SysFont("Arial", 9)
def get_order():
return [i.id for i in image_parts]
print(get_order()) # to give to Rust solver
print(list(iv))
print(list(message))
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
iteration()
pass
elif event.key == pygame.K_SPACE:
print(check(get_order(), True))
disp.fill((0, 0, 0))
for i, image in enumerate(image_parts):
x = i % width
y = i // width
pos = (x * part_size[0], y * part_size[1])
disp.blit(pilImageToSurface(image), pos)
for j, x in enumerate(image.vect):
text = font.render(str(x.round(3)), True, (192, 0, 192))
disp.blit(text, (pos[0], pos[1] + j * 9))
pygame.display.update()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment