Skip to main content Link Search Menu Expand Document (external link)

Lagima

Catégorie: medium-hard

Points: 164

Résolutions: 24

Énoncé

(voir aussi le défi voisin “Larisa”)

You are in the road to learn some interesting cryptosystems, decrypt our cipher!

#!/usr/bin/env sage

from flag import flag

def genperm(n):
    _ = list(range(1, n + 1))
    shuffle(_)
    return _

def genlatrow(n):
    A = []
    for _ in range(n): A.append(genperm(n))
    return A

def prodlat(A, B):
    assert len(A) == len(B)
    C, G = [], SymmetricGroup(len(A))
    for _ in range(len(A)):
        g = (G(A[_]) * G(B[_])).tuple()
        C.append(list(g))
    return C

def powlat(A, n):
    assert n >= 0
    B = bin(n)[2:]
    c, R = len(B), [list(range(1, len(A) + 1)) for _ in range(len(A))]
    if n == 0: return R
    else:
        for b in B:
            if b == '1':
                if c == 1: R = prodlat(R, A)
                else:
                    T = A
                    for _ in range(c - 1): T = prodlat(T, T)
                    R = prodlat(R, T)
            c -= 1
    return R

G = genlatrow(313)
secret = int.from_bytes(flag.lstrip(b'CCTF{').rstrip(b'}'), 'big')
H = powlat(G, secret)

print(f'G = {G}')
print(f'H = {H}')

Description du code

Le drapeau est converti en un grand entier qui sert d’exposant secret.

Un vecteur G de 313 permutations aléatoires des entiers de 1 à 313 est choisi.

La fonction powlat effectue une exponentiation rapide dans le groupe symétrique, terme à terme.

Propriété mathématique

Chaque élément de G est traité séparément. Les puissances d’une permutation sont faciles à décrire en étudiant la décomposition en cycles: la permutation agit de manière… cyclique, sur chaque cycle, on peut donc distinguer ses différentes puissances en observant leur action sur chaque cycle.

Puisque chaque élément de G agit par décalage de 1 sur chacun de ses cycles, l’élement correspondant de H agira par un décalage de secret % len(cycle) sur ce cycle.

On obtient ainsi secret % l pour différentes valeurs de l entre 2 et 313, ce qui permet de conclure en appliquant le théorème des restes chinois.

Solution

Note: SAGE fournit également une fonction générique discrete_log.

import json
from sage.all import SymmetricGroup, CRT

with open("output.txt") as f:
    lG = next(f)
    G = json.loads(lG.split("=")[-1])
    lH = next(f)
    H = json.loads(lH.split("=")[-1])

Sn = SymmetricGroup(313)
g = [Sn(x) for x in G]
h = [Sn(x) for x in H]

rems = {}
for i, gi in enumerate(g):
    for c in gi.cycle_tuples():
        l = len(c)
        k = c.index(h[i](c[0]))
        if l in rems:
            assert rems[l] == k
        else:
            rems[l] = k
c = CRT(list(rems.values()), list(rems.keys()))
print(int(c).to_bytes(64, "big").lstrip(b'\0'))
# b'3lGam4L_eNcR!p710n_4nD_L4T!n_5QuarS3!'