Like a Whispering Entropy | PWNME CTF 2023
Introduction du challenge:
I don't trust those who wrote crypto implementations, so I had to write my own ...
I created everything from scratch so I know it is safe ! No one backdoored my Diffie-Hellman implementation !
Btw, I gave you an output of a connection to my service, but anyway you won't be able to get back the secret message
Like a Whispering Entropy
Avec le challenge sont fournis 2 fichiers :
- logs
- chall.sage
Dans le premier, on a 100 fois ce groupe de lignes :
-------------------------
Alice s public key: 303944725738104736773292818682761237676539243408422159733529747956894368678621645049645070627114278218945685944600948944992220099312948739268425436680143720194562111453986315120087316070223849151163596063751581483286506901176907235
User #99 public key: 809564025375502966704086026543808060813039551725974913290658576413572207289606703462020431496140057417889518191378472574633592651459369801336171048329445802174676004530841151781034766965662208992430242268851471075994293985371787031
encrypt(shared_secret, iv, FLAG) = {"iv": "17aa1262102cbb1ec366f7646e9ba610", "encrypted_msg": "587fd55183514a484382abf9bd79d882b10593de0b75782b1184b0ee38717c4f0570ed62780caf86f02c203e32c70d3c8f840f6b3e11b509cd422876fb8f1d550a8901db9ffa6204a8b7e947f7dde57a"}
-------------------------
Dans le second fichier, on a le challenge :
#!/bin/env sage
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.number import getStrongPrime, long_to_bytes, bytes_to_long
import hashlib
from os import getenv, urandom
from random import getrandbits
FLAG = getenv("FLAG", "PWNME{dummyflag}").encode()
class PRNG(object):
def __init__(self):
self._seed = None
self.q = 279622638356169037213136872013126932777
self.n = 12
self.A = Matrix(GF(self.q), 1, self.n, [
19159356164385140466,
19848194065535878410,
33461959522325830456,
12213590058439028697,
35299014249932143965,
13327781436808877193,
20921178705527762622,
9371898426952684667,
9769023908222006322,
28712160343104144896,
32272228797175569095,
14666990089233663894
])
# LCG props
self.a = getrandbits(32)
self.c = getrandbits(32)
self._lcgseed = getrandbits(32)
self.mod = 2551431067
@property
def noise(self):
self._lcgseed = (self.a * self._lcgseed + self.c) % self.mod
return self._lcgseed
@property
def seed(self):
if self._seed is None:
self._seed = Matrix(GF(self.q), self.n, 1, [
getrandbits(102) for i in range(self.n)
])
print('#################')
print(self._seed)
return self._seed
def randint(self):
b = (self.A * self.seed + self.noise)[0][0]
self.A = Matrix(GF(self.q), 1, self.n, [
int(x * b^(i+1)) % 2^65 for i, x in enumerate(self.A[0])
])
return b
def encrypt(shared_secret: int, iv: bytes, msg: bytes):
sha1 = hashlib.sha1()
sha1.update(str(shared_secret).encode('ascii'))
key = sha1.digest()[:16]
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(msg, 16))
data = {}
data['iv'] = iv.hex()
data['encrypted_msg'] = ciphertext.hex()
return data
def main():
# Initialize PRNG:
rng = PRNG()
g = 2
p = 1552518092300708935130918131258481755631334049434514313202351194902966239949102107258669453876591642442910007680288864229150803718918046342632727613031282983744380820890196288509170691316593175367469551763119843371637221007210577919
n = 100
ivs = [
long_to_bytes(int(rng.randint())).ljust(16, b'\0') for _ in range(n)
]
secret_keys = [
int(rng.randint()) |
int(rng.randint() << 128) |
int(rng.randint() << 256) |
int(rng.randint() << 384) |
int(rng.randint() << 512) |
int(rng.randint() << 640) for _ in range(n)
]
public_keys = [
pow(g, sk, p) for sk in secret_keys
]
for i, e in enumerate(zip(ivs, secret_keys, public_keys)):
iv, sk, pk = e
print(f"Alice's public key: { pk }")
pk2 = bytes_to_long(urandom(0x60))
print(f"User #{i} public key: { pk2 }")
shared_secret = pow(pk2, sk, p)
print(f"{encrypt(shared_secret, iv, FLAG) = }")
print(shared_secret)
print("-------------------------")
if __name__ == '__main__':
main()
Analyse du code.
La première partie du code défini une class PRNG
qui semble être un générateur de nombre.
Nous y reviendrons plus tard.
Le code suivant fait ceci :
- Génère une liste d’
ivs
- Génère une liste de clé
publique
- Génère une liste de clé
privée
- Génère 100 fois:
- un nombre aléatoire pk2
- $shared_{secret} = {pk2}^{sk} \pmod p$
- Une clé AES à partir de $shared_{secret}$
- Chiffre en
AES CBC
le flag avec cette clé.
Ainsi, si on arrive à récupérer une clé privée, on peut en utilisant la clé publique correspondante, présente dans le fichier logs, recalculer la clé AES et déchiffrer le flag chiffré !
Les ivs sont présents dans fichier logs, ceux-ci sont générés juste après l’initialisation de l’objet rng = PRNG()
ce qui veut dire que nous avons les 100 premières valeurs du PRNG.
Si nous arrivons à récupérer l’état interne du PRNG en utilisant ces 100 ivs, nous pourrons re-générer les clés privées et ainsi flag.
Analyse du Prng
Voici le code du PRNG :
class PRNG(object):
def __init__(self):
self._seed = None
self.q = 279622638356169037213136872013126932777
self.n = 12
self.A = Matrix(GF(self.q), 1, self.n, [
19159356164385140466,
19848194065535878410,
33461959522325830456,
12213590058439028697,
35299014249932143965,
13327781436808877193,
20921178705527762622,
9371898426952684667,
9769023908222006322,
28712160343104144896,
32272228797175569095,
14666990089233663894
])
# LCG props
self.a = getrandbits(32)
self.c = getrandbits(32)
self._lcgseed = getrandbits(32)
self.mod = 2551431067
@property
def noise(self):
self._lcgseed = (self.a * self._lcgseed + self.c) % self.mod
return self._lcgseed
@property
def seed(self):
if self._seed is None:
self._seed = Matrix(GF(self.q), self.n, 1, [
getrandbits(102) for i in range(self.n)
])
return self._seed
def randint(self):
b = (self.A * self.seed + self.noise)[0][0]
self.A = Matrix(GF(self.q), 1, self.n, [
int(x * b^(i+1)) % 2^65 for i, x in enumerate(self.A[0])
])
return b
A l’initialisation de l’objet, python génère 3 int de 32
bits:
- $a$
- $c$
- $lcgseed$
De plus il définit:
- $q$ à 279622638356169037213136872013126932777
- $n$ à 12
- $A$ une matrice dans $GF(q)$ de 12 éléments
A chaque appel à noise
, celui si se re-définit de la sorte:
- $lcgseed = (a * lcgseed + c) % mod$
Au premier appel à seed
, on la génération d’une matrice colonne de 12 composantes de 102 bits:
_seed = Matrix(GF(q), n, 1, [
getrandbits(102) for i in range(n)
])
Par la suite, on notera $S_i$, la ième composante de la seed.
Enfin, à chaque appel à la fonction randint
- un nombre $b$ est calculé de la manière suivante :
$b = (A*seed + noise) \pmod q$ - La matrice $A$ est re-définit à partir de son statut actuel et du nombre généré $b$.
Si on décompose la matrice $A$ au rang $i$ comme :
- $A_i=(a_1,a_2,…,a_{12})$
alors nous avons: - $A_{i+1}=(a_1*b^1,a_2*b^2,…,a_{12}*b^{12})$
- $A_i=(a_1,a_2,…,a_{12})$
Ainsi, à chaque appel à la fonction randint
, si on note $N_i$ la valeur de noise; on a:
- $b = A_i*Seed + N_i$
$~= (a_{i,1},…,a_{i,12})*\begin{pmatrix} S_1 \newline S_2 \newline … \newline S_{12} \newline \end{pmatrix} + N_i$
Ce type de générateur de nombre aléatoire ressemble à un LCG
(Linear Congruence Générator) .
Pourtant on remarque une différence avec un LCG classique :
- Le 2nd terme noise n’est pas constant !
En effet, la forme classique d’un LCG
est :
- $state_{i+1} = state_{i}*m + c \pmod q$ avec c constant !
Cette différence est primordiale pour la suite.
Premières recherches sur le LCG.
La première chose à faire est de recalculer toutes les matrices $A_i$ à partir de $A_0$ et des ivs générés:
from Crypto.Util.number import bytes_to_long,long_to_bytes
from binascii import unhexlify
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.number import getStrongPrime, long_to_bytes, bytes_to_long
def decode_iv(iv):
b = unhexlify(bytes(iv,'utf-8')).strip(b'\0')
return bytes_to_long(b)
def parser(data):
alice,user,iv,enc = [],[],[],[]
for line in data.split('\n'):
if 'Alice' in line:
alice.append(int(line.split("public key: ")[1]))
if 'User' in line:
user.append(int(line.split("public key: ")[1]))
if 'encrypt' in line:
info = eval(line.split("= ")[1])
iv.append(decode_iv(info['iv']))
enc.append(info['encrypted_msg'])
return alice,user,iv,enc
alice,user,iv,enc = parser(open('logs','r').read())
q = 279622638356169037213136872013126932777
n = 12
As = [Matrix(GF(q), 1, n, [
19159356164385140466,
19848194065535878410,
33461959522325830456,
12213590058439028697,
35299014249932143965,
13327781436808877193,
20921178705527762622,
9371898426952684667,
9769023908222006322,
28712160343104144896,
32272228797175569095,
14666990089233663894
])]
for b in iv:
As.append(Matrix(GF(q), 1,n, [ int(x * b^(i+1)) % 2^65 for i, x in enumerate(As[-1][0])]))
Une méthode d’attaque classique sur ce genre de PRNG serait de faire des combinaisons linéaires des nombres générés (les ivs) pour ainsi obtenir ce genre d’équations:
$b_{i+1}-b_{i} = A_{i+1}*Seed + c - (A_i*Seed + c) \pmod q$
$\qquad\qquad= (A_{i+1}-A_i)*Seed + c - c \pmod q$
$\qquad\qquad= (A_{i+1}-A_i)*Seed \pmod q$
On peut ainsi retrouver la valeur de la seed.
Ici, notre terme c
n’est pas constant ce qui donnerai :
$b_{i+1}-b_{i} = A_{i+1}*Seed + c - (A_i*Seed + c) \pmod q$
$b_{i+1}-b_{i} = (A_{i+1}-A_i)*Seed + (c_0 - c_1) \pmod{q}$
On peut ainsi exprimer de façon polynomial les $c_i$ de cette manière:
- $b0 = A0*Seed + No$
- $b1 = A0*Seed + (N_0*A+C)$
(En notant $N_0$, le state initial (lcgseed dans le code))
On a:
$b_1-b_0 = ((A_1-A_0)*Seed) \pmod{q} + (N_1 - N_0) \pmod{q_2}$
$\qquad\quad= ((A_1-A_0)*Seed) \pmod{q} + [ N0*a - N_0 + c] \pmod{q_2}$
$b_2-b_1 = ((A_2-A_1)*Seed) \pmod{q} + (N_2 - N_1) \pmod{q_2}$
$\qquad\quad= ((A_2-A_1)*Seed) \pmod{q} + (N_2 - (N_0*a + c) ) \pmod{q_2}$
$\qquad\quad= ((A_2-A_1)*Seed) \pmod{q} + [((N_0*a + c)*a + c ) - (N_0*a + c)] \pmod{q_2}$
$\qquad\quad= ((A_2-A_1)*Seed) \pmod{q} + [(N_0*a^2 + c*a +c) - (N_0*a + c)] \pmod{q_2}$
$\qquad\quad= ((A_2-A_1)*Seed) \pmod{q} + [N_0*a^2 + c*a + c - N_0*a - c] \pmod{q_2}$
$\qquad\quad= ((A_2-A_1)*Seed) \pmod{q} + [N_0*a^2 - N_0*a + c*a] \pmod{q_2}$
$\qquad\quad= ((A_2-A_1)*Seed) \pmod{q} + [N_0*a - N_0 + c] * a \pmod{q_2}$
$b_3-b_2 = ((A_3-A_2)*Seed) \pmod{q} + (N_3 - N_2) \pmod{q_2}$
$\qquad\quad= ((A_3-A_2)*Seed) \pmod{q} + [N_3 - (N_0*a^2 + c*a +c)] \pmod{q_2}$
$\qquad\quad= ((A_3-A_2)*Seed) \pmod{q} + [(N_0*a^2 + c*a +c)*a+c - (N_0*a^2 + c*a + c )] \pmod{q_2}$
$\qquad\quad= ((A_3-A_2)*Seed) \pmod{q} + [N_0*a^3 + c*a^2 +c*a +c - N_0*a^2 - c*a - c] \pmod{q_2}$
$\qquad\quad= ((A_3-A_2)*Seed) \pmod{q} + [N_0*a^3 - N_0*a^2 + c*a^2] \pmod{q_2}$
$\qquad\quad= ((A_3-A_2)*Seed) \pmod{q} + [ N0*a - N_0 + c] * a^2 \pmod{q_2}$
$b_4-b_3 = ((A_4-A_3)*Seed) \pmod{q} + (N_4 - N_3) \pmod{q_2}$
$\qquad\quad= ((A_4-A_3)*Seed) \pmod{q} + [N_4 - (N_0*a^3 + c*a^2 +c*a +c)] \pmod{q_2}$
$\qquad\quad= ((A_4-A_3)*Seed) \pmod{q} + [N_0*a^4 + c*a^3 + c*a^2 +c *a + c - N_0*a^3 - c*a^2 - c*a - c]$
$\qquad\quad= ((A_4-A_3)*Seed) \pmod{q} + [N_0*a^4 + c*a^3 - N_0*a^3] \pmod{q_2}$
$\qquad\quad= ((A_4-A_3)*Seed) \pmod{q} + [N_0*a^4 - N_0*a^3 + c*a^3 ] \pmod{q_2}$
$\qquad\quad= ((A_4-A_3)*Seed) \pmod{q} + [ N_0*a - N_0 + c] * a^3 \pmod{q_2}$
(En notant $q_2$, le modulo du 2nd LCG (mod dans le code))
On obtient ainsi la relation de récurrence :
- $b_{i+1}-b{i} = (A_{i+1}-A_{i})*Seed \pmod{q} + [N0*a-N_0+c]*a^i \pmod{q_2}$
On peut compter le nombre d’inconnus :
- 12 pour la $Seed$
- $N_0$, $a$ et $c$
Ainsi avec 15 équations de cette forme, nous devrions être capable de retrouver tous les inconnus.
A ce stade du CTF, j’ai stoppé cette piste pour une simple.
Pourtant, la méthode présentée devrait très bien fonctionner mais j’ai préféré faire autrement pour m’éviter l’implémentation d’un solver pour ce genre de système.
From Lattice to Crypto
Une chose notable est que le noise ne fait que 32 bits, il est ainsi bien inférieur au résultat de la multiplication matricielle $A_{i}*Seed$ qui elle est dans $GF(q)=GF(279622638356169037213136872013126932777)$
Il est ainsi raisonnable d’écrire :
- $\begin{cases} b_i = A_i*Seed + N_i \pmod{q} \newline N_i « A_i*Seed \end{cases}$
Ce genre de problème s’appelle un LWE
crypto-système et l’utilisation de lattice nous permet de le résoudre.
Nous avons 100 IV d’où: $\exists~(k_1,k_2,…,k_{100})~\in~Z^{100}$ tel que:
$\begin{cases} b_1 - N_1 - k_{1}*q = A_1*Seed \newline b_2 - N_2 - k_{2}*q = A_2*Seed \newline … \newline \end{cases}$
ce qui donne:
$\begin{cases} b_1 - N_1 = A_1*Seed + k_{1}*q \newline b_2 - N_2 = A_2*Seed + k_{2}*q \newline … \newline b_{100} - N_{100} = A_{100}*Seed + k_{100}*q \newline \end{cases}$
Sous forme matricielle, nous avons :
$\begin{bmatrix} A_{1,1} & A_{1,1} & … & A_{1,12} & q & 0 & … & 0 \newline A_{2,1} & ⋱ & & ⋮ & 0 & q & … & 0 \newline ⋮ & & ⋱ & ⋮ & ⋮ & & ⋱ & 0 \newline A_{12,1} & … & … & A_{12,12} & 0 & … & … & q \newline \end{bmatrix}*\begin{bmatrix} S_1 \newline ⋮ \newline S_{12} \newline k_{1} \newline ⋮ \newline k_{100} \newline \end{bmatrix} = b-N$
et
$S_1*\begin{bmatrix} A_{1,1 }\newline ⋮ \newline ⋮ \newline A_{100,1} \newline \end{bmatrix} +~…~+S_n\begin{bmatrix} A_{1,12} \newline ⋮ \newline ⋮ \newline A_{100,12} \newline \end{bmatrix} + k_1* \begin{bmatrix} q \newline 0 \newline ⋮ \newline 0 \newline \end{bmatrix} + k_2* \begin{bmatrix} 0 \newline q \newline ⋮ \newline 0 \newline \end{bmatrix} +~…~+ k_{100}* \begin{bmatrix} 0 \newline 0 \newline ⋮ \newline q \newline \end{bmatrix} = b-N$
On a ainsi un réseau (Lattice) dans une base de $100+12=112$ vecteurs et nous devons trouver le plus petit vecteur s’approchant de $b-N$
L’erreur $N$ étant petite, on peut combiner l’utilisation de l’algorithme LLL ainsi que l’algorithme du plan le plus proche de Babai pour retrouver ce vecteur qui nous donnera la seed.
On a :
def babai_closest_vector(M, G, target):
small = target
for i in reversed(range(M.nrows())):
c = ((small * G[i]) / (G[i] * G[i])).round()
small -= M[i] * c
return target - small
A = [list(As[i][0]) for i in range(1,2*n+1)]
B = [iv[i] for i in range(1,2*n+1)]
T = vector(B)
m = len(A)
L = matrix(ZZ, m + n,m)
L.set_block(0, 0, matrix(A).transpose())
L.set_block(n, 0, matrix.identity(m) * q)
X = L.LLL()
G = X.gram_schmidt()[0]
solution = babai_closest_vector(X[-m:, -m:], G, T)
seed = matrix(GF(q),matrix(GF(q), A).solve_right(solution)).transpose()
Ce cette manière on retrouve la seed !
[3058268284241006487061442364908]
[2402808679325168210186933401485]
[ 912561198398914062812833580123]
[3447026327735407212640466127880]
[3579826512545358844840553316487]
[3929680478630796447813590253380]
[3926341848877002242261661718906]
[ 292072138037108009618964395474]
[ 483084212078556642759837397862]
[1251756688835572777877914645198]
[ 473590955827537442363235226794]
[ 803861956621296089474035124354]
On peut donc isoler les $N_i$:
- $N_i \pmod{q_2} = b_i-A_i*Seed$
mod = 2551431067
Ni = [
int(GF(q)(iv[i]) - (matrix(GF(q), As[i])*seed)[0][0])
for i in range(len(A))
]
Nous avons enfin accès aux valeurs du 2nd PRNG qui lui est trivial à casser:
m = ((Ni[2] - Ni[1]) * pow(Ni[1] - Ni[0],-1,mod)) % mod
c = (Ni[1] - Ni[0]*m) % mod
On obtient :
- $m=1989529305$
- $c=2273245205$
On peut ainsi retrouver $N_0$ = self._lcgseed initial:
state_initial = pow(a,-1,mod)*(Ni[0]-c)%mod
Il ne nous reste plus qu’a modifier la Class d’origine pour y mettre l’état du PRNG :
class PRNG(object):
def __init__(self,a,c,state,seed):
self._seed = None
self.q = 279622638356169037213136872013126932777
self.n = 12
self.A = Matrix(GF(self.q), 1, self.n, [
19159356164385140466,
19848194065535878410,
33461959522325830456,
12213590058439028697,
35299014249932143965,
13327781436808877193,
20921178705527762622,
9371898426952684667,
9769023908222006322,
28712160343104144896,
32272228797175569095,
14666990089233663894
])
# LCG props
self.a = int(a)
self.c = int(c)
self._lcgseed = int(state)
self.mod = int(2551431067)
# LCG
self._seed = seed
@property
def noise(self):
self._lcgseed = (self.a * self._lcgseed + self.c) % self.mod
return self._lcgseed
@property
def seed(self):
return self._seed
def randint(self):
b = (self.A * self.seed + self.noise)[0][0]
self.A = Matrix(GF(self.q), 1, self.n, [
int(x * b^(i+1)) % 2^65 for i, x in enumerate(self.A[0])
])
return b
rng = PRNG(
a=a,
c=c,
state=state_initial,
seed=seed
)
Enfin, on regénère les clés privées pour déchiffrer le flag :
n = 100
p = 1552518092300708935130918131258481755631334049434514313202351194902966239949102107258669453876591642442910007680288864229150803718918046342632727613031282983744380820890196288509170691316593175367469551763119843371637221007210577919
ivs = [long_to_bytes(int(rng.randint())).ljust(16, b'\0') for _ in range(n)]
secret_keys = [int(rng.randint()) | int(rng.randint() << 128) | int(rng.randint() << 256) | int(rng.randint() << 384) | int(rng.randint() << 512) | int(rng.randint() << 640) for _ in range(n)]
shared_secret = pow(user[0], secret_keys[0], p)
sha1 = hashlib.sha1()
sha1.update(str(shared_secret).encode('ascii'))
key = sha1.digest()[:16]
cipher = AES.new(key, AES.MODE_CBC, ivs[0])
print(unpad(cipher.decrypt(unhexlify(bytes(enc[0],'utf-8'))),16))
Ouput:
⚡ root share ✘ sage solver3.sage
b'PWNME{d334d082994743b12464c529ea597ed85dcd08e49e6d2d644b46f295b24a2f25}'
Voici le code complet de solve:
from Crypto.Util.number import bytes_to_long,long_to_bytes
from binascii import unhexlify
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.number import getStrongPrime, long_to_bytes, bytes_to_long
class PRNG(object):
def __init__(self,a,c,state,seed):
self._seed = None
self.q = 279622638356169037213136872013126932777
self.n = 12
self.A = Matrix(GF(self.q), 1, self.n, [19159356164385140466,19848194065535878410,33461959522325830456,12213590058439028697,35299014249932143965,13327781436808877193,20921178705527762622,9371898426952684667,9769023908222006322,28712160343104144896,32272228797175569095,14666990089233663894])
# LCG props
self.a = int(a)
self.c = int(c)
self._lcgseed = int(state)
self.mod = int(2551431067)
# LCG
self._seed = seed
@property
def noise(self):
self._lcgseed = (self.a * self._lcgseed + self.c) % self.mod
return self._lcgseed
@property
def seed(self):
return self._seed
def randint(self):
b = (self.A * self.seed + self.noise)[0][0]
self.A = Matrix(GF(self.q), 1, self.n, [
int(x * b^(i+1)) % 2^65 for i, x in enumerate(self.A[0])
])
return b
def decode_iv(iv):
b = unhexlify(bytes(iv,'utf-8')).strip(b'\0')
return bytes_to_long(b)
def parser(data):
alice,user,iv,enc = [],[],[],[]
for line in data.split('\n'):
if 'Alice' in line:
alice.append(int(line.split("public key: ")[1]))
if 'User' in line:
user.append(int(line.split("public key: ")[1]))
if 'encrypt' in line:
info = eval(line.split("= ")[1])
iv.append(decode_iv(info['iv']))
enc.append(info['encrypted_msg'])
return alice,user,iv,enc
logs = open('logs','r').read()
alice,user,iv,enc = parser(logs)
# Re-Calculate Matrix Ai
q = 279622638356169037213136872013126932777
n = 12
As = [Matrix(GF(q), 1, n, [
19159356164385140466,
19848194065535878410,
33461959522325830456,
12213590058439028697,
35299014249932143965,
13327781436808877193,
20921178705527762622,
9371898426952684667,
9769023908222006322,
28712160343104144896,
32272228797175569095,
14666990089233663894
])]
for b in iv:
As.append(Matrix(GF(q), 1,n, [ int(x * b^(i+1)) % 2^65 for i, x in enumerate(As[-1][0])]))
def babai_closest_vector(M, G, target):
small = target
for i in reversed(range(M.nrows())):
c = ((small * G[i]) / (G[i] * G[i])).round()
small -= M[i] * c
return target - small
A = [list(As[i][0]) for i in range(1,2*n+1)]
B = [iv[i] for i in range(1,2*n+1)]
T = vector(B)
m = len(A)
L = matrix(ZZ, m + n,m)
L.set_block(0, 0, matrix(A).transpose())
L.set_block(n, 0, matrix.identity(m) * q)
X = L.LLL()
G = X.gram_schmidt()[0]
solution = babai_closest_vector(X[-m:, -m:], G, T)
seed = matrix(GF(q),matrix(GF(q), A).solve_right(solution)).transpose()
Ni = [
int(GF(q)(iv[i]) - (matrix(GF(q), As[i])*seed)[0][0])
for i in range(len(A))
]
mod = 2551431067
a = ((Ni[2] - Ni[1]) * pow(Ni[1] - Ni[0],-1,mod)) % mod
c = (Ni[1] - Ni[0]*a) % mod
state_initial = pow(a,-1,mod)*(Ni[0]-c)%mod
rng = PRNG(
a=a,
c=c,
state=state_initial,
seed=seed
)
n,p =100,1552518092300708935130918131258481755631334049434514313202351194902966239949102107258669453876591642442910007680288864229150803718918046342632727613031282983744380820890196288509170691316593175367469551763119843371637221007210577919
ivs = [long_to_bytes(int(rng.randint())).ljust(16, b'\0') for _ in range(n)]
secret_keys = [
int(rng.randint()) |
int(rng.randint() << 128) |
int(rng.randint() << 256) |
int(rng.randint() << 384) |
int(rng.randint() << 512) |
int(rng.randint() << 640)
for _ in range(n)
]
shared_secret = pow(user[0], secret_keys[0], p)
sha1 = hashlib.sha1()
sha1.update(str(shared_secret).encode('ascii'))
key = sha1.digest()[:16]
cipher = AES.new(key, AES.MODE_CBC, ivs[0])
print(unpad(cipher.decrypt(unhexlify(bytes(enc[0],'utf-8'))),16))