---
# System prepended metadata

title: NHÓM VÀ CÁC KIẾN THỨC LIÊN QUAN
tags: [TASK 2 KCSC]

---

# NHÓM VÀ CÁC KIẾN THỨC LIÊN QUAN

### Phép toán hai ngôi
Một phép toán hai ngôi trên X là một ánh xạ ký hiệu là ``*``.

``x*y`` không phải là ``x.y`` mà có thể là bất kỳ phép toán mà ta định nghĩa ví dụ như là ``x + 2y`` hoặc là ``x+y``.

Ví dụ: Phép trừ trong tập N không là một phép toán hai ngôi vì ``1 - 2 ∉ N``, nhưng mà là một phép toán hai ngôi trên tập Z.


### NHÓM
Nhóm là một trong những thành phần quan trọng trong cấu trúc đại số.

Cho G là 1 tập khác rỗng cùng với phép toán hai ngôi (*). Khi đó G được gọi là 1 nhóm nếu thỏa mãn các điều kiện sau:
-    Phép toán hai ngôi (*) có tính chất kết hợp:
\begin{equation}
\forall x, y, z \in G, (x * y) * z = x * (y * z)
\end{equation}


-    Phép toán hai ngôi (*) có phần tử trung lập e ∈ G:  
\begin{equation}
\forall x \in G, x * e = e * x = x
\end{equation}

-    Mọi x ∈ G có phần tử đối xứng (nghịch đảo) ∈ G: 
\begin{equation}
\forall x \in G, x * x^{-1} = e
\end{equation}



Lưu ý:
-    Nếu (*) chỉ thỏa mãn tính chất đầu tiên, thì G được gọi là nửa nhóm.
-    Nếu (*) chỉ thỏa mãn tính chất thứ nhất và thứ hai, thì G được gọi là vị nhóm.
-    Nếu (*) có thêm tính giao hoán thì G được gọi là nhóm Abel hay còn gọi là nhóm giao hoán. 
\begin{equation}
\forall x, y \in G, x * y = y * x
\end{equation}


Ví dụ: 
-    Ví dụ 1:
    -    ($Z^{+}$) là tập $Z$ với phép toán hai ngôi là phép cộng là nhóm Abel. Tương tự với ($Q^{+}$), ($R^{+}$). Lấy $a, b, c \in \mathbb{Z}$ thì:
    -    (a + b) + c = a + (b + c)
    -    a + 0 = 0 + a = a (Phần tử trung lập là 0)
    -    a + (-a) = (-a) + a = 0 (Phần tử nghịch đảo là -a)
    -    Thêm tính chất giao hoán a + b = b + a
        --> ($Z^{+}$) là nhóm Abel.

-    Ví dụ 2:
    -    Gọi ($Z{n}$) là lớp thặng dư mod n $\{0, 1, 2, \dotsb, n-1\}$. ($Z^{+}_{n}$) cũng là một lớp giao hoán.

-    Ví dụ 3:
    -    Gọi $a, b, c \in \mathbb{Q^{*}}$ (tập hợp không có số 0), ta thấy ($Q^{*}_{.}$) là nhóm nhân giao hoán bởi vì:
    -    (a . b) . c = a . (b . c)
    -    a . 1 = 1 . a = a
    -    $a \cdot \frac{1}{a} = \frac{1}{a} \cdot a = 1$
    -    Thế nhưng $Z^{*}$ lại không phải là nhóm vì không có phần tử đối xứng (5 ∈ $Z^{*}$ nhưng $\frac{1}{5}$ ∉ $Z^{*}$)

Định nghĩa: Cho G là một nhóm, số phần tử của G được gọi là cấp của G, ký hiệu là $|G|$.
-    Nếu $|G|$ = ∞ --> G có vô cùng phần tử, gọi là nhóm vô hạn.
-    Nếu $|G|$ = n --> G có n phần tử, gọi là nhóm hữu hạn.


Cấp của một phần tử a ∈ G là số nguyên dương n nhỏ nhất sao cho

<math xmlns="http://www.w3.org/1998/Math/MathML">
  <munder>
    <mrow data-mjx-texclass="OP">
      <munder>
        <mrow>
          <mi>a</mi>
          <mo>&#x2217;</mo>
          <mo>.</mo>
          <mo>.</mo>
          <mo>.</mo>
          <mo>&#x2217;</mo>
          <mi>a</mi>
        </mrow>
        <mo>&#x23DF;</mo>
      </munder>
    </mrow>
    <mrow data-mjx-texclass="ORD">
      <mtext>n l&#x1EA7;n</mtext>
    </mrow>
  </munder>
  <mo>=</mo>
  <mi>e</mi>
</math>

Trong đó e là phần tử đơn vị của nhóm. Nếu không tồn tại n như vậy thì a được gọi là cấp vô hạn. Mọi phần tử của nhóm hữu hạn đều có cấp hữu hạn.
Ký hiệu của cấp của một phần tử a là $|a|$ hoặc là ord(a). Lưu ý là $|a|$ không phải là $|G|$ nha.
Ví dụ:
-    10 ∈ $Z^{+}_{25}$. Giờ ta sẽ tìm $|10|$ là bao nhiêu. Ta thấy rằng (10x5) = 50 mod 25 == 0 (phần tử đơn vị) --> |10| = 5
-    3 ∈ $Z^{+}_{6}$. Tương tự thế, (2x3) = 6 mod 6 = 0 --> |3| = 2.
-    Ta có nhóm nhân $Z^{*}_{5}$ = {1, 2, 3, 4}. Ta thấy rằng ord(2) = 4 vì 16 mod 5 = 1 = e.



