Writeups
  • UMASS CTF 2024
    • Crypto-Brutal Mogging
    • Crypto - Shuffling as a Service
  • GPN CTF 2024
    • Crypto-Boombox
  • WANICTF 2024
    • Crypto - Replacement
    • Crypto - Easy Calc
    • Crypto - dance
    • Crypto - speedy
    • Crypto - Many Xor Shift
    • Crypto - uf
  • UIUCTF 2024
    • Crypto-Without a Trace
    • Crypto-Determined
    • Crypto-Naptime
    • Crypto-Snore Signatures
    • Crypto-Groups
  • DeadSec CTF 2024
    • Crypto - Raul Rosas
    • Crypto - SSP
    • Crypto - Password Guesser
  • corCTF 2024
    • Crypto - Steps
    • Crypto - Monkfish
    • Crypto - Anglerfish
  • LITCTF 2024
    • Crypto- Symmetric RSA
    • Crypto -Truly Symmetric RSA
  • IrisCTF 2025
    • Crypto - knutsacque
  • UofTCTF 2025
    • Misc - Simple Signing
  • HTB CyberApocalypse
    • Crypto - Copperbox
  • BreachCTF 2025
    • Crypto - Taaffeite Encryption
    • Crypto - Big Stuff
Powered by GitBook
On this page
  • Full Implementation
  • Challenge
  1. IrisCTF 2025

Crypto - knutsacque

Behold my original knut sacque scheme in 4D -sera

PreviousIrisCTF 2025NextUofTCTF 2025

Last updated 4 months ago

Challenge

import secrets

F.<i,j,k> = QuaternionAlgebra(-1, -1)
A = []
B = [1, i, j, k]

msg_bin = b"irisctf{redacted_redacted_redacted_}"
assert len(msg_bin) % 4 == 0
msg = [F(sum(Integer(msg_bin[idx+bi])*b for bi, b in enumerate(B))) for idx in range(0, len(msg_bin), len(B))]
targ = 2^64

for _ in range(len(msg)):
    a = F(sum(secrets.randbelow(targ)*b for b in B))
    A.append(a)

sm = F(0)
for idx in range(len(msg)):
    sm += msg[idx] * A[idx]

print("A =", A)
print("s =", sm)

Now, let's break down the code and see how the message is being encrypted.

F.<i,j,k> = QuaternionAlgebra(-1, -1)
msg = [F(sum(Integer(msg_bin[idx+bi])*b for bi, b in enumerate(B))) for idx in range(0, len(msg_bin), len(B))]
for _ in range(len(msg)):
    a = F(sum(secrets.randbelow(targ)*b for b in B))
    A.append(a)
sm = F(0)
for idx in range(len(msg)):
    sm += msg[idx] * A[idx]

On simplifying, we get

We can generalize this by considering all the pairs, which will be in the form

This is similar to the knapsack problem, except the key values are in the range of ascii instead of {0,1}

a = []
for i in A:
    for j in i:
      a.append(j)  
l1 = []
l2 = []
l3 = []
l4 = []
for i in range(0,len(a),4):
    l1+= [a[i],-a[i+1],-a[i+2],-a[i+3]]
    l2+= [a[i+1],a[i],a[i+3],-a[i+2]]
    l3+= [a[i+2],-a[i+3],a[i],a[i+1]]
    l4+= [a[i+3],a[i+2],-a[i+1],a[i]]
M = Matrix.identity(ZZ,len(a))
M = M.augment(vector(list(l1)))
M = M.augment(vector(list(l2)))
M = M.augment(vector(list(l3)))
M = M.augment(vector(list(l4)))
M = M.stack(vector([80 for i in range(len(a))]+list(s)))
M1 = M.LLL()
for i in M1[-1][:-4]:
    print(chr(abs(i-80)),end='')
b'irisctf{wow_i_cant_believe_its_lll!}'

Here, i, j and k can be thought of unit vectors in a Cartesian plane except for the fact that i×i,j×j,k×k=−1i\times i,j\times j ,k \times k = -1i×i,j×j,k×k=−1.

This takes 4 bytes at a time and converts it into the format (midx+midx+1∗i+midx+2∗j+midx+3∗k)(m_{idx} + m_{idx+1}*i + m_{idx+2}*j + m_{idx+3}*k)(midx​+midx+1​∗i+midx+2​∗j+midx+3​∗k)

A is similarly produced as (aidx+aidx+1∗i+aidx+2∗j+aidx+3∗k)(a_{idx} + a_{idx+1}*i + a_{idx+2}*j + a_{idx+3}*k)(aidx​+aidx+1​∗i+aidx+2​∗j+aidx+3​∗k) for each corresponding pair in msg

sm is the sum of all the products of the pairs of msg and A. For example, the first product form is (m0+m1∗i+m2∗j+m3∗k)∗(a0+a1∗i+a2∗j+a3∗k)=sm0+sm1∗i+sm2∗j+sm3∗k(m_{0} + m_{1}*i + m_{2}*j + m_{3}*k) * (a_{0} + a_{1}*i + a_{2}*j + a_{3}*k) = {sm}_0 + sm_1*i + sm_2*j + sm_3*k(m0​+m1​∗i+m2​∗j+m3​∗k)∗(a0​+a1​∗i+a2​∗j+a3​∗k)=sm0​+sm1​∗i+sm2​∗j+sm3​∗k

