-
客户端
- Electron 项目包
- Electron 相关 Node 库
- Django 后端
- Vue 前端(生成静态文件给 Django 使用)
- Vue相关 Node 库
- Django 静态文件夹
- Django 后端
-
服务器
-
服务器后端 Django 项目
首先确保 node 库安装完成,前端需要在 Vue 文件夹内将前端所需库下载下来。然后是 Electron 的所需要的库。
然后 Django 所需的库需要用 Python 下载
Django 关闭 Debug 模式后可能会找不到静态文件
用户数据库设计
参数 | 备注 |
---|---|
userid | 用户ID,由服务器随机发放。 |
username | 用户名,用户注册时使用。 |
IdentityPub | 用户公钥的一种。 |
SignedPub | 用户公钥的一种。 |
OneTimePub | 用户公钥的一种。 |
EphemeralPub | 用户公钥的一种。 |
IdentityPri | 用户私钥的一种。 |
SignedPri | 用户私钥的一种。 |
OneTimePri | 用户私钥的一种。 |
EphemeralPri | 用户私钥的一种。 |
用户数据库是本地存储用户相关信息的数据库,如果是登陆过的用户或者是本机注册用户,则会存储私钥在本地,如果登录用户在本机没有存储私钥,会在登录时提醒,用户在输入私钥后会在本地进行计算,计算出公钥后会与服务器上存储的公钥进行对比,如果一致则说明输入的私钥正确,会将其转码后存储在数据库中,并完成登录。
好友关系数据库设计
参数 | 备注 |
---|---|
userid | 用户ID,由服务器随机发放。 |
whosfriend | 用户ID,用于鉴别本机不同用户的好友。 |
username | 用户名,该好友的用户名。 |
remark | 备注 |
status | 好友当前状态,用于区分是否为正式好友。 |
IdentityPub | 好友公钥之一 |
SignedPub | 好友公钥之一 |
OneTimePub | 好友公钥之一 |
EphemeralPub | 好友公钥之一 |
好友关系数据库是本地存储用户好友的相关信息的数据库,仅存储好友的公钥信息,由于想要达到一种离线消息的效果,好友是指用户单方面添加的好友,添加好友后会从服务器获取好友的公钥信息,添加好友信息到数据库中,然后即可发送消息。在对方登录后,会从服务器中获取到用户发送的消息,但是判断并非是已添加的好友,会先将对方的公钥信息添加到好友数据库中,但是status值设置为0表示并非正式好友。但是由于已经获取到了对方的公钥信息,可以及时对目标消息进行解密,以免造成消息漏收等错误。这类非正式好友会在好友列表中显示并提示是否添加或删除,只有在添加了好友后才可读取目标发送的消息。
消息保存数据库设计
参数 | 备注 |
---|---|
message_id | 消息记录编号。 |
fromUserid | 消息源用户ID |
toUserid | 消息目标用户ID |
kdf_next | KDF函数的下一次输入,用于密钥生成。 |
date | 消息时间。 |
plaintext | 消息明文。 |
EphemeralPri | 本次消息使用的本机用户的临时私钥。若该消息是从他人接收,则不会有私钥。 |
EphemeralPub | 当前消息使用的本机用户的临时公钥。若该消息是接收消息,存储对方的临时公钥,用于解密他人消息。若该消息是发送消息,则存储自己的公钥,在后续发送过程中并不会使用该公钥。 |
消息数据库是本次设计的主要数据库,存储本地所有来回消息,通过这些来回的消息,可以实现消息的链式加密和解密。完成双棘轮加密设计。在本次系统设计中,加密环节是通过双棘轮加密实现的,双棘轮即临时公钥步进棘轮和KDF链棘轮。临时公钥棘轮通过消息发送方生成随机密钥对,将该密钥对的公钥附在消息包中,使用私钥对目标的上一次发送消息到己方的公钥进行DH算法获取KDF的输入和输出,输出将会分成前后两节供加密和下一次加密使用。在加密过程中,会首先获取与目标的上一次发送消息和上一次接收消息,并通过消息中的临时公钥和kdf_next属性来对消息进行加密。在解密过程中,也会获取与目标的上一次发送消息和上一次接收消息,获取消息中的临时私钥和kdf_next加上解密消息包中的临时公钥来对消息进行解密。解密完成后会返回前端并由前端提出存储消息的请求,将该次解密完成的消息明文以及对方的公钥、下一次的KDF值保存在数据库中,然后前端刷新消息列表并重新渲染页面。
服务器端用户数据库设计
参数 | 备注 |
---|---|
userid | 用户ID,注册时服务器随机生成。 |
username | 用户名,用户注册时自定义。 |
password | 登录密码的HASH值。 |
IdentityPub | 用户公钥的一种 |
SignedPub | 用户公钥的一种 |
OneTimePub | 用户公钥的一种 |
EphemeralPub | 用户公钥的一种 |
对于服务器来说,用户数据库是用来登录和对用户身份检验的方式。用户通过获取目标用户的公钥进行加密通讯。用户在登录时会比对password,如果用户本机没有私钥存在会要求用户输入私钥并使用服务器上的公钥对用户提供的私钥进行验证。
服务器端消息数据库设计
参数 | 备注 |
---|---|
fromUserid | 消息源用户ID |
toUserid | 消息目标用户ID |
date | 消息存入服务器的时间 |
ciphertext | 消息包与密文的整合。 |
消息数据库是为用户提供暂存消息服务,通过对用户的身份验证返回其他人发送给用户的消息,然后会删除发送过的消息,保证消息的唯一性。
- 用户注册功能设计
功能 | 参数 | 备注 |
---|---|---|
用户注册 | usernam、password | 通过username和设定password,将username和hash后的password发送到服务器,服务器随机未使用的8位数字作为用户的ID和登录凭据并返回给用户。 |
用户在注册时只需提供username和password,但是前端会向本地后台申请4对新的密钥对,并将公钥与username、password一并发送到服务器,服务器随机8位数字作为用户ID,保存其用户ID、username、hash(password)、4对用户公钥到服务器用户数据库中,成功后返回注册成功的ID给用户用于登录账号。
- 用户登录功能设计
功能 | 参数 | 备注 |
---|---|---|
用户登录 | Userid、password | 使用userid和hash后的password提交到服务端进行登录验证。 |
用户登录功能会经过检查用户在本机是否存有私钥、对password进行hash、提交到服务器让服务器对密码进行比对、返回结果,4个步骤。如果用户登录ID并未在用户本机发现私钥,说明可能用户ID错误或者用户是在其他主机上进行注册,需要用户输入他的私钥,通过本地后端对私钥进行格式检测,返回私钥派生的公钥,前端通过对服务器上该登录用户ID的用户公钥与派生出来的公钥进行比对,如果相同则说明用户输入的私钥是正确的,保存该私钥到本地数据库,最后才向服务器发送登录请求验证登录密码是否正确。
由于该用户ID并未在本地上存储用户私钥,会提示输入私钥才能登录。
如果输入错误的私钥,会提示私钥格式错误并重新登录流程。
私钥如果输入正确返回结果,然后向服务器进行登录请求。
在服务器验证阶段如果失败也会提示错误并重新开始登录流程。
- 好友添加功能设计表
功能 | 参数 | 备注 |
---|---|---|
好友添加 | Userid | 向服务端查询该好友是否存在并返回公钥信息。添加后将公钥信息存储到数据库 |
好友添加在本次系统设计中设计为单方面添加,在搜索到目标ID后即可添加目标到用户的好友列表,同时添加好友的公钥信息到好友关系数据库中。添加成功后即可向目标发送消息,但是对方在接收到消息后会进行选择是否添加用户为好友。所以目标好友是不一定能够接收到用户发送的消息的。
- 好友修改功能设计表
功能 | 参数 | 备注 |
---|---|---|
好友修改 | Userid | 修改好友状态,当接收到非好友信息的时候选择是否添加该用户为好友并解密消息。 |
这个功能是在未知目标发送消息给用户之后,用户会收到这条消息,向服务器获取未知目标的公钥信息并添加到好友关系数据库中,设置status为0,并对消息进行解密并添加到消息数据库中,用户可以在好友列表中看到未知目标,但是需要进行选择是否保留该好友,保留该好友则对好友status修改为1,刷新好友列表,获取目标的消息记录。如果用户选择不保留该好友则会提交请求删除目标好友信息。但是为了保证后续消息能够成功解密和使用正确的目标临时公钥进行加密,非正式好友在删除的时候并不会删除之前的好友消息记录。会在下一次收到该目标消息的时候再次提醒用户是否添加目标为正式好友。
- 发送消息功能表
功能 | 参数 | 备注 |
---|---|---|
发送消息 | Fromuserid、touserid、plaintext | 对明文消息进行加密并上传到服务器暂存消息数据库中。 |
在发送消息功能中,包含加密和发送两个步骤,加密是通过本地后端在读取与目标的消息记录的最后发送消息和最后接收消息后,获取到对方的临时密钥,通过生成新的临时密钥并通过KDF计算和DH计算,对明文进行加密,然后将这个消息的消息源用户ID、消息目标用户ID、消息发送的时间、消息密文、加密所用的混淆、自己使用的临时密钥公钥这些数据进行打包并上传到服务器消息数据库中,等待目标用户获取。在消息上传到服务器成功后也会上传到本地后端消息数据库中,然后刷新消息列表显示这条已发送的消息。
- 接收消息功能表
功能 | 参数 | 备注 |
---|---|---|
接收消息 | 无 | 前端会在登录后、发送消息前向服务器请求获取暂存消息,服务器对已登录用户进行身份验证并检查暂存消息数据库是否有消息目标是已登录用户的消息并返回。 |
接收消息分为从服务器接收消息和解密消息两个步骤,服务器对用户进行身份验证后即会检查暂存数据库是否有目标用户为已登录用户的消息并返回。这里的暂存消息分为已添加好友的消息和为添加好友的消息。前端会通过对数据进行解析,先分离出所有暂存消息的消息源,对消息源进行检查是否为好友,如果不是好友则会向服务器请求消息源好友的公钥信息并将消息源用户添加到已登录用户的待定好友中,如果是好友说明是正式好友或待定好友,可以正常解密。添加完好友后循环对多个消息包进行解密,必须保证每个消息包解密后将明文消息保存在本地数据库后才能对下一条消息进行解密。否则可能会导致双棘轮链错乱,不能正常解密。
- storeMessage() 接口
字段名 | 数据类型 | 字段说明 |
---|---|---|
fromUserid | Int | 消息源的userid |
toUserid | Int | 消息目标的userid |
kdf_next | Char | 消息生成的kdf的下一次输入 |
EphemeralPub | Char | 如果是自己发送的消息,存的是自己的临时公钥,如果是别人发送的消息,存的是目标的临时公钥 |
plaintext | Char | 解密后的明文 |
Date | Char | 消息的时间 |
该接口是用来对本地message数据库进行添加操作的,通过try/except语句如果前端传输的数据有问题,则会返回错误提示。
- filterMessage() 接口
字段名 | 数据类型 | 字段说明 |
---|---|---|
pk | url:int | Pk指的是目标userid,获取与目标userid的来往记录 |
pk指的是url后的关键字,例如 http://localhost:8000/apis/filterMessage/23232323 中,23232323即是pk关键字后端会通过身份验证获取当前登录的用户ID,并已用户id与pk为关键字对消息数据库进行查询。
在查询过程中,由于是需要返回已登录用户与目标用户的消息记录,所以需要将已登录用户与目标用户的往和来消息记录都找出来,在此接口中使用了Python Django框架中的Q() 函数,通过 | & 操作符对数据进行整合。
- storeUser() 接口
字段名 | 数据类型 | 字段说明 |
---|---|---|
Userid | Int | 用户id |
username | Char | 用户名 |
IdentityPub | Char | 用户公钥的一种 |
SignedPub | Char | 用户公钥的一种 |
OneTimePub | Char | 用户公钥的一种 |
EphemeralPub | Char | 用户公钥的一种 |
IdentityPri | Char | 用户私钥的一种 |
SignedPri | Char | 用户私钥的一种 |
OneTimePri | Char | 用户私钥的一种 |
EphemeralPri | Char | 用户私钥的一种 |
storeUser() 接口是用来将已登录用户或者注册用户信息保存到本地user数据库的接口,该接口在用户注册时会用到,将注册成功后的用户私钥和用户ID等相关信息保存在数据库中,在用户登录时会通过getUser接口检查本地数据库是否含有登录用户的相关信息,如果在user数据库中未发现用户私钥信息,需要用户对私钥进行登记,才能正常保证用户加密解密的使用。
- getUser() 接口
字段名 | 数据类型 | 字段说明 |
---|---|---|
Pk | url:int | 获取目标用户的相关信息 |
getUser接口用于获取本地user数据库中的用户信息,在登录时对登录用户进行检测,判断用户是否在本机含有私钥信息。
- checkPri() 接口
字段名 | 数据类型 | 字段说明 |
---|---|---|
IdentityPri | Char | 当用户在非注册机登录时没有用户的私钥信息,需要用户自己上传到本机,后端会检测私钥格式并返回公钥与服务器上的公钥进行比对是否为当前用户真实密钥。 |
CheckPri() 接口用于对用户在登录时由于本机数据库未保存用户的私钥,在用户提供了私钥后对用户的私钥进行检查。将用户所使用的64位16进制数字通过X25519密码规范进行转换,转换成一个私钥对象,通过私钥对象派生出公钥对象并将公钥对象编码成64位的16进制数字并返回,由前端从服务器用户信息中获取登录用户的用户公钥,并进行对比。如果一致则说明用户身份正确,但是还需要对password提交到服务器检测。如果成功则说明用户身份无误,保存用户私钥信息到本地数据库并完成登录。
- storeFriend() 接口
字段名 | 数据类型 | 字段说明 |
---|---|---|
userid | Int | 用户id |
username | Char | 用户公钥的一种 |
remark | Char | 用户公钥的一种 |
status | Int | 是否为正式好友 |
IdentityPub | Char | 用户公钥的一种 |
SignedPub | Char | 用户公钥的一种 |
OneTimePub | Char | 用户公钥的一种 |
EphemeralPub | Char | 用户公钥的一种 |
该接口用于保存好友信息到本地friends数据库中。在添加好友和接收到陌生人消息时使用,通过status字段表示是否为正式好友。同时,该接口通过连接方法的不同如POST/PUT/DELETE区分对friend数据库的添加、更新、删除操作。
在大部分情况下后端会通过对用户的身份验证来保证用户之间不会越权。
- friendsList() 接口
字段名 | 数据类型 | 字段说明 |
---|---|---|
无 | 获取当前登录用户的所有好友信息,包括非正式好友。 | |
Pk | url:int | 获取某一好友的信息。 |
friendsList接口一般很少使用,仅做测试使用,通常情况会包含pk关键字,并通过已登录用户的身份验证获取到已登录用户的好友中的pk用户的信息。
- createNewKeyspair() 接口
字段名 | 数据类型 | 字段说明 |
---|---|---|
无 | 通过该接口生成4对初始密钥对用于用户注册。 |
该接口是在注册过程中由本地后端生成4对密钥对并返回给前端。将公钥发送给服务器进行注册,在注册成功返回用户ID后将公私钥和ID一起保存在user用户数据库中。
- encryptMessage() 接口
字段名 | 数据类型 | 字段说明 |
---|---|---|
toUserid | Int | 消息目标的用户id |
plaintext | Char | 发送给目标的明文信息 |
该接口用于在消息发送时对消息明文进行加密。在加密过程中有查找消息链末端、获取目标用户公钥信息、对明文加密、将密文信息打包返回给前端4个步骤。
在用户发送消息的时候会有4中情况,对目标没有收到也没有发送过消息、从目标接收过消息但是没有发送过消息、对目标发送过消息但是没有接收过消息、与目标有消息往来。这4种情况对应上一次消息产生的kdf_next字段的不同位置,和目标的临时公钥的位置。发送消息会始终生成一个新的临时密钥包含在消息包中,但是目标的临时公钥和kdf_next包含在上一次发送或者接受的消息包中。
- decryptMessage() 接口
字段名 | 数据类型 | 字段说明 |
---|---|---|
fromUserid | Int | 消息源用户id |
toUserid | Int | 消息目标用户id |
ciphertext | Char | 密文 |
aad | Char | 加密salt |
EphemeralPub | Char | 消息公钥 |
该接口用于解密从服务器暂存消息数据库获取到的消息包。与加密一样分为不同的情况获取kdf_next和解密所需的临时私钥和对方的临时公钥。解密后返回给前端然后前端再次申请保存消息到message数据库中。
- 服务器端 user() 接口
方法 | 字段名 | 数据类型 | 字段说明 |
---|---|---|---|
POST | password | Char | 密码的hash值 |
username | Char | 用户名 | |
IdentityPub | Char | 用户公钥之一 | |
SignedPub | Char | 用户公钥之一 | |
OneTimePub | Char | 用户公钥之一 | |
EphemeralPub | Char | 用户公钥之一 | |
POST | password | Char | 用户密码的hash值 |
pk | url:int | 用户id | |
GET | pk | url:int | 获取目标用户的相关信息 |
该接口设计用于用户登录和对用户相关信息的获取。
通过随机一个8位数字并向数据库提交增加,由于设置了userid的主键和unique属性,数据库层面会判断是否重复,如果重复则会添加失败,返回失败结果提示用户重新注册。
最经常使用的接口为user/pk,用于获取服务器端的用户信息,由于用户password信息也是存储在user数据库中,所以会将password字段删除后返回用户信息。
- 服务端 message 相关接口
方法 | 字段名 | 数据类型 | 字段说明 |
---|---|---|---|
POST | fromUserid | Int | 消息源用户id |
toUserid | Int | 消息目标用户id | |
ciphertext | Char | 消息包 | |
Date | Char | 时间 | |
GET | 无 | 获取已登录用户的所有暂存消息 |
对于服务端的message数据库含有上传和获取两种功能并通过访问方法的不同,分成两个接口。
在获取暂存消息的时候会在获取后首先删除获取到的消息,然后才会将这些消息返回,一是可以有效避免服务器消息记录冗余,二是确保消息的安全性。