记录下在docker上部署sage的密码学交互题目的过程。

0x1. Starting from cv

以下是maple佬出的一道题的deploy文件,给选手的py附件和deploy的py附件是同一个文件,非常方便,这也是我选它做模板的理由:

challenge.py

from typing import Tuple
from Crypto.Util.number import *
from gmpy2 import powmod
import os

flag = os.environb.get(b"FLAG", b"TSJ{test_flag}")

def pow(a: int, b: int, c: int) -> int:
    # gmpy2.powmod is much faster than pow
    return int(powmod(a, b, c))

def getPrimeOrderGroup(bits) -> Tuple[int, int, int]:
    """
    Generate a prime p with large prime factor q and a generator g
    """
    while True:
        q = getPrime(bits)
        for i in range(2, 257, 2):
            p = q * i + 1
            if isPrime(p):
                g = pow(getRandomRange(2, p), i, p)
                if g != 1:
                    assert pow(g, q, p) == 1
                    return p, q, g

class RSA:
    def __init__(self, bits):
        self.p = getPrime(bits // 2)
        self.q = getPrime(bits // 2)
        self.n = self.p * self.q
        self.e = 65537
        self.d = pow(self.e, -1, (self.p - 1) * (self.q - 1))

    def encrypt(self, m: int) -> int:
        return pow(m, self.e, self.n)

    def decrypt(self, c: int) -> int:
        return pow(c, self.d, self.n)

    def __str__(self) -> str:
        e = self.e
        n = self.n
        return f"RSA({n}, {e})"

class ElGamal:
    def __init__(self, bits):
        self.p, self.q, self.g = getPrimeOrderGroup(bits)
        self.x = getRandomRange(2, self.q)
        self.y = pow(self.g, self.x, self.p)

    def encrypt(self, m: int) -> Tuple[int, int]:
        r = getRandomRange(2, self.q)
        s = pow(self.y, r, self.p)
        c1 = pow(self.g, r, self.p)
        c2 = (s * m) % self.p
        return c1, c2

    def decrypt(self, c1: int, c2: int) -> int:
        s = pow(c1, self.x, self.p)
        m = (pow(s, -1, self.p) * c2) % self.p
        return m

    def __str__(self) -> str:
        p = self.p
        g = self.g
        y = self.y
        return f"ElGamal({p}, {g}, {y})"

elg = ElGamal(1024)
rsa = RSA(1024)

print("Welcome to Cipher Switching Service!")
print()
print("This is our public keys:")
print(rsa)
print(elg)
print()
print("And this is some information about the encrypted flag:")
print(f"{len(flag) = }")
flag += os.urandom(96 - len(flag))  # random padding
flagenc = rsa.encrypt(bytes_to_long(flag))
print(f"{flagenc = }")
print()

for _ in range(1337):
    print("1. RSA to ElGamal")
    print("2. ElGamal to RSA")
    print("3. Quit")
    choice = int(input("> "))
    if choice == 1:
        c = int(input("c = "))
        print(elg.encrypt(rsa.decrypt(c)))
    elif choice == 2:
        c1 = int(input("c1 = "))
        c2 = int(input("c2 = "))
        print(rsa.encrypt(elg.decrypt(c1, c2)))
    elif choice == 3:
        print("Bye")
        break
    else:
        print(f"Unknown choice: {choice}")
else:
    print("To prevent abuse, we limit each session to have only 1337 attempts at most.")

Dockerfile

FROM python:slim

RUN apt-get update && apt-get install -yqq socat

RUN pip install gmpy2 pycryptodome
RUN mkdir /app

WORKDIR /app
COPY challenge.py .

CMD ["/bin/sh", "-c", "socat TCP-LISTEN:8763,fork,reuseaddr EXEC:'python challenge.py',stderr"]

docker-compose.yml

version: '3.9'
services:
  chall:
    build:
      context: .
    init: true
    environment:
      - FLAG=TSJ{a_weird_oracle?}
    ports:
      - 8763:8763

在这个基础上修改成支持sage的python,首先就要解决安装sage的问题。主要遇到的问题有二:

嘛,解决起来也简单,前者把基础镜像改成debian

FROM debian:stable-slim

后者换源

RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources

就能非常爽滑的操作apt-get update -y sagemath了。