Em…,此 Writeup 从举办方嫖来的,之前早已复现,现在翻出来水一篇。 XD

1.题目给了一个 mceliece 加密系统的实现,公钥和密文,需要还原明文

2.经过查找资料,可以找到破解 mceliece 的相关论文 Stern, Jacques. A method for finding codewords of small weight. Coding theory and applications, Volume 388 of Lecture Notes in Computer Science, 1989.提出了一种 Stern Attack 可以攻破该系统。

3.编写 SternsAlgorithm.sage 用 Sage 实现这篇论文的攻击方法。

4.编写 solve.sage,用 SternsAlgorithm 还原明文,得到 flag

SternsAlgorithm.sage:

 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
def _GetRandomPermutationMatrix(n):
# Set up the permutation matrix
rng = range(n); P = matrix(GF(2),n);
for i in range(n):
    p = floor(len(rng)*random());
    P[i,rng[p]] = 1; rng=list(rng[:p])+list(rng[p+1:]);#此处需要加list()转换,python3与python2不同
return copy(P);
def _GetColumnVectorWeight(n):
weight = 0;
for i in range(n.nrows()):
    if n[i,0] == 1:
        weight = weight+1;
return weight;
def SternsAlgorithm(H, w, p, l):
H_Stern = copy(H);
codeword_found = false;
#Begin Stern's Algorithm for finding a weight-w codeword of the code generated by H
while (not codeword_found):
    n_k = H_Stern.nrows();
    k = H_Stern.ncols() - n_k;
    I_n = identity_matrix(n_k);
    singular = true;
    P_Stern = 0; #initialize the permutation matrix
    H_Prime = 0; #initialize the permuted parity-check matrix
    #Search for (n-k) linearly independent columns.
    while singular:
        P_Stern = _GetRandomPermutationMatrix(H_Stern.ncols());
        H_Prime = H_Stern*P_Stern; #permute the matrix
        H_Prime.echelonize(); #row-reduce the first n-k columns
        #If the selected n-k columns do not row-reduce, select a different combination of columns
        if H_Prime.submatrix(0,0,n_k,n_k) == I_n:
            singular = false;
    #initialize and populate the set of n_k-l rows that will be deleted from H_Prime to leave l rows
    Z = set();
    while len(Z) < n_k-l:
        Z.add(randint(0,n_k-1)); #H.nrows()=n_k, but indices start at 0
    Z = list(Z); Z.sort(); #Make Z a sorted list

    #A copy of H_Prime with only the l selected rows (in Z) remaining
    H_Prime_l = copy(H_Prime);
    H_Prime_l = H_Prime_l.delete_rows(Z);
    #Initialize the sets of indices of the columns of H_Prime_l
    X_Indices = list(); Y_Indices = list();
    #Assign a column indices to X or Y randomly (50/50 chance)
    for i in range(k):
        if randint(0,1)==0:
            X_Indices.append(i+n_k);
        else:
            Y_Indices.append(i+n_k);
    #Generate the size-p subsets of X and Y,
    #and initialize the lists containing each subset's sum
    Subsets_of_X_Indices = Subsets(X_Indices,p);
    Subsets_of_Y_Indices = Subsets(Y_Indices,p);
    pi_A = list();
    pi_B = list();
    #Calculate pi(A) for each subset of X
    for i in range(Subsets_of_X_Indices.cardinality()):
        column_sum = 0;
        for j in range(p):
            column_sum = column_sum + H_Prime_l.submatrix(0,Subsets_of_X_Indices[i][j],H_Prime_l.nrows(),1);
    pi_A.append(column_sum);
    #Calculate pi(B) for each subset of Y
    for i in range(Subsets_of_Y_Indices.cardinality()):
        column_sum = 0;
        for j in range(p):
            column_sum = column_sum + H_Prime_l.submatrix(0,Subsets_of_Y_Indices[i][j],H_Prime_l.nrows(),1);
        pi_B.append(column_sum);
    weight_w_codeword = 0; #initialize the codeword
    #Check each pi(A) value against every pi(B) value to check for collisions
    for i in range(len(pi_A)):
        for j in range(len(pi_B)):
        #If a collision occurs, calculate the n-k - bit vector computed by summing the
        #entirety of the columns whose indices are in A U B
            if pi_A[i] == pi_B[j]:  
                sum = 0; #initialize the sum vector
                for k in (Subsets_of_X_Indices[i]):
                    sum = sum + H_Prime.submatrix(0,k,H_Prime.nrows(),1);
                for k in (Subsets_of_Y_Indices[j]):
                    sum = sum + H_Prime.submatrix(0,k,H_Prime.nrows(),1);
                if _GetColumnVectorWeight(sum) == (w-2*p):
                    codeword_found = true;
                    #Since the sum vector has the appropriate weight, the codeword of weight w can now be calculated
                    #Initialize the codeword
                    weight_w_codeword = matrix(GF(2),H_Stern.ncols(),1);
                    #Mark the appropriate positions of the codeword as ones
                    for index in range(n_k):
                        if sum[index,0]==1:
                            weight_w_codeword[index,0] = 1;
                    for k in Subsets_of_X_Indices[i]:
                        weight_w_codeword[k,0] = 1;
                    for k in Subsets_of_Y_Indices[j]:
                        weight_w_codeword[k,0] = 1;
                    #Undo the permuting done when selecting n-k linearly independent columns
                    weight_w_codeword = weight_w_codeword.transpose()*(~P_Stern);
                    return copy(weight_w_codeword);

