1. 原理介紹
希爾密碼(Hill Cipher)是運用基本矩陣論原理的代替密碼技術,由 Lester S. Hill 在 1929 年發明,26 個英文字母可表示成 0 ~ 25 的數字,將明文轉化成 n 維向量,與一個 n × n 矩陣相乘后,得到的結果模 26,即可得到密文對應的值
假設對明文 act
加密:a 為 0,b 為 1,t 為 19,對其進行向量化得到 \(M = [0,2,19]^T\)。選取 3 × 3 階矩陣密鑰:
加密過程如下:
得到的密文為 pob
解密時,必須先算出密鑰的逆矩陣,再根據加密的過程做逆運算
2. 矩陣求逆
對於矩陣求逆,常見的有 伴隨矩陣 和 行變換 兩種方法,不過需要注意的是,此處的逆矩陣為模 26 的逆矩陣,也就是所里面所有的運算(加減乘除)都是在模 26 下的運算
2.1 利用伴隨矩陣
一個 \(n \times n\) 矩陣 \(A\),\(A\) 的逆矩陣 \(A^{-1}\) 為 \(\frac{1}{d} \cdot (C_A)^T\)。其中 d 是矩陣 \(A\) 的行列式,\(C_A\) 是 \(A\) 的伴隨矩陣,\((C_A)^T\) 是 \(C_A\) 的轉置矩陣
在求伴隨矩陣的轉置 \((C_A)^T\) 的過程中,只使用了乘法與加減法運算, 並未使用特殊的除法運算,故算法過程與對一般矩陣求 \((C_A)^T\) 無異,區別只在於每次運算時都需要模 26
在求矩陣行列式 \(d\) 的時候,若是采用余子式法,則也與一般矩陣無異。最后使用 擴展歐幾里得 算法判斷逆元的存在性:若不存在逆元,則矩陣在模 26 條件下不可逆;若存在逆元,則結合上述求解出的 \((C_A)^T\) 可計算出 \(A^{-1}\)
2.2 利用高斯行變換
(1) 先不考慮模 26 的條件
根據求伴隨矩陣的過程,有 \(A^{-1} = \frac{1}{d} \cdot (C_A)^T\),那么 \((C_A)^T = d A^{-1}\)。由於矩陣 \(A\) 為整數矩陣,故 \((C_A)^T\) 也為整數矩陣(因為其計算過程中只使用了加減法與乘法)
也就是說,雖然通過 初等行變換 求解出的 \(A^{-1}\) 不是整數矩陣,但計算 \(d A^{-1}\) 的結果一定是整數矩陣。雖然使用初等行變換的過程會產生一些精度誤差,但是可以通過四舍五入的方式避免
同時,在初等行變換的過程中可以很容易求出行列式 \(d\),也就是說可以求出 \(d A^{-1}\),即 \((C_A)^T\)
(2) 結合模 26 的條件
在通過上述方法求解出 \((C_A)^T\) 和 \(d\) 后,采用類似的方式求解 \(\frac{1}{d} \cdot (C_A)^T\) 就可得到矩陣在模 26 條件下的逆矩陣
這種方式的優點在於時間復雜度少,但有可能產生一定的精度問題,在計算除法的過程中很可能會造成精度的丟失,在求解高階矩陣時會產生錯誤
2.3 利用高斯行變換(另)
思考利用行變換求逆的過程,一般是將矩陣的某個元素變為 1 之后,再消去同一列上的其它元素。在沒有模 26 條件下對矩陣求逆時,使用的是除法,但在模 26 條件下時,只有少數元素存在逆元,能夠使用模 26 條件下的除法
回顧 擴展歐幾里得 算法,思考是否存在其它的方式能夠將元素變為 1。如果能夠找到兩個互素的元素 \(a,b\),利用 擴展歐幾里得 算法可以找到 \(x,y\),使得 \(xa+by=1\),通過這種方法也能讓矩陣元素變成 1
那么在模 26 條件下,滿足以下任意一個條件的元素能夠化成 1:
- 自身在模 26 條件下可逆
- 存在另一個同列的元素與自身互素
稍微再細化一些,可以發現以下兩個規律
- 在模 26 條件下,除了 13 的奇數,其它奇數都可逆
- 任意一對奇數和偶數都互素
那么在尋找能夠化成 1 的元素時,可以分為以下幾個步驟:
- 尋找不等於 13 的奇數,若存在,則將該元素化成 1;否則跳轉第 (2) 步
- 若在步驟 (1) 中已經尋找到等於 13 的奇數,則尋找一個不為 0 的偶數,利用 擴展歐幾里得 算法將其化成 1;否則跳轉第 (3) 步
- 不存在能夠化成 1 的元素,矩陣不可逆
舉例
例如要求解矩陣
的逆元
對其擴展矩陣同步執行上述變換,可以得到逆矩陣
就時間復雜度來說,該方法的耗時略高於上文提及的會照成精度損失的高斯行變換,但優點是不會產生精度誤差,在求解高階矩陣時依舊具有很好的效果
3. 代碼實現(python)
3.1 模26求逆
def _ex_gcd(a: int, b: int) -> (int, int, int):
"""
:return: gcd x y
"""
if a == 0 and b == 0:
return None
else:
x1, y1, x2, y2 = 1, 0, 0, 1 # 初始化x1,y1,x2,y2
while b:
q, r = divmod(a, b)
a, b = b, r # gcd(a,b)=gcd(b,a%b)
x1, y1, x2, y2 = x2, y2, x1 - q * x2, y1 - q * y2
return (a, x1, y1) if a > 0 else (-a, -x1, -y1)
def _opt1(matrix_r: list, col: int, n: int):
"""矩陣某行乘以一個數"""
for c in range(col):
matrix_r[c] = (matrix_r[c] * n) % 26
def _opt2(matrix_r1: list, matrix_r2: list, col: int, n: int):
"""某行加上另一行的倍數"""
for c in range(col):
matrix_r1[c] = (matrix_r1[c] + n * matrix_r2[c]) % 26
def _inverse(matrix):
"""模26求逆"""
row, col = len(matrix), len(matrix[0]) # 矩陣的行列
t_matrix = [[matrix[r][c] for c in range(col)] for r in range(row)]
e_matrix = [[0 if c != r else 1 for c in range(col)] for r in range(row)] # 擴展矩陣
for i in range(row):
# 尋找出符合條件的行
odd, even, = None, None
for r in range(i, row):
if t_matrix[r][i] & 1:
odd = r
elif t_matrix[r][i] != 0:
even = r
# 找到對應元素為不等於13的奇數的行
if odd is not None and t_matrix[odd][i] != 13:
_, iv, _ = _ex_gcd(t_matrix[odd][i], 26)
_opt1(t_matrix[odd], col, iv)
_opt1(e_matrix[odd], col, iv)
break
# 找到對應元素分別為奇數和偶數的兩行
elif odd is not None and even is not None:
_, x, y = _ex_gcd(t_matrix[odd][i], t_matrix[even][i])
_opt1(t_matrix[odd], col, x)
_opt2(t_matrix[odd], t_matrix[even], col, y)
_opt1(e_matrix[odd], col, x)
_opt2(e_matrix[odd], e_matrix[even], col, y)
break
else: # 找不到對應的行
return None
# 交換行
if odd != i:
t_matrix[i], t_matrix[odd] = t_matrix[odd], t_matrix[i]
e_matrix[i], e_matrix[odd] = e_matrix[odd], e_matrix[i]
# 對其它行的變換
for r in range(row):
if r != i:
temp = t_matrix[r][i]
_opt2(t_matrix[r], t_matrix[i], col, -temp)
_opt2(e_matrix[r], e_matrix[i], col, -temp)
return e_matrix
3.2 矩陣乘法
def _mul(m1, m2):
"""矩陣乘法"""
row1, col1 = len(m1), len(m1[0])
row2, col2 = len(m2), len(m2[0])
if col1 != row2:
return None
res = [[0 for _ in range(col2)] for _ in range(row1)]
for row in range(row1):
for col in range(col2):
res[row][col] = sum([m1[row][k] * m2[k][col] for k in range(col1)]) % 26
return res
3.3 Hill密碼
此處將 Hill 密碼寫成一個類的形式
class HillCipher:
def __init__(self, matrix):
self.encrypt_key = matrix
self.decrypt_key = _inverse(matrix)
self._n = len(matrix)
def encrypt(self, plaintext: str) -> str:
return self._do(plaintext, self.encrypt_key)
def decrypt(self, ciphertext: str) -> str:
return self._do(ciphertext, self.decrypt_key)
def _do(self, string: str, key) -> str:
"""矩陣乘法"""
res = []
for i in range(0, len(string), self._n):
vector = self._to_vector(string[i:i + self._n])
temp = _mul(key, vector)
s = self._from_vector(temp)
res.append(s)
return ''.join(res)
@staticmethod
def _to_vector(string: str):
"""字符串轉矩陣"""
return [[ord(item) - 97] for item in string.lower()]
@staticmethod
def _from_vector(vector):
"""矩陣轉字符串"""
return ''.join([chr(97 + item[0]) for item in vector])
3.4 測試樣例和結果
根據密鑰矩陣是左乘還是右乘,會產生不同的結果。此處使用的是左乘密鑰矩陣
# 樣例一
key = [[5, 8],
[17, 3]]
plaintext = "loveyourself"
ciphertext = "lvhfyicbsgru"
# 樣例二
key = [[6, 24, 1],
[13, 16, 10],
[20, 17, 15]]
plaintext = "ysezymxvv"
ciphertext = "iqokxwnno"
更多樣例可以自己去網上的在線加解密網站構造
參考資料:《密碼學實驗教程》