Try   HackMD

TSG CTF 2020 Beginner's Crypto

問題

配布ファイルは以下の通り。

assert(len(open('flag.txt', 'rb').read()) <= 50)
assert(str(int.from_bytes(open('flag.txt', 'rb').read(), byteorder='big') << 10000).endswith('1002773875431658367671665822006771085816631054109509173556585546508965236428620487083647585179992085437922318783218149808537210712780660412301729655917441546549321914516504576'))

フラグを整数値に変換して左に10000ビットシフトした値の末尾175桁の値が与えられる。すなわち

c=flag210000mod10175

からflagを復元するのが本問の趣旨である。

解法

F10175での
210000
の逆元を取りたくなるが、これは互いに素でないため存在しない。

正しくは

c=flag210000mod5175

を考えて、オイラーの定理より

(210000)1mod5175=(210000)φ(5175)1=(210000)517551741

を計算して

flag=c(210000)1mod5175

よりflagを得る。

25650<5175であるためこれが正しいフラグである。

ソルバ

c = 1002773875431658367671665822006771085816631054109509173556585546508965236428620487083647585179992085437922318783218149808537210712780660412301729655917441546549321914516504576
mod = 5 ** 175
phi = 5 ** 175 - 5 ** 174
inv = pow(pow(2, 10000, mod), phi - 1, mod)
print(((c * inv) % mod).to_bytes(50, byteorder='big'))

答えはTSGCTF{0K4y_Y0U_are_r3aDy_t0_Go_aNd_dO_M0r3_CryPt}である。

別解1 (by @naan112358)

c=flag210000mod5175
とするところまでは同じ。
2x={2x  (2x<M)2xM (2xM)modM

において、上なら偶数で下なら奇数であることを利用すると、

def divide_by_two(x):
    if x % 2 == 0:
        return x // 2
    else:
        return (x + mod) // 2

のようにして2で割る操作を実現できる。
これを10000回繰り返せばflagを得られる。

ソルバ

mod = 5 ** 175
c = 1002773875431658367671665822006771085816631054109509173556585546508965236428620487083647585179992085437922318783218149808537210712780660412301729655917441546549321914516504576 % mod

def divide_by_two(x):
    if x % 2 == 0:
        return x // 2
    else:
        return (x + mod) // 2

for i in range(10000):
    c = divide_by_two(c)

print(c.to_bytes(50, byteorder='big'))

別解2 (by @satos___jp)

C=flag210000

とおく (modが無いことに注意)。

いま

C の末尾175桁
c
がわかっているので、

C=c010175+c

とおける。

ここで、

C を2進数表記したときに末尾10000桁が0になるような
c0
を求める。

いま、

c0
c0=c0+2x
に置き換えて得られる
C
について考えてみる。このとき

C=c010175+c=(c0+2x)10175+c=c010175+c+2x10175=C+51752x+175

より、

C を2進数で表記したときの末尾
x+175
bitは変化しない。

よって、

c0 の値を下のビットから定めることによって、末尾10000bitが0であるような
C
を求めることができる。

flagは

C を右に10000bitシフトすると得られる。

ソルバ

c = 1002773875431658367671665822006771085816631054109509173556585546508965236428620487083647585179992085437922318783218149808537210712780660412301729655917441546549321914516504576
c0 = 0
bit = 1
mask = 1 << 175
while '1' in bin(c0 * 10 ** 175 + c)[-10000:]:
  if (c0 * 10 ** 175 + c) & mask != 0:
    c0 += bit
  bit <<= 1
  mask <<= 1
C = c0 * 10 ** 175 + c
flag = (C >> 10000).to_bytes(50, byteorder='big')
print(flag)