## 1. “Symmectric Ciphers”
**Keyed Permutations**
`crypto{bijection}`
**Resisting Bruteforce**
`crypto{biclique}`
**Structure of AES**
Để giải quyết bài toán ta chỉ cần chuyển lần lượt từng phần tử của matrix từ số về dạng kí tự ascii của chúng.
```
def bytes2matrix(text):
""" Converts a 16-byte array into a 4x4 matrix. """
return [list(text[i:i+4]) for i in range(0, len(text), 4)]
def matrix2bytes(matrix):
""" Converts a 4x4 matrix into a 16-byte array. """
text = ''
for i in range(len(matrix)):
for j in range(4):
text += chr(matrix[i][j])
return text
matrix = [
[99, 114, 121, 112],
[116, 111, 123, 105],
[110, 109, 97, 116],
[114, 105, 120, 125],
]
print(matrix2bytes(matrix))
```
## 2. “RSA”
**RSA Starter 1**
Find `pow(101,17,22663)` python
(19906)
**RSA Starter 2**
`C = m^e mod n`
Script
```
e = 65537
p = 17
q = 23
n = p*q
m = 12 #message
print(pow(m,e,n))
```
(301)
**RSA Starter 3**
`(p-1)*(q-1)`
```
p = 857504083339712752489993810777
q = 1029224947942998075080348647219
print((p-1)*(q-1))
```
(882564595536224140639625987657529300394956519977044270821168)
## 3. “Diffe-Helman”
**Diffie-Hellman Starter 1**
```
p = 991 # Prime modulus
g = 209 # Element in the finite field Fp
d = pow(g, -1, p)
print(d)
```
(569)
**Diffie-Hellman Starter 2**
Script
```
p = 28151
def is_primitive_element(g):
# Set of powers generated by g
powers = set()
# Calculate powers of g modulo p
for i in range(1, p):
power = pow(g, i, p)
if power in powers:
# If a power is repeated, g is not a primitive element
return False
powers.add(power)
# If all elements in Fp are generated by g, it is a primitive element
return len(powers) == p - 1
# Iterate over elements of Fp
for g in range(1, p):
if is_primitive_element(g):
# Found the smallest primitive element
smallest_primitive_element = g
break
# Print the smallest primitive element (the flag)
print("Smallest primitive element of Fp:", smallest_primitive_element)
```
(7)
**Diffie-Hellman Starter 3**
```
g = 2
p = 2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919
a = 972107443837033796245864316200458246846904598488981605856765890478853088246897345487328491037710219222038930943365848626194109830309179393018216763327572120124760140018038673999837643377590434413866611132403979547150659053897355593394492586978400044375465657296027592948349589216415363722668361328689588996541370097559090335137676411595949335857341797148926151694299575970292809805314431447043469447485957669949989090202320234337890323293401862304986599884732815
# Calculate g^a mod p to obtain the shared secret
shared_secret = pow(g, a, p)
# Print the shared secret
print(shared_secret)
```
## 4. “Elliptic Curves”
**Point Addition**
Làm từng bước theo phương pháp đã được nêu ra trong challenge, nhưng thay vì phép chia ta sẽ dùng modular inverse

```
from Crypto.Util.number import*
a = 497
b = 1768
p = 9739
'''
(a) If P = O, then P + Q = Q.
(b) Otherwise, if Q = O, then P + Q = P.
(c) Otherwise, write P = (x1, y1) and Q = (x2, y2).
(d) If x1 = x2 and y1 = - y2, then P + Q = O.
(e) Otherwise:
(e1) if P ≠ Q: λ = (y2 - y1) / (x2 - x1)
(e2) if P = Q: λ = (3x12 + a) / 2y1
(f) x3 = λ2- x1 - x2, y3 = λ(x1 - x3) - y1
(g) P + Q = (x3, y3)
'''
def add_point(p1, p2):
if p1 == (0, 0):
return p2
if p2 == (0,0):
return p1
x1, y1 = p1
x2, y2 = p2
if x1 == x2 and y1 == -y2:
return (0, 0)
lamda = 0
if p1 == p2:
lamda = ((3*pow(x1,2,p)+a) * inverse(2*y1, p))
else:
lamda = ((y2-y1) * inverse(x2-x1, p))
x3 = (pow(lamda, 2) - x1 - x2) % p
y3 = (lamda*(x1 - x3) - y1) % p
return (x3, y3)
P = (493, 5564)
Q = (1539, 4742)
R = (4403,5202)
#S(x,y) = P + P + Q + R
s = add_point(P,add_point(P, add_point(Q, R)))
print(s)
```
## 5. “Hash Function”
**Jack's Birthday Hash**
Giá trị hash là chuỗi nhị phân 11 bits nên có tất cả n = 2^11 = 2048 giá trị hash.
p(n) là xác suất để nhóm n secret có ít nhất 1 cái gặp collision với secret của Jack.
Khi đó p(k) là xác suất để không có secret nào trong n cái gặp collision với secret của Jack, *p(k)=(n−1/n)^k.*
Suy ra *p(k)=1−((n−1)/n)^k>0.5*
Script
```
n = 1 << 11
P = 1
for i in range(1, n):
P = pow((1 - 1/n), i)
nP = 1 - P
if nP > 0.5:
print(i)
break
```
(1420)
**Jack’s Birthday Confusion**
Tương tự bài trên, n = 2^11.
Gọi p là xác xuất để tồn tại 2 secret có chung giá trị hash trong k secret cho trước, suy ra !p(k)
là xác suất để k secret có giá trị hash đôi một phân biệt, khi đó 
Script:
```
from math import factorial
n = 2048
for i in range(n):
probability = 1 - factorial(n) / (factorial(n - i)*pow(n,i))
if probability > 0.75:
print(i)
break
```
(76)