deploy_sage_on_docker

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

0x1 Starting from cv

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

challenge.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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

1
2
3
4
5
6
7
8
9
10
11
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

1
2
3
4
5
6
7
8
9
10
version: '3.9'
services:
chall:
build:
context: .
init: true
environment:
- FLAG=TSJ{a_weird_oracle?}
ports:
- 8763:8763

0x2 Deploy

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

  • debian.org连不上,apt-get报错
  • sagemath安装过慢

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

1
FROM debian:stable-slim

后者换源

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

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

接下来的步骤大差不差,唯一有点卡的步骤是安装第三方库,按下面模板写就没问题了:

1
2
3
RUN apt install -y python3-pip

RUN pip install pycryptodome --break-system-packages

操作基本完成,用qwb2022的一道题测试一下:

challenge.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from sage.all import *
from sage.modules.free_module_integer import IntegerLattice
from Crypto.Cipher import AES
from base64 import b64encode
from hashlib import *
import signal

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

n = 75
m = 150
r = 10
N = 126633165554229521438977290762059361297987250739820462036000284719563379254544315991201997343356439034674007770120263341747898897565056619503383631412169301973302667340133958109

def gen(n, m, r, N):
t1 = [ZZ.random_element(-2^15, 2^15) for _ in range(n*m)]
t2 = [ZZ.random_element(N) for _ in range(r*n)]
B = matrix(ZZ, n, m, t1)
L = IntegerLattice(B)
A = matrix(ZZ, r, n, t2)
C = (A * B) % N
return L, C

def pad(s):
return s + (16 - len(s) % 16) * b"\x00"

signal.alarm(60)
L, C = gen(n, m, r, N)
print(C)
key = sha256(str(L.reduced_basis[0]).encode()).digest()
aes = AES.new(key, AES.MODE_ECB)
ct = b64encode(aes.encrypt(pad(flag))).decode()
print(ct)

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM debian:stable-slim
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources

RUN apt-get update
RUN apt-get install -y socat
RUN apt-get install -y sagemath

RUN apt install -y python3-pip

RUN pip install pycryptodome --break-system-packages

RUN mkdir /app

WORKDIR /app
COPY challenge.py .

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

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
version: '0.0'
services:
chall:
build:
context: .
init: true
environment:
- FLAG=flag{OK!_secret_number_is_114514}
ports:
- 23333:23333

最终效果:

Untitled