The Book of Gehn

Affine Cipher

March 20, 2019

A linear cipher like the Hill Cipher is vulnerable to a known plaintext attack: just resolve a set of linear equations and get the secret key.

An affine cipher is a little harder to break, however it could be vulnerable to a differential attack.

Formerly, an affine encryption looks like this

$$ A p_i + B = c_i\quad(\textrm{mod } m)$$

and the decryption like this:

$$ p_i = [A]^{-1} (c_i - B)\quad(\textrm{mod } m)$$

where \(A\) and \(B\) are secret and unknown to the attacker but we can assume that have known shapes of \(n\textrm{x}n\) and \(n\textrm{x}1\) respectively.

Differential cryptanalysis

Consider the following ciphertext and the partial known plaintext:

>>> from cryptonita import B as asbytes
>>> import numpy as np

>>> m = 251
>>> ciphertext = asbytes(b'"\x93&\xd3)\x97\xb0\xa8\xa6\xf17@,f\xb2\x17LsNs\xe0\xd7').toarray()
>>> kplaintext = asbytes(b'..fi....ra..fo..at....').toarray()

From there we can take two plaintexts \(p_i\) and \(p_j\) with their associated ciphertexts \(c_i\) and \(c_j\).

$$ A p_i + B = c_i\quad(\textrm{mod } m)$$ $$ A p_j + B = c_j\quad(\textrm{mod } m)$$

If we substract both equations we obtain a linear system like the Hill Cipher:

$$ A (p_i - p_j) = (c_i - c_j)\quad(\textrm{mod } m) $$

Keep in mind the affine transformation is a block cipher with blocks of \(n\) bytes. So the plaintext/ciphertext pairs must be \(n\)-bytes aligned (they must come from positions multiple of \(n\)).

In order to break an affine cipher we need \(2n\) independent plaintext-ciphertext pairs (for a linear cipher we need just \(n\))

Here is the first two pairs and the first difference:

>>> p11, c11 = kplaintext[2:4].reshape(2,1), ciphertext[2:4].reshape(2,1)
>>> p12, c12 = kplaintext[12:14].reshape(2,1), ciphertext[12:14].reshape(2,1)

>>> dp1 = (p11 - p12) % m
>>> dc1 = (c11 - c12) % m

Here we build the second difference:

>>> p21, c21 = kplaintext[8:10].reshape(2,1), ciphertext[8:10].reshape(2,1)
>>> p22, c22 = kplaintext[16:18].reshape(2,1), ciphertext[16:18].reshape(2,1)

>>> dp2 = (p21 - p22) % m
>>> dc2 = (c21 - c22) % m

Stacking all this together we build the difference matrices for the plaintexts and ciphertexts of shapes \(n\textrm{x}n\).

>>> dP = np.hstack((dp1, dp2))
>>> dC = np.hstack((dc1, dc2))

Remembering that the linear cipher is:

$$ A\ dP = dC \quad(\textrm{mod } m)$$

From here we can obtain \(A\):

$$ A = dC\ [dP]^{-1} \quad(\textrm{mod } m)$$
>>> from cryptonita.mod import inv_matrix
>>> idP = inv_matrix(dP, m)

>>> A = np.dot(dC, idP) % m
>>> A
array([[ 95,   1],
       [ 88, 191]])

>>> iA = inv_matrix(A, m)
>>> iA
array([[  4,  67],
       [123, 161]])

Using one of the plaintext-ciphertext pairs we can obtain the remaining unknown value: the \(B\) vector.

$$ B = c - A p \quad(\textrm{mod } m)$$
>>> B = (c11 - np.dot(A, p11)) % m

Finally, let’s decrypt the message!

>>> cblocks = asbytes(ciphertext).nblocks(2)
>>> pblocks = []

>>> for cb in cblocks:
...     cb = cb.toarray().reshape(2,1)
...     pb = np.dot(iA, cb - B) % m
...     pb = asbytes(pb.reshape(-1))
...     pblocks.append(pb)

>>> plaintext = b''.join(pblocks)
>>> plaintext
'Affine transformation!'

Related tags: cryptography, cryptonita, affine, differential attack

Affine Cipher - March 20, 2019 - Martin Di Paola