**Lưu ý: (*) là phép toán hai ngôi chứ không phải là phép nhân.**

**Lưu ý: (*) là phép toán hai ngôi chứ không phải là phép nhân.**

**Lưu ý: (*) là phép toán hai ngôi chứ không phải là phép nhân.**

### Nhóm con
Cho G là một nhóm. Tập con $X \neq \emptyset$ của G là một nhóm con của G nếu X cùng với phép toán thu hẹp trên X làm thành một nhóm. Hiểu đơn giản là G sau khi giảm một số phần tử thì ra X.

Ví dụ: Ta biết ($Q^{+}$) là một nhóm. Bây giờ thu hẹp tập Q thành tập Z thì ($Z^{+}$) cũng là một nhóm. Khi đó, ta nói ($Z^{+}$) là một nhóm con của ($Q^{+}$). Còn ($Q^{+}$) được gọi là nhóm mẹ.

Điều kiện để tập con X là con của G là:
-    X khác rỗng
-    Với mọi a, b ∈ X, thì a*b ∈ X
-    Nếu a ∈ X thì phần tử đối xứng $a^{-1} \in X$


### Nhóm Cyclic
Một nhóm G được gọi là nhóm Cyclic nếu trong G tồn tại phần tử g sao cho $G = \langle g \rangle$ = {g^n với mọi số nguyên n}. 
Ví dụ:
-    $\langle a \rangle = \{ \ldots, a^{-2}, a^{-1}, 1, a, a^2, \ldots \}$
-    Với nhóm nhân, ta lấy a ∈ G, ta có $\langle a \rangle = \{ \ldots, a^{-2}, a^{-1}, 1, a, a^2, \ldots \}$. Nếu $G = \langle a \rangle$  thì G là Cyclic group.
-    Với nhóm cộng, ta lấy a ∈ G, ta có $\langle a \rangle = \{ \ldots, -3a, -2a, -a, 0, a, 2a, 3a, \ldots \}$.

Cho (G,*) là một nhóm. Tồn tại 1 phần tử g sao cho ord(g) = |G| thì G được gọi là nhóm cyclic, phần tử g đó được gọi là phần tử sinh hoặc là phần tử nguyên thủy.

Ví dụ: Xét nhóm nhân $Z_7^* = \{1, 2, 3, 4, 5, 6\}$ và phần tử sinh g = 3, ta nhận thấy rằng ord(3) = 6 = |$Z_7^*$| và nhóm nhân sinh bởi 3 là $\{3, 9, 27, 81, 243, 729\} = \{3, 2, 6, 4, 5, 1\} = Z_7^*$.

### Vành và Trường
A được gọi là một vành nếu trong A được trang bị hai phép toán hai ngôi (+) và nhân (.) thỏa mãn các tiên đề sau:
-    (A,+) lập thành một nhóm Abel có phần tử đơn vị là 0.
-    (A, .) lập thành một nửa nhóm (tức là chỉ có tính chất kết hợp).
-    Phép nhân trong A phân phối được với phép cộng của A, tức là 
$(x + y) \cdot z = x \cdot z + y \cdot z$
$z \cdot (x + y) = z \cdot x + z \cdot y$   


Vành A được gọi là **vành có đơn vị** nếu phép nhân A có phần tử đơn vị khác 0.
Vành A được gọi là **vành giao hoán** nếu phép nhân A có tính giao hoán.
A được gọi là trường nếu A là một vành giao hoán có đơn vị khác 0 và mọi phần tử khác 0 của A đều có tính khả nghịch với phép nhân.