(m0∗a0−m1∗a1−m2∗a2−m3∗a3)+(m0∗a1+m1∗a0+m2∗a3−m3∗a2)∗i+(m0∗a2−m1∗a3+m2∗a0+m3∗a1)∗j+(m0∗a3+m1∗a2−m2∗a1+m3∗a0)∗k(m_0*a_0-m_1*a_1-m_2*a_2-m_3*a_3)+(m_0*a_1+m_1*a_0+m_2*a_3-m_3*a_2)*i + (m_0*a_2-m_1*a_3+m_2*a_0+m_3*a_1)*j + (m_0*a_3+m_1*a_2-m_2*a_1+m_3*a_0)*k(m0​∗a0​−m1​∗a1​−m2​∗a2​−m3​∗a3​)+(m0​∗a1​+m1​∗a0​+m2​∗a3​−m3​∗a2​)∗i+(m0​∗a2​−m1​∗a3​+m2​∗a0​+m3​∗a1​)∗j+(m0​∗a3​+m1​∗a2​−m2​∗a1​+m3​∗a0​)∗k

Separating i,j,ki,j,ki,j,k and rewriting into matrix form, we get

[m0m1m2m3]⋅[a0a1a2a3−a1a0−a3a2−a2a3a0−a1−a3−a2a1a0]=[sm0sm1sm2sm3]\begin{bmatrix} m_0 & m_1 & m_2 & m_3 \end{bmatrix} \cdot \begin{bmatrix} a_0& a_1 & a_2 & a_3 \\ -a_1 &a_0 & -a_3 & a_2 \\ -a_2 &a_3 & a_0 & -a_1 \\ -a_3 & -a_2 & a_1 & a_0 \end{bmatrix} = \begin{bmatrix} sm_0 & sm_1 & sm_2 & sm_3 \end{bmatrix}[m0​​m1​​m2​​m3​​]⋅​a0​−a1​−a2​−a3​​a1​a0​a3​−a2​​a2​−a3​a0​a1​​a3​a2​−a1​a0​​​=[sm0​​sm1​​sm2​​sm3​​]
[m0m1⋯⋯mn]⋅[aiai+1ai+2ai+3−ai+1ai−ai+3ai+2−ai+2ai+3ai−ai+1−ai+3−ai+2ai+1ai]=[sos1s2s3]\begin{bmatrix} m0 & m1 & \cdots & \cdots & m_n \end{bmatrix} \cdot \begin{bmatrix} a_i& a_{i+1} & a_{i+2} & a_{i+3} \\ -a_{i+1} &a_{i} & -a_{i+3} & a_{i+2} \\ -a_{i+2} &a_{i+3} & a_{i} & -a_{i+1}\\ -a_{i+3} & -a_{i+2} & a_{i+1} & a_{i} \end{bmatrix} = \begin{bmatrix} s_o & s_1 & s_2 & s_3 \end{bmatrix}[m0​m1​⋯​⋯​mn​​]⋅​ai​−ai+1​−ai+2​−ai+3​​ai+1​ai​ai+3​−ai+2​​ai+2​−ai+3​ai​ai+1​​ai+3​ai+2​−ai+1​ai​​​=[so​​s1​​s2​​s3​​]

Generally we can solve for mim_imi​ using A.solve_left() in sage , but since the values of aia_iai​ are much bigger than the values of mim_imi​ , we have to solve it using LLL.

Usually while solving knapsack cryptosystems, we only need one vector (public key) to find the short vector. However, due to the size difference between mim_i mi​ and aia_iai​, we will be using all the four vectors that we have inorder for LLL to produce a vector that isn't too short and fits to our solution. In the CJLOSS algorithm, since the key consists of 0/1, we use ½ in the last row to increase the density. Similarly, since mim_imi​ are ascii characters, we approximate that they are in the range {60,100} and we use 80 in the last row

[10⋯⋯⋮aiai+1ai+2ai+30⋱0⋮⋮−ai+1ai−ai+3ai+2⋮⋯⋱⋮⋮−ai+2ai+3ai−ai+100⋯⋱⋯−ai+3−ai+2ai+1ai0⋯⋯⋯1⋮⋮⋮⋮80⋯⋯⋯⋯s0s1s2s3]\begin{bmatrix} 1 & 0 & \cdots & \cdots & \vdots & a_{i} & a_{i+1} & a_{i+2} & a_{i+3} \\ 0 & \ddots & 0 & \vdots & \vdots & -a_{i+1} & a_{i} & -a_{i+3} & a_{i+2} \\ \vdots & \cdots & \ddots & \vdots & \vdots & -a_{i+2} & a_{i+3} & a_i & -a_{i+1} \\ 0 & 0 & \cdots & \ddots & \cdots & -a_{i+3} & -a_{i+2} & a_{i+1} & a_i \\ 0 & \cdots & \cdots & \cdots & 1 & \vdots & \vdots & \vdots & \vdots \\ 80 & \cdots & \cdots & \cdots & \cdots & s_{0} & s_{1} & s_2 & s_3 \end{bmatrix}​10⋮0080​0⋱⋯0⋯⋯​⋯0⋱⋯⋯⋯​⋯⋮⋮⋱⋯⋯​⋮⋮⋮⋯1⋯​ai​−ai+1​−ai+2​−ai+3​⋮s0​​ai+1​ai​ai+3​−ai+2​⋮s1​​ai+2​−ai+3​ai​ai+1​⋮s2​​ai+3​ai+2​−ai+1​ai​⋮s3​​​

After applying LLL on this matrix we get all the values of mim_i mi​, and after converting to ascii we get the flag.

Full Implementation