密碼學專題訓練
實 驗 報 告
實驗二 signal協議
2.1簡介
Signal是一種私人通訊平臺,它使用Signal Protocol作為加密協議來保護使用者的資訊保安和隱私。Signal Protocol是一種端到端的加密通訊協議,具有極高的安全性。它被認為是世界上最安全的通訊協議之一。該協議的基本原理是在通訊雙方之間進行終端加密,這意味著只有傳送者和接收者能夠解密訊息,即使Signal自身也無法檢視或儲存訊息的內容。
2.2基礎知識
迪菲-赫爾曼金鑰交換協議(Diffie–Hellman key exchange)
允許通訊兩方在沒有安全預共享金鑰的情況下建立共享秘密,並可以用於加密通訊。DH協議的基本思想是透過模數取冪的方式得出一個共享的秘密值,而這個結果對外界來說是不可知的。
擬定Alice和Bob要確定一個訊息金鑰
1、Alice和Bob各自建立符合DH協議的金鑰對,假設Alice金鑰對為(私鑰A,公鑰A),Bob金鑰對為(私鑰B,公鑰B)
2、雙方傳送自己的公鑰給對方,即使有駭客監聽,他只能得到公鑰A,公鑰B
3、Alice用自己的私鑰和Bob的公鑰計算訊息金鑰為S,即DH (私鑰A,公鑰B) =金鑰S
4、Bob用自己的私鑰和Alice的公鑰計算訊息金鑰也為S;即DH (私鑰B,公鑰A) =金鑰S
5、雙方同時確定了協商金鑰S,後續可以透過S衍生出訊息金鑰,進行加密通訊。
6、駭客只知道公鑰A和公鑰B,因為不知道任意一方的私鑰,所以無法計算出金鑰S。
該實驗中沒有第四次
2.3雙棘輪演算法
棘輪:棘輪就是一種特殊的齒輪,他只能往一個方向轉下去,而不能往回轉。
單棘輪演算法:只向一個方向轉動,不能往回轉,保證前向安全或後向安全。
KDF演算法也是一種棘輪演算法,KDF演算法匯出的KDF鏈只能往後面派生,而不能計算出前向的金鑰,這就保證了,如果某一輪的金鑰被破解出來,但前面的金鑰是無法計算出來的,也就是前面的訊息無法被解密。
如果再加上一個棘輪演算法,就可以再前向安全的基礎上保障後向安全,即一條訊息的金鑰被破解,之前和之後的訊息金鑰都無法推算,這種演算法被稱為“雙棘輪演算法”signal protocol協議雙棘輪加密演算法為:“KDF鏈棘輪”+“DH棘輪”。以保證訊息的前向安全和後向安全。
- 棘輪一
DH棘輪,DH棘輪運作在EK變換的DH鏈上
步進條件:
當一方角色轉換時,即角色在傳送者和接收者之間出現轉換時
步進方式:
①情況一:當接收者轉為傳送者時,生成自己新的EK對,利用自己新EK私鑰,步進棘輪
②情況二:當傳送者轉為接收者時,收到對方新的EK公鑰,利用對方新EK公鑰,步進棘輪
- 棘輪二
KDF棘輪(也稱為對稱金鑰棘輪),KDF棘輪運作在根鏈,接收鏈,傳送鏈這三條鏈上
步進條件:
當要傳送一條新資訊(即需要新的加密金鑰),或者要接收一條新資訊(即需要新的解密金鑰)時
步進方式:
①情況一:當要傳送加密資訊或接收解密資訊時,DH棘輪未出現步進時。直接在當前的傳送或接收鏈上往前做KDF步進
②情況二:當要傳送加密資訊或接收解密資訊時,DH棘輪出現步進時。需要先步進DH棘輪,之後在新的DH棘輪的衍生下,做嶄新的KDF接收鏈或傳送鏈步進
2.4數學定理證明
假設,使用者Alice有一個秘密整數a=6,使用者Bob有一個秘密整數b=15。Alice和Bob提前協商了兩數字,它們分別是素數p=23及其原根g=5(設m是正整數,a是整數,若a mod m的階等於φ(m),則稱a為模m的一個原根。其中φ(m)表示m的尤拉函式)。Alice透過a計算出公共整數A=g^a mod p=5^6 mod 23=8,Bob透過b計算出公共整數B=g^b mod p=5^15 mod 23=19。透過公共網路,Alice和Bob交換了公共整數A和B。然後,A使用自己的秘密整數a和對方的公共整數B計算得到協商數字s=B^a mod p=19^6 mod 23=2,而B也使用自己的秘密整數b和對方的公共整數A計算得到協商整數s=A^b mod p=8^15 mod 23=2。此時,Alice和Bob成功地獲得了相同的協商整數s。而自始至終,Alice和Body只透過公共網路獲取了對方的公共整數,即便有人也得到了公共整數,也無法計算出同樣的協商整數s。
Windows 11
Python Python 3.10.11
Pycharm 2023
四、實驗內容與步驟
該專案分為server.py、client.py、utils.py、gui_client1.py、gui_client2.py、double_ratchet_offline.py
4.1Utils.py:
import base64
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import x25519
def b64_encode(msg):
# base64 encoding helper function
return base64.b64encode(msg)
def b64_decode(msg):
# base64 decoding helper function
return base64.b64decode(msg)
def pad(msg):
# pkcs7 padding
msg = bytes(msg,'ascii')
num = 16 - (len(msg) % 16)
return msg + bytes([num] * num)
def unpad(msg):
# remove pkcs7 padding
return msg[:-msg[-1]]
def hkdf(inp, length):
# use HKDF on an input to derive a key
hkdf = HKDF(algorithm=hashes.SHA256(), length=length, salt=b'',
info=b'', backend=default_backend())
return hkdf.derive(inp)
def pk_to_bytes(pk_obj):
return pk_obj.public_bytes(encoding=serialization.Encoding.Raw,format=serialization.PublicFormat.Raw)
def byte_to_pk(byte):
return x25519.X25519PublicKey.from_public_bytes(byte)
這段 Python 程式碼包含了幾個函式,每個函式都執行不同的加密、編碼或解碼操作。下列是每個函式的作用:
1、b64_encode(msg) 和 b64_decode(msg) 函式是用來執行 Base64 編碼和解碼的操作。Base64 是一種編碼方式,用於將二進位制資料轉換為可列印字元,便於在文字協議中傳輸。b64_encode 接受一個訊息 msg 並對其進行 Base64 編碼,而 b64_decode 則對 Base64 編碼的訊息進行解碼。
2、pad(msg) 函式執行的是 PKCS7 填充。PKCS7 是一種填充方案,用於填充訊息,使其長度能夠被塊大小整除。在這個函式中,訊息 msg 首先被轉換為 ASCII 編碼的位元組序列,然後計算需要填充的位元組數,接著在末尾新增相應數量的位元組以達到填充要求。
3、unpad(msg) 函式執行的是 PKCS7 的反向操作,即移除填充。根據 PKCS7 規則,函式透過檢查最後一個位元組的值確定了需要移除的填充位元組數,然後將這些位元組從訊息中移除。
4、hkdf(inp, length) 函式使用 HKDF(HMAC-based Extract-and-Expand Key Derivation Function)演算法對輸入 inp 執行金鑰派生,生成指定長度 length 的金鑰。HKDF 是一種金鑰派生函式,用於從輸入生成一個或多個金鑰。
5、pk_to_bytes(pk_obj) 和 byte_to_pk(byte) 函式涉及公鑰的序列化和反序列化。pk_to_bytes 函式接受一個公鑰物件 pk_obj,將其轉換為原始的位元組表示。byte_to_pk 函式接受位元組表示的公鑰,並將其轉換回公鑰物件。
4.2double_ratchet_offline.py
在此Python 程式碼實現了端到端加密協議,基於Double Ratchet演算法。這個演算法在實現端到端加密時非常有用,比如在即時通訊應用中確保訊息的安全傳輸。在這個程式碼中Alice和Bob透過X3DH協議進行金鑰交換,然後使用Double Ratchet演算法來進行端到端加密的通訊。
主要部分:
1.金鑰協商和初始化
使用了X25519曲線進行金鑰交換。
X3DH方法用於執行四個Diffie-Hellman金鑰交換,得到一個共享的金鑰sk。init_ratchet方法初始化了根金鑰鏈和傳送/接收金鑰鏈。
2. Double Ratchet
SymmRatchet類包含了狀態以及生成新的對稱金鑰的方法next基於輸入來改變狀態並生成新的金鑰和IV。send方法用於傳送加密訊息,生成密文並將其傳送給對方,同時傳遞當前的DH公鑰。recv方法用於接收加密訊息,解密訊息並列印解密後的內容。
3.加密和解密
採用了對稱加密演算法AES-CBC。
pad和unpad方法用於填充和去除填充,採用了PKCS7標準填充方案。
4.例項化Alice和Bob
Alice和Bob分別執行X3DH金鑰交換,並初始化各自的金鑰鏈。Alice和Bob可以透過send和recv方法進行加密訊息的傳送和接收。
5.流程:
(1)Alice和Bob執行X3DH金鑰交換。
(2)Alice初始化金鑰鏈並使用Bob的DH公鑰進行ratchet。
(3)Alice向Bob傳送加密訊息,同時傳送她的新DH公鑰。
(4)Bob接收Alice的訊息,使用她的DH公鑰進行ratchet並向Alice回覆加密訊息。
這個流程會持續下去,每次傳送訊息時,傳送方都會更新金鑰鏈,並且接收方會使用新的金鑰鏈來解密訊息。這保證了訊息的安全性和前向保密。
4.3使用流程:
首先啟動server.py, 程式自動設定伺服器的主機和埠:
host = 127.0.0.1 port = 7976
將主機地址和埠繫結到Socket並開始監聽連線
執行gui_client1.py
在彈出的視窗輸中入Alice點選continue
執行gui_client2.py
在彈出的兩個對話方塊中可以任意輸入,對方可以接受訊息
實驗程式碼:
double_ratchet_offline.py
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey, Ed25519PrivateKey
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.backends import default_backend
from Cryptodome.Cipher import AES
def b64(msg):
# base64 encoding helper function
return base64.encodebytes(msg).decode('utf-8').strip()
def hkdf(inp, length):
# use HKDF on an input to derive a key
hkdf = HKDF(algorithm=hashes.SHA256(), length=length, salt=b'',
info=b'', backend=default_backend())
return hkdf.derive(inp)
class SymmRatchet(object):
def __init__(self, key):
self.state = key
def next(self, inp=b''):
# turn the ratchet, changing the state and yielding a new key and IV
print("state", self.state)
print("inp", inp)
output = hkdf(self.state + inp, 80)
print("output", output)
self.state = output[:32]
outkey, iv = output[32:64], output[64:]
return outkey, iv
class Bob(object):
def __init__(self):
# generate Bob's keys
self.IKb = X25519PrivateKey.generate()
self.SPKb = X25519PrivateKey.generate()
self.OPKb = X25519PrivateKey.generate()
self.DHratchet = X25519PrivateKey.generate()
def x3dh(self, alice):
# perform the 4 Diffie Hellman exchanges (X3DH)
dh1 = self.SPKb.exchange(alice.IKa.public_key())
dh2 = self.IKb.exchange(alice.EKa.public_key())
dh3 = self.SPKb.exchange(alice.EKa.public_key())
dh4 = self.OPKb.exchange(alice.EKa.public_key())
# the shared key is KDF(DH1||DH2||DH3||DH4)
self.sk = hkdf(dh1 + dh2 + dh3 + dh4, 32)
print(f"sk:: {self.sk} \n\n")
print('[Bob]\tShared key:', b64(self.sk))
def init_ratchets(self):
# initialise the root chain with the shared key
self.root_ratchet = SymmRatchet(self.sk)
# initialise the sending and recving chains
self.recv_ratchet = SymmRatchet(self.root_ratchet.next()[0])
self.send_ratchet = SymmRatchet(self.root_ratchet.next()[0])
def dh_ratchet(self, alice_public):
# perform a DH ratchet rotation using Alice's public key
dh_recv = self.DHratchet.exchange(alice_public)
shared_recv = self.root_ratchet.next(dh_recv)[0]
# use Alice's public and our old private key
# to get a new recv ratchet
self.recv_ratchet = SymmRatchet(shared_recv)
print('[Bob]\tRecv ratchet seed:', b64(shared_recv))
# generate a new key pair and send ratchet
# our new public key will be sent with the next message to Alice
self.DHratchet = X25519PrivateKey.generate()
dh_send = self.DHratchet.exchange(alice_public)
shared_send = self.root_ratchet.next(dh_send)[0]
self.send_ratchet = SymmRatchet(shared_send)
print('[Bob]\tSend ratchet seed:', b64(shared_send))
def send(self, alice, msg):
key, iv = self.send_ratchet.next()
cipher = AES.new(key, AES.MODE_CBC, iv).encrypt(pad(msg))
print('[Bob]\tSending ciphertext to Alice:', b64(cipher))
# send ciphertext and current DH public key
alice.recv(cipher, self.DHratchet.public_key())
def recv(self, cipher, alice_public_key):
# receive Alice's new public key and use it to perform a DH
self.dh_ratchet(alice_public_key)
key, iv = self.recv_ratchet.next()
# decrypt the message using the new recv ratchet
msg = unpad(AES.new(key, AES.MODE_CBC, iv).decrypt(cipher))
print('[Bob]\tDecrypted message:', msg)
class Alice(object):
def __init__(self):
# generate Alice's keys
self.IKa = X25519PrivateKey.generate()
self.EKa = X25519PrivateKey.generate()
self.DHratchet = None
def x3dh(self, bob):
# perform the 4 Diffie Hellman exchanges (X3DH)
dh1 = self.IKa.exchange(bob.SPKb.public_key())
dh2 = self.EKa.exchange(bob.IKb.public_key())
dh3 = self.EKa.exchange(bob.SPKb.public_key())
dh4 = self.EKa.exchange(bob.OPKb.public_key())
# the shared key is KDF(DH1||DH2||DH3||DH4)
self.sk = hkdf(dh1 + dh2 + dh3 + dh4, 32)
print('[Alice]\tShared key:', b64(self.sk))
def init_ratchets(self):
# initialise the root chain with the shared key
self.root_ratchet = SymmRatchet(self.sk)
# initialise the sending and recving chains
self.send_ratchet = SymmRatchet(self.root_ratchet.next()[0])
self.recv_ratchet = SymmRatchet(self.root_ratchet.next()[0])
def dh_ratchet(self, bob_public):
# perform a DH ratchet rotation using Bob's public key
if self.DHratchet is not None:
# the first time we don't have a DH ratchet yet
dh_recv = self.DHratchet.exchange(bob_public)
shared_recv = self.root_ratchet.next(dh_recv)[0]
# use Bob's public and our old private key
# to get a new recv ratchet
self.recv_ratchet = SymmRatchet(shared_recv)
print('[Alice]\tRecv ratchet seed:', b64(shared_recv))
# generate a new key pair and send ratchet
# our new public key will be sent with the next message to Bob
self.DHratchet = X25519PrivateKey.generate()
dh_send = self.DHratchet.exchange(bob_public)
shared_send = self.root_ratchet.next(dh_send)[0]
self.send_ratchet = SymmRatchet(shared_send)
print('[Alice]\tSend ratchet seed:', b64(shared_send))
def send(self, bob, msg):
key, iv = self.send_ratchet.next()
cipher = AES.new(key, AES.MODE_CBC, iv).encrypt(pad(msg))
print('[Alice]\tSending ciphertext to Bob:', b64(cipher))
# send ciphertext and current DH public key
bob.recv(cipher, self.DHratchet.public_key())
def recv(self, cipher, bob_public_key):
# receive Bob's new public key and use it to perform a DH
self.dh_ratchet(bob_public_key)
key, iv = self.recv_ratchet.next()
# decrypt the message using the new recv ratchet
msg = unpad(AES.new(key, AES.MODE_CBC, iv).decrypt(cipher))
print('[Alice]\tDecrypted message:', msg)
def pad(msg):
# pkcs7 padding
num = 16 - (len(msg) % 16)
return msg + bytes([num] * num)
def unpad(msg):
# remove pkcs7 padding
return msg[:-msg[-1]]
alice = Alice()
bob = Bob()
# Alice performs an X3DH while Bob is offline, using his uploaded keys
alice.x3dh(bob)
# Bob comes online and performs an X3DH using Alice's public keys
bob.x3dh(alice)
# Initialize their symmetric ratchets
alice.init_ratchets()
bob.init_ratchets()
# Initialise Alice's sending ratchet with Bob's public key
alice.dh_ratchet(bob.DHratchet.public_key())
# Alice sends Bob a message and her new DH ratchet public key
alice.send(bob, b'Hello Bob!')
# Bob uses that information to sync with Alice and send her a message
bob.send(alice, b'Hello to you too, Alice!')
# # Alice sends Bob a message and her new DH ratchet public key
# alice.send(bob, b'asfd Bob!')
# # Bob uses that information to sync with Alice and send her a message
# bob.send(alice, b'Heasdfo, Alice!')
# # Alice sends Bob a message and her new DH ratchet public key
# alice.send(bob, b'Hasdfllo Bob!')
# # Bob uses that information to sync with Alice and send her a message
# bob.send(alice, b'Hello asdfasd, Alice!')
Sever.Py
import socket
import threading
import json
import time
print("Server starting....")
time.sleep(2)
host = '127.0.0.1' #LocalHost
port = 7976 #Choosing unreserved port
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #socket initialization
server.bind((host, port)) #binding host and port to socket
server.listen()
print(f"Server Listening at: {host, port}")
clients = []
nicknames = []
pub_key_bundle = {}
def broadcast(message): #broadcast function declaration
for client in clients:
client.send(message)
def handle(client):
while True:
try: #recieving valid messages from client
message = client.recv(2048).decode('utf-8')
print(message)
try:
act_msg = message.split(":")[1]
# print(act_msg)
except:
act_msg = message
if act_msg in nicknames:
# print(True)
send_pk = pub_key_bundle[act_msg]
# print(send_pk)
client.send(str(send_pk).encode('utf-8'))
else:
for i in clients:
if(i != server and i != client):
i.sendall(message.encode('utf-8'))
# broadcast(message.encode('utf-8'))
except: #removing clients
index = clients.index(client)
clients.remove(client)
client.close()
nickname = nicknames[index]
broadcast('{} left!'.format(nickname).encode('utf-8'))
nicknames.remove(nickname)
break
def receive(): #accepting multiple clients
while True:
client, address = server.accept()
print("Connected with {}".format(str(address)))
#send initial message to client
client.send('NICKNAME'.encode('utf-8'))
#receive nickname and pk from client
nickname = client.recv(2048).decode('utf-8')
client_pk = client.recv(2048)
pub_key_bundle[nickname] = client_pk
nicknames.append(nickname)
clients.append(client)
print("Nickname is {}".format(nickname))
print(f'[{nickname}]: PK received')
broadcast("{} joined!".format(nickname).encode('utf-8'))
client.send('Connected to server!'.encode('utf-8'))
#sending public key bundle to client
pk_bundle_endoded = "\nAvailable Users\n" + str(nicknames)
client.send(pk_bundle_endoded.encode('utf-8'))
# time.sleep(0.5)
# client.send("Talk".encode('utf-8'))
thread = threading.Thread(target=handle, args=(client,))
thread.start()
receive()
Cilint客戶端
from logging import raiseExceptions
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives.asymmetric import x25519
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey, Ed25519PrivateKey
from Cryptodome.Cipher import AES
import json
import socket, threading
from utils import *
import ast
import time
ROOT_KEY = b"o\x99\xa1\xdd@#\xc0\x0b \xec\xf5\x80GI\xbf\xca\x8b\x16}L;j\x02f\x07'\x88\x8f\x816e4"
nickname = input("Choose your nickname: ")
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #socket initialization
client.connect(('127.0.0.1', 7976)) #connecting client to server
class SymmetricRatchet(object):
def __init__(self, key) -> None:
self.state = key
def next(self, inp=b''):
# print("state",self.state)
output = hkdf(self.state + inp, 80)
# print(output)
self.state = output[:32]
outkey = output[32:64]
iv = output[64:]
return outkey, iv
class Client(object):
def __init__(self) -> None:
self.DHratchet = X25519PrivateKey.generate()
self.sk = ROOT_KEY
def init_ratchets(self):
self.root_ratchet = SymmetricRatchet(self.sk)
self.recv_ratchet = SymmetricRatchet(self.root_ratchet.next()[0])
self.send_ratchet = SymmetricRatchet(self.root_ratchet.next()[0])
def dh_ratchet(self, alice_pk):
self.DHratchet = X25519PrivateKey.generate()
dh_send = self.DHratchet.exchange(alice_pk)
shared_send = self.root_ratchet.next(dh_send)[0]
self.send_ratchet = SymmetricRatchet(shared_send)
print('Send ratchet seed:', b64_encode(shared_send))
def receive_ratchet(self,alice_pk):
dh_recv = self.DHratchet.exchange(alice_pk)
shared_recv = self.root_ratchet.next(dh_recv)[0]
self.recv_ratchet = SymmetricRatchet(shared_recv)
print('Recv ratchet seed:', b64_encode(shared_recv))
def enc(self, msg):
key, iv = self.send_ratchet.next()
cipher = AES.new(key, AES.MODE_CBC, iv).encrypt(pad(msg))
return cipher, self.DHratchet.public_key()
def dec(self, cipher, alice_pk):
self.receive_ratchet(alice_pk)
key, iv = self.recv_ratchet.next()
msg = unpad(AES.new(key, AES.MODE_CBC, iv).decrypt(cipher))
print(str(msg,'utf-8'))
alice = Client()
alice.init_ratchets()
pk_obj = alice.DHratchet.public_key()
init_pk = pk_obj.public_bytes(encoding=serialization.Encoding.Raw,format=serialization.PublicFormat.Raw)
'''
Initial Client is alice, subsequent client is BOB
Here Bob tries to communicate with Alice first
'''
def receive():
while True: #making valid connection
try:
message = client.recv(2048).decode('utf-8')
if message == 'NICKNAME': #hello message received from the server.
print("## ##")
print("# Registration Phase #")
print("## ##\n")
init_server_msg = nickname
client.send(init_server_msg.encode('utf-8'))
client.send(init_pk)
print("Public Key Sent to user")
elif message[0:1] == "[": ## receiving pk bundle from server i.e a list.
available_users = ast.literal_eval(message) # convert list
print("## ##")
print("# Active Users #")
print("## ##\n")
print(available_users)
print("Who would you like to talk to??")
elif message[0:4] == "Talk":
print("Who would you like to talk to??")
elif message[0:2] == "b'" or message[0:2] == 'b"': ## if message is pubkey then starts with b(byte)
global alice_pk
if message[-2] == "=":
# print("received_msg_nPK", alice_pk)
byte_msg = ast.literal_eval(message)
decode_msg = b64_decode(byte_msg)
# print("decoded msg:", decode_msg)
alice.dec(decode_msg, alice_pk)
else:
mes = ast.literal_eval(message)
# print(len(mes))
alice_pk = x25519.X25519PublicKey.from_public_bytes(mes)
print("PK received")
else:
print(message)
except Exception as e: #case on wrong ip/port details
print(e)
print("An error occured!")
client.close()
break
def write():
count = 0
while True: #message layout
message = '{}:{}'.format(nickname, input(''))
count += 1
try:
if alice_pk:
count = 2
except:
pass
if count > 1:
alice.dh_ratchet(alice_pk)
cipher, pk = alice.enc(message)
pk_byte = pk_to_bytes(pk)
client.send(str(pk_byte).encode('utf-8'))
time.sleep(0.5)
c = b64_encode(cipher)
client.send(str(c).encode('utf-8'))
else:
client.send(str(message).encode('utf-8'))
receive_thread = threading.Thread(target=receive) #receiving multiple messages
receive_thread.start()
write_thread = threading.Thread(target=write) #sending messages
write_thread.start()
實驗心得:
在本次實驗中,我成功設計並實現了基於signal協議的雙棘輪端到端通訊程式。透過該程式,我實現了安全的、實時的通訊,並保護了通訊資料的機密性和完整性。
瞭解了signal的雙棘輪的原理,學習了一些python庫的使用。