![image](https://hackmd.io/_uploads/ByNIhTwcp.png)

Ví dụ: Xét tập số nguyên $Z$ với phép công và phép nhân thông thường.
-    Tính kết hợp: $(x + y) + z = x + (y + z)$ $\forall x, y, z \in \mathbb{Z}$
-    Phần tử đơn vị là 0.
-    Phần tử đối xứng: $x + (-x) = 0$
-   Tính giao hoán: $x+y = y + x,$ $\forall x,y \in \mathbb{Z}$ 
$(Z,.)$ là vị nhóm giao hoán, phần tử đơn vị là 1.
-    Tính kết hợp: $(x.y).z = z.(y.z)$ $\forall x,y,z \in \mathbb{Z}$
-    Phần tử đơn vị là 1.
-    Tính giao hoán $x.y = y.x, \forall x,y \in \mathbb{Z}.$
-    Phần tử $a\neq0$, $a \in \mathbb{Z}$ không tồn tại phần tử nghịch đảo $\frac{1}{a} \in \mathbb{Z}$
-    Thỏa mãn tính phân phối $x.(y+z) = x.y + x.z, z.(x+y)= z.x + z.y$
--> $(\mathbb{Z,+,.})$ là **Vành giao hoán đơn vị là 1.**

Với $(\mathbb{Q,.})$ $(\mathbb{R,.})$ $(\mathbb{C,.})$ cũng như thế nhưng mà có phần tử nghịch đảo.
--> $(\mathbb{Q,+,.})$ $(\mathbb{R,+,.})$ $(\mathbb{C,+,.})$ là **Trường**


Tập hợp lớp thặng dư $(\mathbb{Z}_n)$ modulo n với hai phép cộng thông thường:
-    $(Z_n,+)$ là nhóm Abel, phần tử trung hòa là $\overline{0}$
    -    Tính kết hợp $(\overline{a} + \overline{b}) + \overline{c} = \overline{a} + (\overline{b} + \overline{c}) = \overline{a + b + c},\ \forall a,b,c \in \mathbb{Z}$ 
    -    Phần tử đơn vị là $\overline{0}$
    -    Phần tử đối xứng là $\overline{-a}$
    -    Tính giao hoán: $\overline{a} + \overline{b} = \overline{b} + \overline{a} = \overline{a + b} = \overline{b + a}$

### Vành đa thức hữu hạn biến
Cho A là một vành giao hoán có đơn vị là 1.
Với một ký hiệu $X$, gọi $A[X]$ là một tập các biểu thức có dạng:
$f(X)= a_0 + a_1X + a_2X^2+ ... + a_nX^n$. Trong đó $a_i \in A$ với mọi  $0 \leq i \leq n, n \in \mathbb{N}$

Giả sử $f(X) = a_0 + a_1X +...+ a_nX^n$, $g(X) = b_0 + b_1X + ... + b_mX^m \in A[X]$

Trong $A[X]$ phép cộng như sau:
$f(X) + g(X) = \sum_{i=0}^{n}(a_i + b_i)X^i$

Phép nhân như sau:
$f(X) \cdot g(X) = \prod_{i=0}^{n+m}\left(\sum_{j=0}^{i}a_{(i-j)} \cdot b_j\right) \cdot X^i$

# DIFFIE HELLMAN 
### Tóm tắt thuật toán
Alice và Bob sử dụng nhóm nhân số nguyên tố modulo p, trong đó p là số nguyên tố, và g là phần tử sinh. p và g sẽ là khóa công khai.
Alice sẽ chọn 1 số bí mật a. Sau đó sẽ tính giá trị A = pow(g, a, p).
Bob cũng thế, sẽ tìm 1 giá trị bí mật b và B = pow(g, b, p).

Giờ 2 người sẽ trao đổi A và B cho nhau.

Khi đó thì Alice sẽ thu được 1 giá trị ``s = pow(B, a, p)`` và Bob cũng thế, cũng sẽ thu được giá trị ``s = pow(A, b, p)`` vì $(g^a)^b \equiv (g^b)^a \pmod{p}$

![image](https://hackmd.io/_uploads/r1Y2ZrS9p.png)


### Diffie-Hellman Starter 1

![image](https://hackmd.io/_uploads/BkjVAVScT.png)


```
from Crypto.Util.number import*

print(inverse(209,991))
```
**Flag: 569**

### Diffie-Hellman Starter 2

![image](https://hackmd.io/_uploads/S119RES5p.png)

Chall bắt ta tìm phần tử sinh g nhỏ nhất trong trường Fp với p = 28151.

Ta dùng sage để giải challenge này.

![image](https://hackmd.io/_uploads/rJ6f1SS96.png)

**Flag: 7**


### Diffie-Hellman Starter 3

![image](https://hackmd.io/_uploads/S1gBkHSq6.png)

Chall này đơn giản thui, giờ tính ``pow(g,a,p)``.

![image](https://hackmd.io/_uploads/BkCuJSHca.png)

**Flag: 1806857697840726523322586721820911358489420128129248078673933653533930681676181753849411715714173604352323556558783759252661061186320274214883104886050164368129191719707402291577330485499513522368289395359523901406138025022522412429238971591272160519144672389532393673832265070057319485399793101182682177465364396277424717543434017666343807276970864475830391776403957550678362368319776566025118492062196941451265638054400177248572271342548616103967411990437357924**

### Diffie-Hellman Starter 4

![image](https://hackmd.io/_uploads/H1qLfrSca.png)

Như lý thuyết ở trên, ``s = pow(A,b,p)``. Giờ dùng python để tìm lại thui.


```
g = 2
p = 2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919
A = 70249943217595468278554541264975482909289174351516133994495821400710625291840101960595720462672604202133493023241393916394629829526272643847352371534839862030410331485087487331809285533195024369287293217083414424096866925845838641840923193480821332056735592483730921055532222505605661664236182285229504265881752580410194731633895345823963910901731715743835775619780738974844840425579683385344491015955892106904647602049559477279345982530488299847663103078045601
b = 12019233252903990344598522535774963020395770409445296724034378433497976840167805970589960962221948290951873387728102115996831454482299243226839490999713763440412177965861508773420532266484619126710566414914227560103715336696193210379850575047730388378348266180934946139100479831339835896583443691529372703954589071507717917136906770122077739814262298488662138085608736103418601750861698417340264213867753834679359191427098195887112064503104510489610448294420720
B = 518386956790041579928056815914221837599234551655144585133414727838977145777213383018096662516814302583841858901021822273505120728451788412967971809038854090670743265187138208169355155411883063541881209288967735684152473260687799664130956969450297407027926009182761627800181901721840557870828019840218548188487260441829333603432714023447029942863076979487889569452186257333512355724725941390498966546682790608125613166744820307691068563387354936732643569654017172

print(pow(A,b,p))
```

**Flag: 1174130740413820656533832746034841985877302086316388380165984436672307692443711310285014138545204369495478725102882673427892104539120952393788961051992901649694063179853598311473820341215879965343136351436410522850717408445802043003164658348006577408558693502220285700893404674592567626297571222027902631157072143330043118418467094237965591198440803970726604537807146703763571606861448354607502654664700390453794493176794678917352634029713320615865940720837909466**
### Diffie-Hellman Starter 5

![image](https://hackmd.io/_uploads/S1rczHB9p.png)

Chall đưa cho ta 2 source code, 1 code là encrypt, 1 code là decrypt, ta thấy là mã hóa CBC, với Key sẽ là bí mật chung giữa Alice và Bob hash dạng sha256 và lấy 16 byte đầu.

Giờ code giải sẽ như sau

```
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import hashlib
import os

def encrypt_flag(shared_secret: int):
    # Derive AES key from shared secret
    sha1 = hashlib.sha1()
    sha1.update(str(shared_secret).encode('ascii'))
    key = sha1.digest()[:16]
    # Encrypt flag
    iv = os.urandom(16)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    ciphertext = cipher.encrypt(pad(FLAG, 16))
    # Prepare data to send
    data = {}
    data['iv'] = iv.hex()
    data['encrypted_flag'] = ciphertext.hex()
    return data

def is_pkcs7_padded(message):
    padding = message[-message[-1]:]
    return all(padding[i] == len(padding) for i in range(0, len(padding)))


def decrypt_flag(shared_secret: int, iv: str, ciphertext: str):
    # Derive AES key from shared secret
    sha1 = hashlib.sha1()
    sha1.update(str(shared_secret).encode('ascii'))
    key = sha1.digest()[:16]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext)

    if is_pkcs7_padded(plaintext):
        return unpad(plaintext, 16).decode('ascii')
    else:
        return plaintext.decode('ascii')
    
g = 2
p = 2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919
A = 112218739139542908880564359534373424013016249772931962692237907571990334483528877513809272625610512061159061737608547288558662879685086684299624481742865016924065000555267977830144740364467977206555914781236397216033805882207640219686011643468275165718132888489024688846101943642459655423609111976363316080620471928236879737944217503462265615774774318986375878440978819238346077908864116156831874695817477772477121232820827728424890845769152726027520772901423784
B = 1241972460522075344783337556660700537760331108332735677863862813666578639518899293226399921252049655031563612905395145236854443334774555982204857895716383215705498970395379526698761468932147200650513626028263449605755661189525521343142979265044068409405667549241125597387173006460145379759986272191990675988873894208956851773331039747840312455221354589910726982819203421992729738296452820365553759182547255998984882158393688119629609067647494762616719047466973581
b = 197395083814907028991785772714920885908249341925650951555219049411298436217190605190824934787336279228785809783531814507661385111220639329358048196339626065676869119737979175531770768861808581110311903548567424039264485661330995221907803300824165469977099494284722831845653985392791480264712091293580274947132480402319812110462641143884577706335859190668240694680261160210609506891842793868297672619625924001403035676872189455767944077542198064499486164431451944

iv = bytes.fromhex("737561146ff8194f45290f5766ed6aba")
encrypt_flag = bytes.fromhex("39c99bf2f0c14678d6a5416faef954b5893c316fc3c48622ba1fd6a9fe85f3dc72a29c394cf4bc8aff6a7b21cae8e12c")
print(decrypt_flag(pow(A,b,p),iv,encrypt_flag))
```

**Flag: crypto{sh4r1ng_s3cret5_w1th_fr13nd5}**

### Parameter Injection

![image](https://hackmd.io/_uploads/HyZHUrSq6.png)


Chall này sẽ cho mình là người ở giữa, mình sẽ được can thiệp vào cuộc trò chuyện giữa hai người. Mình có thể thay đổi nội dung của Alice dành cho Bob và ngược lại.

Giờ mình chỉ cần nói rằng khóa công khai của Bob là ``B = 0x01``. Từ đó ``s = pow(1, a, p) = 1``. Từ đó ta sẽ có được bí mật chung của 2 người.


```
from Crypto.Util.number import*
from Crypto.Cipher import AES
import hashlib
from json import loads
from pwn import*

io = remote("socket.cryptohack.org", 13371)

io.recvuntil(b'Intercepted from Alice: ')
data1 = io.recvuntil(b'\n', drop=True)
io.recvuntil(b'Send to Bob: ')
print(data1)
io.sendline(data1)
io.recvuntil(b'Send to Alice: ')
io.sendline(b'{"B": "0x01"}')
io.recvuntil(b'Intercepted from Alice: ')
output = io.recvuntil(b'\n', drop=True)
Alice = loads(output)

s = 1
sha1 = hashlib.sha1()
sha1.update(str(s).encode('ascii'))
key = sha1.digest()[:16]

iv = bytes.fromhex(Alice['iv'])
ciphertext = bytes.fromhex(Alice['encrypted_flag'])
cipher = AES.new(key,AES.MODE_CBC,iv)

print(cipher.decrypt(ciphertext))

```

**Flag: crypto{n1c3_0n3_m4ll0ry!!!!!!!!}**

### EXPORT_GRADE

Chall đưa ta 1 source encrypt như sau:
```
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import hashlib
import os
from secret import shared_secret

FLAG = b'crypto{????????????????????????????}'


def encrypt_flag(shared_secret: int):
    # Derive AES key from shared secret
    sha1 = hashlib.sha1()
    sha1.update(str(shared_secret).encode('ascii'))
    key = sha1.digest()[:16]
    # Encrypt flag
    iv = os.urandom(16)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    ciphertext = cipher.encrypt(pad(FLAG, 16))
    # Prepare data to send
    data = {}
    data['iv'] = iv.hex()
    data['encrypted_flag'] = ciphertext.hex()
    return data


print(encrypt_flag(shared_secret))
```

Ta thấy rằng khi kết nối với server, thì ta được như sau:
```
Intercepted from Alice: {"supported": ["DH1536", "DH1024", "DH512", "DH256", "DH128", "DH64"]}
```
Thấy được rằng, Alice hỗ trợ các loại Diffie Hellman từ 64 đến 1536 bits
Giờ ta gửi cho Bob loại Diffie Hellman loại 64bits để lấy được khóa dài 64bits
``` {"supported": ["DH64"]}```
Sau đó ta tiếp tục gửi cho Alice ```{"chosen": "DH64"}``` để Alice lấy 
khóa dài 64bits

![image](https://hackmd.io/_uploads/Sk4GPBr9p.png)


Từ đó ta thu được các khóa công khai, iv và encrypted_flag

Trong thư viện sage, ta thấy có 1 hàm ``discrete_log(a, b)``. Hàm ``discrete_log(a, b)`` (còn được gọi là logarithm rời rạc) là một hàm toán học được sử dụng trong lý thuyết số và mã hóa mật mã. Hàm này tính toán giá trị x sao cho ``a^x ≡ b (mod n)``, trong đó a, b, và n là các số nguyên dương đã biết và a và n phải là các số nguyên tố cùng nhau.

Ý nghĩa của hàm ``discrete_log(a, b)`` là tìm giá trị x mà khi a được nhân với chính nó lặp đi lặp lại x lần và lấy phần dư khi chia cho n, ta sẽ thu được giá trị b. Đây là một bài toán khá phức tạp khi n có giá trị lớn.

Từ đó ta sẽ thu được ``a = discrete_log(A, g)`` (Với A và g thuộc ``F(p)``)

Source solution sage như sau:
```sage=
from Crypto.Cipher import AES
from Crypto.Cipher import*
from Crypto.Util.number import*
import hashlib

p = 0xde26ab651b92a129
g = Mod(0x2,p)
A = Mod(0x78ab0eb17a55cdcb,p)
B = 0x6ec23355b1a51fb4
iv = "9dcd838ee8f0d1c191933ad1371cc1d0"
encrypted_flag = "c89bbd8eaa0771cbf0d9ac623cf8582182eee62355ff46d82f93f72c95357699"
a = discrete_log(A, g)
s = pow(B,a,p)
sha1 = hashlib.sha1()
sha1.update(str(s).encode('ascii'))
key = sha1.digest()[:16]
iv = bytes.fromhex(iv)
ciphertext = bytes.fromhex(encrypted_flag)
cipher = AES.new(key,AES.MODE_CBC,iv)
print(cipher.decrypt(ciphertext))
```

Flag: **crypto{d0wn6r4d35_4r3_d4n63r0u5}**

### Static Client

Chall này khi ta kết nối, ta có thể trở thành Alice giả mạo để gửi g, p, A cho Bob, khi đó Bob sẽ gửi khóa B cho mình, từ đó ta có thể thu được khóa bí mật

Ta thấy rằng khi ta gửi cho Bob khóa g, thì Bob sẽ gửi lại cho ta ``B = pow(b,g,p)``, thế giờ ta thay thế giữa g và A của Alice thì ``B = pow(b,A,p) = shared_secret``  

Khi thu được shared_secret thì làm như bình thường thuii

Source solution như sau:
```
from pwn import*
import json 
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import hashlib


def is_pkcs7_padded(message):
    padding = message[-message[-1]:]
    return all(padding[i] == len(padding) for i in range(0, len(padding)))

def decrypt_flag(shared_secret: int, iv: str, ciphertext: str):
    # Derive AES key from shared secret
    sha1 = hashlib.sha1()
    sha1.update(str(shared_secret).encode('ascii'))
    key = sha1.digest()[:16]
    # Decrypt flag
    ciphertext = bytes.fromhex(ciphertext)
    iv = bytes.fromhex(iv)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext)

    if is_pkcs7_padded(plaintext):
        return unpad(plaintext, 16).decode('ascii')
    else:
        return plaintext.decode('ascii')

io = remote('socket.cryptohack.org', 13373)

io.recvuntil(b'Intercepted from Alice: ')
Alice = json.loads(io.recvuntil(b'\n',drop=True).decode())
io.recvuntil(b'Intercepted from Bob: ')
Bob = json.loads(io.recvuntil(b'\n',drop=True).decode())
io.recvuntil(b'Intercepted from Alice: ')
encrypted = json.loads(io.recvuntil(b'\n',drop=True).decode())

p = Alice['p']
A = Alice['A']
g = Alice['g']
B = Bob['B']
iv = (encrypted['iv'])
ciphertext = (encrypted['encrypted'])


io.recvuntil(b'Bob connects to you, send him some parameters: ')
to_send = {
    "p": p,
    "g": A,
    "A": g
}
to_send = json.dumps(to_send)
io.sendline((to_send).encode())
io.recvuntil(b'Bob says to you: ')
secret = json.loads(io.recvuntil(b'\n',drop=True).decode())
share_key = int(secret["B"],16)

print(decrypt_flag(share_key,iv,ciphertext))
```

Flag: **crypto{n07_3ph3m3r4l_3n0u6h}**



### Additive

Ta thấy đề miêu tả như sau:
```
Alice and Bob decided to do their DHKE in an additive group rather than a multiplicative group. What could go wrong?
```

Có nghĩa là thay vì ``A = pow(g,a,p)`` và ``B = pow(g,b,p)`` thì ``A = pow(g*a,1,p)`` và ``B = pow(g*b,1,p)``

Ta có ``A*inverse(g,p) = a*g*inverse(g,p) mod p`` <=> ``A*inverse(g,p) = a mod p`` --> Ta có được a

Khi có a rồi thì ``s = B*a mod p``

Đoạn code sẽ như sau:
```
from Crypto.Util.number import*
from Crypto.Cipher import AES
import hashlib
from Crypto.Util.Padding import pad, unpad

p = 0xffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff
g = 0x02
A = 0x7c552dc34d83ef5c448cd6e752fcfef27429a700014550c6844d23e65601953ff4574ce3dc7d07b6bd3357b1e9aaffc497025f4b29fac72d651b0c42f11151dd6ade6254b92337a9524ada2f20b4f221899aed5bd548467ff57a29668527679deab87263f6950bde6194ae717b888d14c7ade8a52e83e671de1d4f5b8b5e489235f3da8bd3b5bc32c328aa65d472a144aa35a3897514bcd05c76be3632b331d3f5d0dcb4e2ccd2eeacac5991c6fea3f586e0c5c7dea0e91cb85bbeee9da8211e
B = 0x1eb2a3a6264d04bc9ec838aa38bb074232382263bb8c44a4c81f342d629747fa08086c8021fbc941b9a8288887a88082841d2280050b990cb4d12bd4095b9add4f7333f9ae04b2a6d5fe169319a843500abd7bb4e6e18a0d49e04d408bd065665af2d8cc3de598e6aec20c840dff017a5230a7f22e6ea340b9ca75e408c5dc59fcae177ddac8324d5330b30bb5f5fbb0807ddba65a0eee229aeb97ccc2fab1458bbd9d3d10b5544cc24d3ae2a4fe3c8d9cb2836e597efc495ccb6f8dd508e5ed
iv = "b3b2567219b5540c3ff280f5eb28cc4f"
encrypted_flag = "be1fcf223e9aa01047caf18191f6e21639a950bf8e1056c2a481ae03f3faef21982e95ad6112b6adb0e0d56f97692218"

A = 0x7c552dc34d83ef5c448cd6e752fcfef27429a700014550c6844d23e65601953ff4574ce3dc7d07b6bd3357b1e9aaffc497025f4b29fac72d651b0c42f11151dd6ade6254b92337a9524ada2f20b4f221899aed5bd548467ff57a29668527679deab87263f6950bde6194ae717b888d14c7ade8a52e83e671de1d4f5b8b5e489235f3da8bd3b5bc32c328aa65d472a144aa35a3897514bcd05c76be3632b331d3f5d0dcb4e2ccd2eeacac5991c6fea3f586e0c5c7dea0e91cb85bbeee9da8211e
a = (inverse(g, p) * A) % p
s = int((a * B) % p)
sha1 = hashlib.sha1()
sha1.update(str(s).encode('ascii'))
key = sha1.digest()[:16]
iv = bytes.fromhex(iv)
ciphertext = bytes.fromhex(encrypted_flag)
cipher = AES.new(key,AES.MODE_CBC,iv)
print(unpad(cipher.decrypt(ciphertext),16))
```
Flag: **crypto{cycl1c_6r0up_und3r_4dd1710n?}**

### Static Client 2
Chall này cũng như Static Client trước thui, thế nhưng không còn dùng cách cũ như ban nãy được nữa, thế nên mình phải dùng cách mới huhuhuuhu :crying_cat_face: 

Sau một thời gian tìm hiểu thì bài này thuộc dạng $Pohlig-hellman attack$ trong tường số nguyên tố p. Thế nhưng, số nguyên tố p này không phải là bình thường mà khi $p-1$ thì sẽ là [``smooth_number``](https://en.wikipedia.org/wiki/Smooth_number).

Vì $p-1$ là smooth prime thế nên rất dễ factor, khi đó ta có thể sử dụng nó để tấn công như trên [**video**](https://www.youtube.com/watch?v=BXFNYVmdtJU) này.

Giờ ta vẫn sẽ gửi cho Bob g và A như bình thường, nhưng mà sẽ gửi 1 số nguyên tố p với $p-1$ là smooth number.

```python3=
from pwn import remote
from json import loads, dumps
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from Crypto.Util.number import *
import hashlib
from sympy.ntheory.residue_ntheory import discrete_log
from gmpy2 import *


def getParams():
    Alice = loads(io.recvline().split(b":", 1)[1])
    Bob = loads(io.recvline().split(b":", 1)[1])
    cipher = loads(io.recvline().split(b":",1)[1])
    iv = cipher['iv']
    encrypted = cipher['encrypted']
    return Alice,Bob,iv,encrypted

def getSmoothP(p,digits):
    pSmooth = 2
    primes = []
    while (pSmooth.bit_length() < p.bit_length()):
        prime = getPrime(digits)
        if prime not in primes:
            pSmooth *= prime
            primes.append(prime)
    pSmooth += 1
    return pSmooth

io = remote("socket.cryptohack.org", 13378)
Alice,Bob,iv,encrypted = getParams()

p = int(Alice["p"], 16)
pSmooth = 0
while not is_prime(pSmooth):
    pSmooth = getSmoothP(p,32)

print(pSmooth)

io.sendline(dumps({"p": hex(pSmooth), "g": Alice["g"], "A": Alice["A"]}).encode())
reply = loads(io.recvline().split(b":", 2)[2])
B = int(reply["B"], 16)

b = discrete_log(pSmooth, B, 2)

print(b)

shared_secret = pow(int(Alice['A'],16), b, p)
sha1 = hashlib.sha1()
sha1.update(str(shared_secret).encode('ascii'))
key = sha1.digest()[:16]
iv = bytes.fromhex(iv)
ciphertext = bytes.fromhex(encrypted)
cipher = AES.new(key,AES.MODE_CBC,iv)
print(unpad(cipher.decrypt(ciphertext),16))


```
**Flag: crypto{uns4f3_pr1m3_sm4ll_oRd3r}**



### Script Kiddie

Ta được source như sau:
```
from Crypto.Cipher import AES
import hashlib
import secrets


def header():
    print("""  _____  _  __  __ _
 |  __ \(_)/ _|/ _(_)
 | |  | |_| |_| |_ _  ___
 | |  | | |  _|  _| |/ _ \
 | |__| | | | | | | |  __/
 |_____/|_|_| |_| |_|\___|
 | |  | |    | | |
 | |__| | ___| | |_ __ ___   __ _ _ __
 |  __  |/ _ \ | | '_ ` _ \ / _` | '_ \
 | |  | |  __/ | | | | | | | (_| | | | |
 |_|  |_|\___|_|_|_| |_| |_|\__,_|_| |_|

                                        """)


def is_pkcs7_padded(message):
    padding = message[-message[-1]:]
    return all(padding[i] == len(padding) for i in range(0, len(padding)))


    def pkcs7_unpad(message, block_size=16):
        if len(message) == 0:
            raise Exception("The input data must contain at least one byte")
        if not is_pkcs7_padded(message):
            return message
        padding_len = message[-1]
        return message[:-padding_len]


def decrypt_flag(shared_secret: int, iv: str, ciphertext: str):
    # Derive AES key from shared secret
    sha1 = hashlib.sha1()
    sha1.update(str(shared_secret).encode('ascii'))
    key = sha1.digest()[:16]
    # Decrypt flag
    ciphertext = bytes.fromhex(ciphertext)
    iv = bytes.fromhex(iv)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext)
    return pkcs7_unpad(plaintext).decode('ascii')


def generate_public_int(g, a, p):
    return g ^ a % p


def generate_shared_secret(A, b, p):
    return A ^ b % p


def goodbye():
    print('Goodbye!')


def main():
    header()
    print('[-] Collecting data from Alice')
    p = int(input('> p: '))
    q = (p - 1) // 2
    g = int(input('> g: '))
    A = int(input('> A: '))
    print('[+] All data collected from Alice')

    print('[+] Generating public integer for alice')
    b = secrets.randbelow(q)
    B = generate_public_int(g, b, p)
    print(f'[+] Please send the public integer to Alice: {B}')
    print('')
    input('[+] Press any key to continue')
    print('')

    print('[+] Generating shared secret')
    shared_secret = generate_shared_secret(A, b, p)

    query = input('Would you like to decrypt a message? (y/n)\n')
    if query == 'y':
        iv = input('[-] Please enter iv (hex string)\n')
        ciphertext = input('[-] Please enter ciphertext (hex string)\n')
        flag = decrypt_flag(shared_secret, iv, ciphertext)
        print(f'[+] Flag recovered: {flag}')
        goodbye()
    else:
        goodbye()


if __name__ == '__main__':
    main()
```

Nhìn kỹ code thì ta thấy rằng có 1 lỗ hỏng ở đây là:
```
def generate_public_int(g, a, p):
    return g ^ a % p


def generate_shared_secret(A, b, p):
    return A ^ b % p
```
Thay vì là hàm mũ thì đây là xor, thì có nghĩa là ``B = (g xor b) mod p`` và ``s = (A xor b) mod p``

Suy ra: ``s = B xor g xor A``

Thu được s rùi thì decrypt thuiii

Source solution như sau:
```
import hashlib
from Crypto.Cipher import AES

def pkcs7_unpad(message, block_size=16):
    if len(message) == 0:
        raise Exception("The input data must contain at least one byte")
    if not is_pkcs7_padded(message):
        return message
    padding_len = message[-1]
    return message[:-padding_len]
def is_pkcs7_padded(message):
    padding = message[-message[-1]:]
    return all(padding[i] == len(padding) for i in range(0, len(padding)))

def decrypt_flag(shared_secret: int, iv: str, ciphertext: str):
    # Derive AES key from shared secret
    sha1 = hashlib.sha1()
    sha1.update(str(shared_secret).encode('ascii'))
    key = sha1.digest()[:16]
    # Decrypt flag
    ciphertext = bytes.fromhex(ciphertext)
    iv = bytes.fromhex(iv)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext)
    return pkcs7_unpad(plaintext).decode('ascii')

p = 2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919
g = 2
A = 539556019868756019035615487062583764545019803793635712947528463889304486869497162061335997527971977050049337464152478479265992127749780103259420400564906895897077512359628760656227084039215210033374611483959802841868892445902197049235745933150328311259162433075155095844532813412268773066318780724878693701177217733659861396010057464019948199892231790191103752209797118863201066964703008895947360077614198735382678809731252084194135812256359294228383696551949882
B = 652888676809466256406904653886313023288609075262748718135045355786028783611182379919130347165201199876762400523413029908630805888567578414109983228590188758171259420566830374793540891937904402387134765200478072915215871011267065310188328883039327167068295517693269989835771255162641401501080811953709743259493453369152994501213224841052509818015422338794357540968552645357127943400146625902468838113443484208599332251406190345653880206706388377388164982846343351
iv =  'c044059ae57b61821a9090fbdefc63c5'
encrypted_flag = 'f60522a95bde87a9ff00dc2c3d99177019f625f3364188c1058183004506bf96541cf241dad1c0e92535564e537322d7'

s = B^A^g
print(decrypt_flag(s,iv,encrypted_flag))
```
Flag: **crypto{b3_c4r3ful_w1th_y0ur_n0tati0n}**


### The Matrix

``source.sage``
```python=
import random

P = 2
N = 50
E = 31337

FLAG = b'crypto{??????????????????????????}'

def bytes_to_binary(s):
    bin_str = ''.join(format(b, '08b') for b in s)
    bits = [int(c) for c in bin_str]
    return bits

def generate_mat():
    while True:
        msg = bytes_to_binary(FLAG)
        msg += [random.randint(0, 1) for _ in range(N*N - len(msg))]

        rows = [msg[i::N] for i in range(N)]
        mat = Matrix(GF(2), rows)

        if mat.determinant() != 0 and mat.multiplicative_order() > 10^12:
            return mat

def load_matrix(fname):
    data = open(fname, 'r').read().strip()
    rows = [list(map(int, row)) for row in data.splitlines()]
    return Matrix(GF(P), rows)

def save_matrix(M, fname):
    open(fname, 'w').write('\n'.join(''.join(str(x) for x in row) for row in M))

mat = generate_mat()

ciphertext = mat^E
save_matrix(ciphertext, 'flag.enc')
```

``flag.enc``
```python=
00000001111101100001101010010001001011000110001001
10111010010100110001011011111110011000001001000001
01011101000110110101010100100100111110110110011111
11101100011011010001111011011000100010110001001010
11111111101001010101001011111101010010011010101001
10010011101000000010000110100101111011110011101110
00011100010011010110000000001011000111010101001011
01001111000110100111110011000101011110010111111110
01111001110011000100000110010101011111010000000011
11001101010111111011110110101010001101001001111101
01100111000100010101000001011011001101001110101000
00010001001011101111100010101101011000101100010111
01101100101101011101101000110001011111111010000100
00001110111111000111111100011110000101100100000011
10001001111111011000111011111010110111111111000110
01111101100011110110111000011110000100111001110100
11111100110101111001111000110100011010111011110001
00100011011100101010111011111100000010000101111111
01111001110100011111011100100011011010010011111000
01011011101011111111101011011011000111110011111010
00010100110111110011111100111101100000001101110111
10011011011101110101100110110000011101000010101011
01111000001111011011111000100010010010010111101001
00100000010001110000001101111100011111110011011000
10010101101011011111101111101000111010010011111001
10011011000111000001010111011000000000100111100011
11001001010001111111000011011011101001101010001000
00100100000101110010001001011001111011001110100001
00000101000101100111010111101010001101111110011001
00101000011010100110100111111110000101011001011110
11011001001111111010000001100111011101101100110110
00111000011011011111111011110001001101001101101100
11110010101001100110000110110000100000101010101011
00101001000011001110110111100010010011100101001000
11000100010010111110110010100110110110101000110110
01101011000111001111011110000110001011111000011100
11010011001111111110100101100011000000000011110001
10000011000101100011000110111111011010110111101000
11000011000100010001001011010011010000001101100011
11011001111001010100010101001100001010101100010010
10110101010111111010110001111111100100110001001100
11101001100110001001001100000011100101001010011010
10000011100110001101010110010010100001010011011101
10001110111111100110011000010000011011011111011001
00011100011111110101011100111000110010100011000010
00111111010010111100100101100001001011110101111100
10000100101101000011011010100100011111100101101111
00011101110001001010111001111011111110110011011001
11111100110101111100110001011001000001111100110011
00110010110110011001001111110110000011001111010110
```



Trong RSA, ta biết được rằng $C = M^e \mod N$ và để decrypt lại thì ta có $M = C^d \mod N$ với $d = e^{-1} \mod\ φ(N)$

Và $φ(N)$ là trong trường số nguyên, trong challenge này thì lại trong $GF(50, GF(2))$, giờ ta cần tìm lại khóa d trong trường ma trận này.

lên wiki thì ta có được công thức sau đây

![image](https://hackmd.io/_uploads/SJN1HZiV0.png)

Từ đó ta có thể tìm lại được khóa d và flag.

```python=
import random
from Crypto.Util.number import *

P = 2
N = 50
E = 31337

FLAG = b'crypto{??????????????????????????}'

def load_matrix(fname):
    data = open(fname, 'r').read().strip()
    rows = [list(map(int, row)) for row in data.splitlines()]
    return Matrix(GF(P), rows)

def bin_to_bytes(s):
    all_bytes = [s[i:i+8] for i in range(0, len(s), 8)]
    return b''.join(long_to_bytes(int(byte, 2)) for byte in all_bytes)

def find_order():
    order = 1
    for k in range(N):
        order *= (P**N - P**k)
    
    return order

ciphertext = load_matrix('flag.enc')

phi = ZZ(find_order())

d = inverse_mod(E, phi)   # either phi or ciphertext.multiplicative_order()

M = ciphertext^d

bin_flag = ""
for i in range(N):
    for j in range(N):
        bin_flag += str(M[j,i])
print(bin_to_bytes(bin_flag[:len(FLAG)*8]))
```

**Flag: crypto{there_is_no_spoon_66eff188}**