密碼技術與我們息息相關,使用密碼技術不僅僅能夠保證信息的機密性,而且可以保證信息的完整性和可用性,防止信息被篡改、僞造和假冒。一直以來,設計和破解密碼都是人類的專利,然而,隨着人工智能的發展,Google Brain的研究成果《讓神經對抗網絡學習保護通信》(learning to Protect Communications with Adversarial Neural Cryptography), 試圖讓用0和1思考的機器學習對信息進行加密。
下面簡要介紹一下對稱加密算法:假設兩個人要進行通信,定義爲Alice和Bob,很自然,Alice和Bob想要私密的通信,可是有一個破壞者Eve想要竊取Alice和Bob之間的通信信息,所以Alice就把要發送的信息(我們稱其爲明文P)用祕鑰K進行加密得到密文C,然後將密文C發送給Bob,Eve和Bob都能夠接觸到密文C,由於Bob手中有祕鑰K,就可以將密文C恢復成明文P,也就是能夠得到Alice發送的信息,然而Eve僅僅能夠接觸到密文,沒有祕鑰,所以就不能夠得到明文P, 這就是對稱加密算法。
對稱加密系統
Alice,Bob,Eve三個都是神經網絡,它們的參數分別爲, 和。我們向神經網絡Alice中投入明文P和祕鑰K數據,它的計算結果會作爲密文C,與祕鑰K一起交給另一個神經網絡Bob,並優Bob進行解密得到。Eve接收到密文C, 試圖去恢復明文P,得到。這三個神經網絡模塊共同構成了對抗神經網絡的主體,Alice和Bob組成的網絡看做GAN中的生成模型G, 而Eve則被看做GAN中的批判模型D,我們希望Eve恢復祕鑰的概率爲0.5,在這裏是對等於恢復祕鑰的位數爲明文的1/2,即相當於瞎猜一樣。
下面介紹一些網絡的細節:
- Alice和Bob有着相同的網絡結構,但是輸入,輸出和參數不同。
- P和K是由-1和1組成的比特串,和是位於區間(-1,1)之間的浮點數,在最後計算準確率的時候,把和中的浮點數轉換成整數-1和1,大於等於0的爲1,小於0的則爲-1。
- 每一個明文對應一個密文
網絡模型 | 輸入 | 輸出 |
Alice | P, K | |
Bob | C, K | |
Eve | C |
損失函數
Eve目標是能夠準確地從竊聽到的密文C中恢復出明文P,也就是說要儘可能地縮小P和之間誤差,選擇使用L1範數作爲Eve的損失函數,其中d就是指的是P和之間的L1範數值。
Alice和Bob的目標不僅僅使得能夠 使得P和之間的誤差比較小,而且還要能夠安全準確地進行通信,Alice和Bob之間的損失函數爲:
網絡架構
首先一個全連接層,輸入數量等於輸出數量,全連接層的輸入爲明文P和祕鑰K,全連接層後面跟着4個1-D的卷積層,每個卷積層的window size,input depth輸入深度和output depth輸出深度分別爲[4, 1, 2], [2, 2, 4], [1, 4, 4]和[1, 4, 1],步長strides分別爲1,2,1,1,這樣,最後一層的向量的個數爲明文長度。除了最後一個卷積層,其他層(全連接層+3個卷積層)的激活函數爲sigmoid函數,最後一個卷積層的激活函數爲tanh函數,是因爲要使得和的取值位於(-1,1)區間。下圖是Alice神經網絡的架構,由於Bob和Alice網絡架構相似,不同的是Bob的輸入爲密文C和祕鑰K。
由於Eve的輸入僅僅只有祕鑰K,所以網絡架構和Alice和Bob的略微有些差別。需要在全連接層前面再多加上一個N*2N全連接層
結果
下面的訓練結果取自明文和密文的長度都是16, minibatch的大小爲4096。理想的結果應當時Bob恢復明文的錯誤比特數爲0,Eve恢復明文的比特數爲8。在這個例子中,訓練剛開始,Bob和Eve的錯誤位數都是8, 對於一個16位的二進制文本來說,隨便生成的16位數字在概率上錯誤位數就是8--這時候Bob和Eve都還沒有具備解密的能力,伴隨的神經網絡之間的對抗,每個神經網絡不斷的調整參數值,Alice和Bob也逐漸獲得了極強的加密和解密能力。 當迭代7000次的時候,Bob預測明文的錯誤率大幅度下滑,在15000次迭代之後,Bob恢復明文的錯誤率已經幾乎爲0,而這時Eve的錯誤位數仍然在8左右波動,也就是說Eve的破解密碼的能力基本上等同於瞎猜,所以這時候Alice發出的信息非常安全 。
代碼實現
import tensorflow as tf
import numpy as np
import Net
import os
plainTextLength = 16
keyLength = 16
N = plainTextLength / 2
batch = 4096
learningRate = 0.0008
TRAIN_STEP= 20000
iterations = 1
def get_random_block(N, batch):
return 2 * np.random.randint(2, size=(batch, N)) - 1
def train():
with tf.name_scope('input_variable'):
plain = tf.placeholder(tf.float32, shape=[None, plainTextLength], name='plainText')
key = tf.placeholder(tf.float32, shape=[None, keyLength], name='keyText')
Zeros = tf.zeros_like(plain, dtype=tf.float32, name='zeroVector')
#
Alice_output, Bob_output, Eve_output = Net._build_Network(plain, key, plainTextLength, keyLength)
reshape_Bob_output = tf.reshape(Bob_output, shape=[-1, plainTextLength])
reshape_Eve_output = tf.reshape(Eve_output, shape=[-1, plainTextLength])
# Bob L1 loss
with tf.name_scope('Bob_loss'):
Bob_loss = tf.reduce_mean(tf.abs(reshape_Bob_output - plain))
tf.summary.scalar('Bob_loss_value', Bob_loss)
# Eve L1 Loss
with tf.name_scope('Eve_loss'):
Eve_loss = tf.reduce_mean(tf.abs(reshape_Eve_output - plain))
tf.summary.scalar('Eve_loss_value', Eve_loss)
# Alice_Bob Loss
with tf.name_scope('A_B_loss'):
Alice_Bob_loss = Bob_loss + (1 - Eve_loss) ** 2
tf.summary.scalar('Alice_Bob_loss_value', Alice_Bob_loss)
# error
boolean_P = tf.greater(plain, Zeros)
boolean_B = tf.greater_equal(reshape_Bob_output, Zeros)
boolean_E = tf.greater_equal(reshape_Eve_output, Zeros)
accuracy_B = tf.reduce_mean(tf.cast(tf.equal(boolean_B, boolean_P), dtype=tf.float32))
accuracy_E = tf.reduce_mean(tf.cast(tf.equal(boolean_E, boolean_P), dtype=tf.float32))
Bob_bits_wrong = plainTextLength - accuracy_B * plainTextLength
Eve_bits_wrong = plainTextLength - accuracy_E * plainTextLength
tf.summary.scalar('accuracy_B_value', accuracy_B)
tf.summary.scalar('accuracy_E_value', accuracy_E)
tf.summary.scalar('Bob_bits_wrong', Bob_bits_wrong)
tf.summary.scalar('Eve_bits_wrong', Eve_bits_wrong)
A_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, 'Alice')
B_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, 'Bob')
AB_vars = A_vars + B_vars
Eve_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, 'Eve')
Alice_Bob_optimizer = tf.train.AdamOptimizer(learningRate).minimize(Alice_Bob_loss, var_list=AB_vars)
Eve_optimizer = tf.train.AdamOptimizer(learningRate).minimize(Eve_loss, var_list=Eve_vars)
merged = tf.summary.merge_all()
with tf.Session() as session:
session.run(tf.global_variables_initializer())
train_writer = tf.summary.FileWriter('F:\\MachineLearning+DeepLearningCode\\adversarial_crypto\\adver_logs', session.graph)
if not os.path.exists('adver_logs'):
os.makedirs('adver_logs')
for step in range(TRAIN_STEP):
# train Bob
feedDict = {plain: get_random_block(plainTextLength, batch),
key: get_random_block(keyLength, batch)}
for index in range(iterations):
_, Bob_error, Bob_accuracy, Bob_wrong_bits = session.run(
[Alice_Bob_optimizer, Bob_loss, accuracy_B, Bob_bits_wrong], feed_dict=feedDict)
Bob_accuracy_bits = Bob_accuracy * plainTextLength
# train Eve
Eve_feedDict = {plain: get_random_block(plainTextLength, 2 * batch),
key: get_random_block(keyLength, 2 * batch)}
for index in range(2 * iterations):
session.run(Eve_optimizer, feed_dict=Eve_feedDict)
Eve_error, Eve_accuracy, Eve_wrong_bits = session.run([Eve_loss, accuracy_E, Eve_bits_wrong], feed_dict=Eve_feedDict)
Eve_accuracy_bits = Eve_accuracy * plainTextLength
AB_error, summary = session.run([Alice_Bob_loss, merged], feed_dict=feedDict)
'''
if step % 500 == 0:
print('Step:', step)
print('Eve_error:', Eve_error, 'Eve accuracy bits', Eve_accuracy_bits, ' AB_error:', AB_error)
print('Bob_loss:', Bob_error, ' Bob Accuracy Bits:', Bob_accuracy_bits)
'''
train_writer.add_summary(summary, step)
def main(argv=None):
train()
if __name__ == '__main__':
tf.app.run()
#Net.py
import tensorflow as tf
def _conv1D(input, filter, stride, kernelSize, name, activation = tf.nn.sigmoid):
with tf.variable_scope(name):
return tf.layers.conv1d(inputs=input, filters=filter, strides=stride,
kernel_size=kernelSize, padding='SAME', activation=activation, use_bias=False)
def _ConvNet(input, unitsLength):
"""
構建網絡架構
"""
input = tf.convert_to_tensor(input, dtype=tf.float32)
input = tf.reshape(input, shape=[-1, unitsLength, 1])
# print(input.shape)
with tf.name_scope('convlayers'):
conv1 = _conv1D(input, 2, 1, [4], name='conv_1')
conv2 = _conv1D(conv1, 4, 2, [2], name='conv_2')
conv3 = _conv1D(conv2, 4, 1, [1], name='conv_3')
output = _conv1D(conv3, 1, 1, [1], name='conv_4', activation=tf.nn.tanh)
return output
def _build_Network(plain, key, plainTextLength, keyLength):
unitsLength = plainTextLength + keyLength
# 定義Alice網絡
with tf.variable_scope('Alice'):
Alice_input = tf.concat([plain, key], axis=1)
A_w = tf.Variable(tf.truncated_normal(shape=[unitsLength, unitsLength], mean=0, stddev=0.1))
Alice_FC_layer = tf.nn.sigmoid(tf.matmul(Alice_input, A_w))
Alice_output = _ConvNet(Alice_FC_layer, unitsLength)
# print(Alice_output.shape)
reshape_Alice_output = tf.reshape(Alice_output, shape=[-1, plainTextLength])
# 定義Bob網絡
with tf.variable_scope('Bob'):
Bob_input = tf.concat([reshape_Alice_output, key], axis=1)
B_w = tf.Variable(tf.truncated_normal(shape=[unitsLength, unitsLength], mean=0, stddev=0.1))
Bob_FC_layer = tf.nn.sigmoid(tf.matmul(Bob_input, B_w))
Bob_output = _ConvNet(Bob_FC_layer, unitsLength)
# 定義Eve 網絡
with tf.variable_scope('Eve'):
E_w_1 = tf.Variable(tf.truncated_normal(shape=[plainTextLength, unitsLength], mean=0, stddev=0.1))
E_FC_layer1 = tf.nn.sigmoid(tf.matmul(reshape_Alice_output, E_w_1))
E_w_2 = tf.Variable(tf.truncated_normal(shape=[unitsLength, unitsLength], mean=0, stddev=0.1))
E_FC_layer2 = tf.nn.sigmoid(tf.matmul(E_FC_layer1, E_w_2))
Eve_output = _ConvNet(E_FC_layer2, unitsLength)
# print('Alice:', Alice_output.shape, ' Bob:', Bob_output.shape, ' Eve:', Eve_output.shape)
return Alice_output, Bob_output, Eve_output
下面的兩幅圖明文和密文的長度爲16,minibatch大小爲4096時的結果,在tensorboard中顯示,可以看出,Bob幾乎能夠正確地恢復出明文,Eve恢復明文P的錯誤位數穩定於7.6bits。
當明文長度取值32比特時,Eve恢復明文時的錯誤比特數大約穩定於15.3比特(理想應該是16bites),當明文長度取值64比特時,Eve恢復明文時的錯誤比特數大約爲30比特(理想應該是32比特), 我給出的解釋是,當輸入長度增大時,也相當於數據結構變複雜,而此時的網絡架構對於輸入數據就會顯得簡單,處於‘欠擬合’狀態。可是在 對稱密碼中,如果分組長度爲16的話,過於短小,一般爲128bits,所以可以適當增加網絡複雜度。