# 安全的即时通信系统
# 1 系统设计
# 1.1 设计目标
本项目旨在实现一个安全的即时通信系统,主要关注点包括信息的加密传输、信息完整性的保障以及用户的隐私保护。系统的主要特点是:
新用户注册与登录功能,用户身份的安全验证,信息加密传输,采用公钥和对称密钥结合,密钥管理功能,信息完整性的校验,用户管理功能。
# 1.2 信息要求
用户注册与登录: 系统应允许新用户创建账户,并提供登录功能。注册过程中应收集必要的信息,如用户名、密码、电子邮件等。密码应通过哈希等安全手段存储。
即时消息交换: 用户应能够实时发送和接收文本消息。系统应支持多种消息类型,如文本、图片、文件等。
# 1.3 开发和运行环境选择
开发语言: 前台开发语言为 Flutter/Dart 以及 React+mui + 后台 python-Flask 框架
开发工具:andriodStudio,pycharm
后台数据库:Mysql
运行环境: MacOS 操作系统,ios 系统
# 2 网络安全相关设计
# 2.1 网络安全相关原理论述
在即时通信系统中,网络安全是至关重要的。本系统采用高级加密标准(AES)进行数据加密,以保证信息的机密性和完整性。AES 是一种广泛使用的对称加密算法,提供了强大的安全性。下面是一些关键概念:
对称加密: 在对称加密中,相同的密钥用于加密和解密数据。这意味着发送方和接收方必须共享同一个密钥。
AES 密钥: AES 密钥是一个特定长度的位序列,用于 AES 加密算法。在我们的系统中,密钥将被动态生成,并且在每次会话开始时更新。
传输向量(IV): 初始化向量(IV)是 AES 加密中使用的一个随机数,用于保证即使相同的数据被加密多次,每次生成的密文也是不同的。这提高了安全性,因为它防止了某些类型的攻击。
密钥交换: 由于 AES 是对称加密,因此需要安全地传输密钥。我们的系统将采用密钥交换协议来安全地共享密钥。
# 2.2 相应功能概要设计
AES 密钥相关功能设计
功能 | 描述 |
---|---|
AES 密钥生成 | 系统将动态生成 AES 密钥。每次用户会话开始时,都会生成一个新的密钥,以确保通信的安全性。 |
传输向量 | 每个加密的消息都会附带一个传输向量,以确保即使相同的消息被重复加密,也会产生不同的密文。 |
加密过程 | 使用生成的 AES 密钥和传输向量对用户的信息进行加密,确保在传输过程中信息的机密性。 |
解密过程 | 接收方使用相同的 AES 密钥和传输向量对收到的信息进行解密,以还原原始消息。 |
密钥和 IV 管理 | 密钥和初始化向量不在服务器上存储,且每次打开页面都会重新生成,增加了系统的安全性。 |
# 3AES 加密系统(AES 密钥模块)详细设计
# 3.1 AES 密钥传输过程:
# 3.2 AES 密钥使用过程:![密钥使用过程(加密解密)]()
# 3.3 AES 模块代码
# 3.3.1AES 密钥生成
获取好友的对话密钥,如果不存在则生成
// 生成 AES 密钥 | |
Uint8List _generateAESKey() { | |
var secureRandom = SecureRandom("Fortuna")..seed(KeyParameter(Uint8List(32))); | |
var key = Uint8List(16); // 128 位 | |
for (int i = 0; i < key.length; i++) { | |
key[i] = secureRandom.nextUint8(); | |
} | |
return key; | |
} | |
// 获取或生成 AES 密钥 | |
Future<Uint8List> getOrGenerateAESKey(int friendId) async { | |
if (_aesKeys.containsKey(friendId)) { | |
return _aesKeys[friendId]!; | |
} else { | |
var aesKey = _generateAESKey(); | |
_aesKeys[friendId] = aesKey; | |
await _storage.write(key: 'aes_key_$friendId', value: base64.encode(aesKey)); | |
print("已生成AESK:"); | |
print(aesKey); | |
return aesKey; | |
} | |
} |
# 3.3.2 密钥传输
# 1. 从 PEM 中解析 RSA 公钥
- 先消除 PEM 格式:
List<int> decodePEM(String pem) { | |
// 去除 PEM 字符串中的头部和尾部,以及换行符 | |
var startsWith = [ | |
'-----BEGIN PUBLIC KEY-----', | |
'-----BEGIN RSA PUBLIC KEY-----' | |
]; | |
var endsWith = [ | |
'-----END PUBLIC KEY-----', | |
'-----END RSA PUBLIC KEY-----' | |
]; | |
bool isOpenPgp = pem.contains('-----BEGIN PGP PUBLIC KEY BLOCK-----'); | |
if (isOpenPgp) { | |
startsWith.add('-----BEGIN PGP PUBLIC KEY BLOCK-----'); | |
endsWith.add('-----END PGP PUBLIC KEY BLOCK-----'); | |
} | |
pem = pem.replaceAll('\r\n', '').replaceAll('\r', '').replaceAll('\n', ''); | |
String? start; | |
for (String s in startsWith) { | |
if (pem.startsWith(s)) start = s; | |
} | |
String? end; | |
for (String s in endsWith) { | |
if (pem.endsWith(s)) end = s; | |
} | |
if (start == null || end == null) { | |
throw Exception('Invalid PEM'); | |
} | |
pem = pem.substring(start.length, pem.length - end.length); | |
// 将 Base64 字符串解码为二进制数据 | |
return base64.decode(pem); | |
} |
- 得到字符串之后再提取 RSA 公钥:
RSAPublicKey parsePublicKeyFromPem(String pemString) { | |
// 解码 PEM 字符串以获取二进制数据 | |
final publicKeyDER = Uint8List.fromList(decodePEM(pemString)); | |
// 解析 ASN.1 结构 | |
var asn1Parser = ASN1Parser(publicKeyDER); | |
var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; | |
// 确保序列包含至少两个元素(模数和指数) | |
if (topLevelSeq.elements.length < 2) { | |
throw Exception('Invalid ASN.1 sequence length'); | |
} | |
var modulus = topLevelSeq.elements[0] as ASN1Integer; | |
var exponent = topLevelSeq.elements[1] as ASN1Integer; | |
// 创建 RSAPublicKey 对象 | |
return RSAPublicKey(modulus.valueAsBigInteger!, exponent.valueAsBigInteger!); | |
} |
# 2. 传输密钥
- 使用 RSA 进行加密,发送 AES 密钥
Uint8List convertStringToUint8List(String str) { | |
// 去除字符串两端的方括号,并分割字符串 | |
var parts = str.substring(1, str.length - 1).split(','); | |
// 将分割得到的字符串数组转换为 int 数组 | |
List<int> intList = parts.map((part) => int.parse(part.trim())).toList(); | |
// 使用 int 数组创建 Uint8List | |
return Uint8List.fromList(intList); | |
} | |
Future<String> encryptMessage(String message, String publicKeyPem) async { | |
final publicKey = parsePublicKeyFromPem(publicKeyPem); | |
final encrypter = en.Encrypter(en.RSA(publicKey: publicKey)); | |
final encrypted = encrypter.encrypt(message); | |
return encrypted.base64; | |
} | |
void sendKey(String message) async { | |
// 等待获取好友的公钥 | |
String publicKey = await getFriendPublicKey(widget.friendId); | |
// Encrypt the key | |
String encryptedKey = await encryptMessage(message, publicKey); | |
print("打印密钥: $message"); | |
print("打印密文"); | |
print(encryptedKey); | |
// Send the encrypted key to the backend | |
http.post( | |
Uri.parse('http://localhost:5000/send_message'), | |
headers: <String, String>{ | |
'Content-Type': 'application/json; charset=UTF-8', | |
}, | |
body: jsonEncode(<String, dynamic>{ | |
'sender_id': widget.userId, | |
'receiver_id': widget.friendId, | |
'message': encryptedKey, | |
'type': 'AESkey', | |
}), | |
); | |
print("AESKey 已发送"); | |
// 将 AES 密钥保存在发送者的本地 | |
Uint8List aesKey = convertStringToUint8List(message); // 假设 message 是 Uint8List 格式的密钥 | |
_userProvider.saveAESKeyForUser(widget.friendId, aesKey); | |
print("密钥已经保存!!!"); | |
} |
# 3. 信息加密
- 之后发送信息使用 AES 密钥加密。
void sendMessage() async{ | |
final text = _messageController.text; | |
if (text.isNotEmpty) { | |
final newMessage = Message(content: text, isUserMessage: true); | |
setState(() { | |
messages.add(newMessage); | |
}); | |
_messageController.clear(); | |
var aesKey = await Provider.of<UserProvider>(context, listen: false).getOrGenerateAESKey(widget.friendId); | |
print("加密时候用的aes密钥:$aesKey"); | |
// 生成随机的 IV | |
var iv = createSecureRandom().nextBytes(16); | |
print("加密时候的初始向量:$iv"); | |
// 加密消息,并将 IV 附加到密文前面 | |
var encryptedMessage = base64.encode(iv) + base64.encode(encryptWithAES(text, aesKey, iv)); | |
Future.delayed(Duration(seconds: 2), () { | |
if (mounted) { | |
setState(() { | |
newMessage.status = MessageStatus.sent; | |
}); | |
} | |
}).catchError((error) { | |
if (mounted) { | |
setState(() { | |
newMessage.status = MessageStatus.failed; | |
}); | |
} | |
}); | |
// 发送消息到后端 | |
http.post( | |
Uri.parse('http://localhost:5000/send_message'), | |
headers: <String, String>{ | |
'Content-Type': 'application/json; charset=UTF-8', | |
}, | |
body: jsonEncode(<String, dynamic>{ | |
'sender_id': widget.userId, | |
'receiver_id': widget.friendId, | |
'message': encryptedMessage, | |
'type':'message', | |
}), | |
); | |
_scrollToBottom(); | |
} | |
} |
AES 传输结果:
# 4 系统测试
点击软件聊天界面,观察是否有信息发送,且信息是否被正确加密解密