solve.sage:

 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
load("./SternsAlgorithm.sage")
def SternsAttack(PK, encrypted_message, t, p, l):
#Attacker has knowledge of PK and y (and presumably t), and is looking for a codeword of weight w
y = encrypted_message;
#Calculate a parity check matrix for the code G + {0,y}, where y = mG + e
H = (PK.stack(y)).right_kernel().basis_matrix();
w = t;
#Find a weight w codeword
weight_w_codeword = SternsAlgorithm(H, w, p, l);
#Decrypt the message using the codeword found via Stern's Algorithm
decrypted_message = PK.solve_left((y-weight_w_codeword));
return decrypted_message;
def encrypt(msg,crypto,l):
bin = BinaryStrings()
msg = map(int ,str(bin.encoding(msg)))
msg+=[0 for i in range(l-(len(msg)%l))]
assert(len(msg)%l == 0)
cipher = []
for i in range(len(msg)/l):
    plain = matrix(GF(2),1,l)
    for j in range(l):
        plain[0,j] = msg[i*l+j]
    encrypted = crypto.Encrypt(plain);
    cipher.append(encrypted)
return cipher
def decrypt(cipher,crypto):
plain = ""
tmp = []
for x in cipher:
    decrypted = crypto.Decrypt(x)
    tmp += [ x for x in decrypted[0]]
tmp += [0 for i in range(8-(len(tmp)%8))] 
print (bin_to_ascii(tmp))
from sage.crypto.util import ascii_to_bin,bin_to_ascii
m = 6
n = 2**m
t = floor((2+(2**m-1)/m)/2);
pubkey = load("pubkey.sobj")
cipher = load("cipher.sobj")
plain = []
for encrypted_message in cipher:
    H = (pubkey.stack(encrypted_message)).right_kernel().basis_matrix();
    p = 1;
    k_2 = floor((H.ncols()-H.nrows())/2);
    l_min = floor(log(k_2,2))-1;
    for k in range(1):
        l = l_min + k;
        if (H.nrows() < l):
            l = H.nrows();
        de = SternsAttack(pubkey,encrypted_message,t,p,l);
        print (de)
        plain+=de[0]
plain += [0 for i in range(8-(len(plain)%8))] 
print (bin_to_ascii(plain))

solve.sage 加载 SternsAlgorithm.sage 进行解密,pubkey = load(“pubkey.sobj”) 加载公钥,cipher = load(“cipher.sobj”) 加载密文。

flag