Larisa
Catégorie: medium-hard
Points: 174
Résolutions: 22
First blood! 🩸
Énoncé
You think you can understand the way our cryptosystem encrypts messages? Here you can challenge yourself by decrypting this message!
#!/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
def pad(msg, n):
assert len(msg) <= n
return msg + msg[-1] * (n - len(msg))
def embed(msg, n):
assert len(msg) < n
msg = pad(msg, n)
while True:
r, s = [randint(2, n) for _ in '__']
if gcd(r, len(msg)) == 1:
break
A = []
for _ in range(n):
while True:
R = genperm(n)
if R[(_ * r + s) % n] == ord(msg[_]):
A.append(R)
break
return A
def encrypt(A, e = 65537):
return powlat(A, e)
l, e = 128, 65537
M = embed(flag, l)
C = encrypt(M, e)
print(C)
(voir également le défi connexe Lagima).
Analyse
Les fonctions fournies effectuent des opérations sur des tableaux de permutations:
getlatrow(non utilisée) tire aléatoirement un vecteur de permutationprodlatcalcule le produit terme à terme de 2 vecteurs de permutationspowlatcalcule l’exponentiation rapide, terme à terme, d’un vecteur de permutations
La fonction principale est embed qui encode un message de la manière suivante: on choisit des coefficients de mélange (r, s) qui définissent une fonction affine modulo 128 x → r*x+s et on tire aléatoirement un vecteur de permutations A tel que A[i][r*i+s] == msg[i] pour tout i.
Le chiffrement se fait en élevant le vecteur de permutations à la puissance 65537.
Les puissances d’une permutation p agissent cycliquement sur les cycles de la décomposition de p. On peut donc inverser l’exponentiation en calculant l’inverse de 65537 modulo la longueur des cycles.
Solution
On peut utiliser la méthode multiplicative_order de SAGE pour ne pas écrire soi-même le calcul sur chacun des cycles.
On cherche ensuite ret s par recherche exhaustive, en connaissant le format du drapeau CCTF{...} et en lisant les éléments A[i][r*i+s].
import json
from sage.all import SymmetricGroup
t = json.load(open("enc.txt"))
embed = []
G = SymmetricGroup(128)
for perm in t:
g = G(perm)
order = g.order()
d = pow(65537, -1, order)
embed.append((g**d).tuple())
for r in range(1, 128, 2):
for s in range(128):
txt = bytes(embed[i][(r * i + s) % 128] for i in range(128))
if b"CCTF" in txt:
print(txt)