from Crypto.Util.number import bytes_to_long, getPrime, inverse
import os
flag = os.environ.get("FLAG", "Alpaca{REDACTED}").encode()
p = getPrime(512)
q = getPrime(512)
n = p * q
phi = (p - 1) * (q - 1)
e = 65537
d = inverse(e, phi)
c = pow(bytes_to_long(flag), e, n)
print(f"{n = }")
print(f"{e = }")
print(f"{c = }")
print("Give me a ciphertext. I will decrypt it, unless it is the flag ciphertext.")
while True:
try:
x = int(input("> "))
except:
print("invalid")
exit(0)
if not 0 <= x < n:
print("out of range")
exit(0)
if x == c:
print("no")
exit(0)
m = pow(x, d, n)
print(m)
以降、flag を $m$ とおく。また法を $n$ とする(以降、可能な限り省略する)
プログラムより以下がわかる。
ここで、(オイラーの法則) $ed \equiv 1 \pmod{\varphi(n)}$ であることをなんとか利用して $m$ を求めたい。 このために、$f(x) = x^d$ の $x$ を工夫して、$x$ に ${}^e$ をつけることで ${}^d$ を排除したい。 また $x$ には $m$ を入れ込みたい。
制約上 $x = m^e$ ($= c$) は使えないが、 例えば $x \equiv c\cdot 2^e \pmod{n}$ は求められる。 定義から $c \equiv m^e \pmod{n}$ より、
$$ x \equiv m^e\cdot 2^e \pmod{n} $$
と変形できる。この $x$ を入れると得られる値は
$$ \begin{aligned} f(x) &= x^d \\ &\equiv \left(m^e\cdot 2^e\right)^d \pmod{n} \\ &= (m\cdot 2)^{ed} \\ &\equiv m\cdot 2 \pmod{n} \\ &= 2m \end{aligned} $$
である。 よって、$m$ (flag) は $2m$ を2で割れば求められる(法を $n$ とした2の逆元をかければよい)。
プログラムには
$$ x = c\cdot 2^e \% n $$
を入力する。