# OT or NOT OT - zer0pts CTF 2021
###### tags: `zer0pts CTF 2021` `crypto`
## 概要
サーバーに繋ぐ式の問題。
512ビットの素数$p$と、乱数$r, s \in [2, p-1]$が生成される。
ここで$r$は$p$を法とした平方剰余にならないよう、$s$はなるように作られる。
まずランダムな鍵とIVでAESを初期化し、フラグを暗号化する。
最初にこのIVと暗号化されたフラグ、素数$p$、そして鍵のビット長が貰える。
その後、鍵$k$を$k_0$として、以下の処理を$k_i > 0$の間実行する
1. 過去に入力されていない$g > 1$をユーザー入力として受け取る
2. さらに、$g_a, g_b, g_{c_0}, g_{c_1}$を受け取る(いずれも1より大きく、$g_{c_0} \neq g_{c_1}$である)
3. $a, b$をそれぞれ$k_i$の下位1ビット、2ビット目とする
4. $k_{i+1} = k_i >> 2$
5. $m_0 = r^{a + 1}s \mod p$
6. $m_1 = r^{b + 1}s \mod p$
7. $w = \left(g_a^r \mod p \right) \left(g^s \mod p \right) \mod p$
8. $z_0 = \left((g_{c_0}^r \mod p)(g_b^s \mod p)\mod p \right) \oplus {m_0}$
9. $z_1 = \left((g_{c_1}^r \mod p)(g_b^s \mod p)\mod p \right) \oplus {m_1}$
各イテレーションにおける$w, z_0, z_1$の値が貰える。
## 解法
XORしてるところとか意味不明感がある。
今$r$は平方剰余でないので、求めるビットが0ならば$m$は平方剰余にならず、1ならば平方剰余になる。これを使えば、とりあえず$m_0, m_1$が分かったときに$a, b$の値を知ることができる。
$m_0, m_1$を知るためには$z_0, z_1$を上手く操作する必要がある。理想的には$z_0 = w \oplus m_0, z_1 = w \oplus m_1$とかになったら嬉しい。
$w, z_0 \oplus m_0, z_1 \oplus m_1$は似た形をしているので、これは実現可能なのだが、$g_{c_0} \neq g_{c_1}$という条件が厳しい。これでは1回にどちらか一方のビットしか復元できないように思える。
しかし、$r$が偶数のとき(?)に$g_a = g_{c_0} = g_{c_1} - p$と定めると、$g_a^r\mod p = g_{c_0}^r\mod p = g_{c_1}^r\mod p$となる。暗号担当じゃないので数学的な理由は知らないが、sageで適当に試してたらそうなった。(Legendre symbolが-1なのが関係してそう?)
これにより、両方のビットが復元できるので、あとはAESでフラグを復号してやれば良い。
## コード
```python=
from ptrlib import *
from Crypto.Cipher import AES
import base64
def legendre(x, p):
a = pow(x, (p-1)//2, p)
if a == 1:
return 1
elif a == 0:
return 0
else:
return -1
sock = Socket("localhost", 10130)
#sock = Process(["python", "./server.py"])
b = base64.b64decode(sock.recvlineafter("flag: "))
iv, cipher = b[:AES.block_size], b[AES.block_size:]
p = int(sock.recvlineafter("p = "))
keylen = int(sock.recvlineafter("() = "))
k = ''
g = 2
for i in range((keylen + 1) // 2):
print(f"{i} / {(keylen + 1) // 2}")
sock.sendlineafter("g = ", str(g))
sock.sendlineafter("ga = ", str(g))
sock.sendlineafter("gb = ", str(g))
sock.sendlineafter("gc0 = ", str(g))
sock.sendlineafter("gc1 = ", str(p - g))
z0 = int(sock.recvlineafter("z0 = "))
z1 = int(sock.recvlineafter("z1 = "))
w = int(sock.recvlineafter("w = "))
m0 = z0 ^ w
m1 = z1 ^ w
k = ('0' if legendre(m0, p) == -1 else '1') + k
k = ('0' if legendre(m1, p) == -1 else '1') + k
g += 1
key = int.to_bytes(int(k, 2), 32, 'big')
aes = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
flag = aes.decrypt(cipher)
print(flag)
sock.close()
```
## 感想・意見
- 難易度感わからんけど結構考えた(mediumくらい?)
- これ以上簡単な解法は見つからなかった