Last active
August 24, 2023 16:02
-
-
Save zdimension/0f2a308fa9960b1644e81c12dca94b87 to your computer and use it in GitHub Desktop.
Pycto color spectrum puzzle solver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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