問題概要
picoCTFの「EVEN RSA CAN BE BROKEN???」という問題の解説記事です。
- カテゴリ: Cryptography
- 難易度: Easy
問題文
解説
ステップ1: picoCTFのサーバーに接続
まず、問題文に記載されているサーバーに接続します。
ncコマンドを使用して、指定されたホストとポートに接続します。$ nc verbal-sleep.picoctf.net xxxxx
N: 19717589578836383613288380933302915055460046872338736531560971952640046688445811420337222036918341147335263071128959761369934032574988010574766426472259018
e: 65537
cyphertext: 103383222591393288699291000626233579128644528807366610545028605727542178516340706562756939242107082305732087684752037041734072285998617891769121977948199
※ポート番号は問題文に記載されているものを使用してください。
ステップ2: 提供されたencrypt.pyを確認
この問題では、暗号化の処理が書かれた
encrypt.py が提供されています。
まずは「何が出力されているのか」「どのようなRSAなのか」を整理します。提供コード(重要な部分)
from Crypto.Util.number import bytes_to_long, inverse
from setup import get_primes
e = 65537
def gen_key(k):
p,q = get_primes(k//2)
N = p*q
d = inverse(e, (p-1)*(q-1))
return ((N,e), d)
def encrypt(pubkey, m):
N,e = pubkey
return pow(bytes_to_long(m.encode('utf-8')), e, N)
def main(flag):
pubkey, _privkey = gen_key(1024)
encrypted = encrypt(pubkey, flag)
return (pubkey[0], encrypted)
このコードから、サーバーが出力している値は以下の通りです。
N: RSAの法(modulus)。N = p * qe: 公開指数(ここでは固定で65537)cyphertext: 平文(フラグ)を整数化してc = pow(m, e, N)を計算したもの
ステップ3: N が偶数であることに着目
出力された
N の末尾が ...18 になっているため、N は偶数です。RSAでは通常、
p と q は「大きな奇数の素数」を選ぶため、N = p * q は奇数になります。
ここで N が偶数ということは、次のいずれかです。pまたはqが偶数- 偶数の素数は
2しか存在しない
つまり、
p = 2 または q = 2 が強く疑われます。ステップ4: p = 2として秘密鍵を復元
p = 2 とすると、もう片方は q = N // 2 です。RSAでは次を使って秘密指数
d を計算できます。phi = (p - 1) * (q - 1)(オイラーのトーシェント)dは「eの逆元(modphi)」として求める
Pythonでは
pow(e, -1, phi) で「e の逆元(mod phi)」を計算できます。ステップ5: 復号してフラグを取得
ここは少し分かりづらいので、手順を細かく分けて整理します。
結論から言うと、提供された
encrypt.py を修正する必要はありません。
encrypt.py は「サーバー側がどう暗号化しているか」を理解するための参考コードなので、
手元では 別の復号用スクリプト を作って解きます。5-1. まずは値を手元にメモする
nc の出力から、次の3つをコピーします。Necyphertext(ここではcとして扱います)
5-2. 復号スクリプトを作成して実行(ファイルを作る方法)
例えば
decrypt.py を作り、以下を貼り付けます。N = 19717589578836383613288380933302915055460046872338736531560971952640046688445811420337222036918341147335263071128959761369934032574988010574766426472259018
e = 65537
c = 103383222591393288699291000626233579128644528807366610545028605727542178516340706562756939242107082305732087684752037041734072285998617891769121977948199
p = 2
q = N // 2
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
m = pow(c, d, N)
pt = m.to_bytes((m.bit_length() + 7) // 8, 'big')
print(pt.decode())
実行します。
$ python3 decrypt.py
出力例
picoCTF{xxxxx}
※フラグはマスクしています。
5-3. 1回だけならCLIに直接流し込む方法(ワンライナー)
ファイルを作らずに、そのまま標準入力でPythonに渡す方法もあります。
$ python3 - <<'PY'
N = 19717589578836383613288380933302915055460046872338736531560971952640046688445811420337222036918341147335263071128959761369934032574988010574766426472259018
e = 65537
c = 103383222591393288699291000626233579128644528807366610545028605727542178516340706562756939242107082305732087684752037041734072285998617891769121977948199
p = 2
q = N // 2
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
m = pow(c, d, N)
pt = m.to_bytes((m.bit_length() + 7) // 8, 'big')
print(pt.decode())
PY
出力例
picoCTF{xxxxx}
※フラグはマスクしています。
使用したコマンドの軽い解説
nc
nc <ホスト> <ポート>
指定したホスト・ポートにTCP接続するコマンドです。 今回のように、接続すると値が表示されるタイプの問題でよく使います。
まとめ
picoCTFの「EVEN RSA CAN BE BROKEN???」問題では、出力された
N が偶数である点が重要でした。▼ポイントは以下の通りです。
- RSAの
Nが偶数の場合、素因数に2が含まれる可能性が高い p = 2と仮定するとq = N // 2で簡単に分解できるphiを求めてdを復元すれば、復号してフラグを取り出せる
NEXT
次におすすめ
読み終わったら、そのまま次へ
【picoCTF】interencdec - Base64とシーザー暗号の多段エンコード解読
カテゴリ: Cryptography難易度: Easy#picoCTF
次の記事へ →
同じカテゴリ/難易度/picoCTFでの表示順が近い記事を優先しておすすめしています。