Neo Diffie Hellman

25 Jun 2025 • 3 min read

Did you try Diffie-Hellman with matrices before

السلام عليكم ورحمة الله وبركاته

Today challenge add a trick to Diffie-Hellman key exchange what happens if we use matrices ?

here is the challenge code:

PYTHON
from hashlib import sha256
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes, random
from Crypto.Util.Padding import pad


p = 1000117

Fq = GF(p)

G = matrix(Fq, [[8544, 7125, 942, 1054, 2338, 8223, 1149, 3981],
                [7803, 9243, 6830, 8788, 9576, 1916, 7762, 5861],
                [9026, 9381, 9235, 994, 6194, 508, 7351, 1406],
                [6410, 6445, 6086, 653, 1783, 4564, 8874, 4739],
                [2797, 8921, 113, 1078, 6810, 7392, 3659, 1316],
                [1688, 1010, 631, 6495, 7379, 5804, 7237, 527],
                [2211, 4452, 1519, 498, 9284, 3282, 9628, 4355],
                [1267, 9413, 3340, 2316, 8627, 1310, 4481, 4808]])

secret_a = random.randint(int(1000000), int(100000000))
secret_b = random.randint(int(1000000), int(100000000))

pub_a = G^secret_a
pub_b = G^secret_b

print("pubkey A:")
print(pub_a)
print("pubkey B:")
print(pub_b)

key_a = pub_b^secret_a
key_b = pub_a^secret_b

assert key_a == key_b

flattened = key_a.list()
matrix_bytes = ",".join(map(str, flattened)).encode('utf-8')

key = sha256(matrix_bytes).digest()

flag = open('flag', 'r').read().encode()

iv = get_random_bytes(16)
padded_plaintext = pad(flag, AES.block_size)

cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(padded_plaintext)

print(f"iv: {iv.hex()}")
print(f"flag: {ciphertext.hex()}")

The first thing I have noticed is that the range of exponents could be brute forced

PYTHON
secret_a = random.randint(int(1000000), int(100000000))
secret_b = random.randint(int(1000000), int(100000000))

so I have implemented a script to brute force the secret by guessing it here is the code

PYTHON
from hashlib import sha256
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from multiprocessing import Pool, cpu_count
import sys


p = 1000117
Fq = GF(p)

G = matrix(Fq, [[8544, 7125, 942, 1054, 2338, 8223, 1149, 3981],
                [7803, 9243, 6830, 8788, 9576, 1916, 7762, 5861],
                [9026, 9381, 9235, 994, 6194, 508, 7351, 1406],
                [6410, 6445, 6086, 653, 1783, 4564, 8874, 4739],
                [2797, 8921, 113, 1078, 6810, 7392, 3659, 1316],
                [1688, 1010, 631, 6495, 7379, 5804, 7237, 527],
                [2211, 4452, 1519, 498, 9284, 3282, 9628, 4355],
                [1267, 9413, 3340, 2316, 8627, 1310, 4481, 4808]])

pub_a = matrix(Fq, [[535437, 436702, 226549, 36181, 121389, 153630, 731259, 540567],
                   [907971, 938518, 894603, 755768, 216225, 593672, 741423, 23476],
                   [722305, 647423, 326338, 242088, 488457, 728979, 922735, 747889],
                   [297685, 306919, 290639, 27509, 2322, 325140, 477421, 280920],
                   [29774, 527786, 611495, 899899, 521717, 533020, 146923, 228648],
                   [220169, 473019, 557359, 889119, 915468, 309429, 426937, 289970],
                   [353297, 925012, 273876, 541080, 490035, 332930, 328121, 278415],
                   [486511, 551604, 110330, 675237, 32977, 360728, 468534, 470750]])

pub_b = matrix(Fq, [[278458, 53534, 847067, 639466, 299135, 226889, 76846, 630318],
                    [389389, 394186, 698985, 793202, 290495, 837646, 870685, 718848],
                    [758075, 979002, 904988, 856135, 697027, 565219, 562831, 586066],
                    [610508, 496282, 719959, 310184, 841117, 700200, 225924, 938975],
                    [553891, 268611, 42248, 348624, 769549, 609875, 442900, 984258],
                    [397633, 478352, 880372, 982228, 238901, 18500, 192661, 872537],
                    [927550, 649966, 414777, 456967, 907846, 112230, 445766, 510641],
                    [554075, 858774, 422448, 789101, 664939, 373076, 823091, 439356]])


iv = bytes.fromhex("dd389f38c4980b66ac5fd4c9cd5a7484")
ciphertext = bytes.fromhex("a514a4defc7a3c6a1024641231b6fb8b255f234ff6100aff911ff4b5b6a7990f5210c1768977d0dd900e323ab320ed67")


def check_secret(start_end):
    start, end = start_end
    for secret_guess in range(start, end + 1):
        if (G^secret_guess) == pub_a:
            return secret_guess
    return None

def brute_force_parallel():
    num_cores = cpu_count()
    print(f"Using {num_cores} CPU cores for parallel search")
    
    
    total_range = 100_000_000 - 1_000_000
    chunk_size = total_range // num_cores
    ranges = [(1_000_000 + i*chunk_size, 
              1_000_000 + (i+1)*chunk_size - 1) for i in range(num_cores)]
    
    
    ranges[-1] = (ranges[-1][0], 100_000_000)
    
    with Pool(num_cores) as pool:
        results = pool.imap_unordered(check_secret, ranges)
        for result in results:
            if result is not None:
                pool.terminate()  
                return result
    return None

if __name__ == '__main__':
    print("Starting parallel brute-force attack...")
    secret_a = brute_force_parallel()
    
    if secret_a:
        print(f"\nFound secret_a: {secret_a}")
        
        
        shared_key_matrix = pub_b^secret_a
        flattened = shared_key_matrix.list()
        key_bytes = ",".join(map(str, flattened)).encode()
        aes_key = sha256(key_bytes).digest()
        
        cipher = AES.new(aes_key, AES.MODE_CBC, iv)
        decrypted = unpad(cipher.decrypt(ciphertext), AES.block_size)
        print(f"\nDecrypted flag: {decrypted.decode()}")
    else:
        print("Failed to find secret_a")

Hope that this was useful thanks for reading.

Start searching

Enter keywords to search articles.