Some checks failed
CI / Lint (push) Has been cancelled
修正 cipher_guard_sdk 4 个关键密码学差异使其与老 Flutter 项目 (im-client-im-dev) 和 iOS
EncryptionManager 完全互操作:
1. AES: 显式 SIC/CTR 模式 + 16 zero-byte IV(原 SDK 用随机 IV + KDF 派生密钥)
2. RSA: bare RSAEngine 无 PKCS1 padding(原 SDK 用 PKCS1Encoding)
3. Session key: 32-char alphanumeric ASCII(原 SDK 用 base64 random bytes)
4. Wire format: base64(ciphertext) 无 IV 前缀
新增 EncryptionManager:
- Per-chat round-based key chain(最多 10 rounds/chat,FIFO 淘汰)
- 登录后自动 setup:RSA 密钥对生成/存储 + 公钥上传 + chat 密钥拉取解密
- API 集成:cipher/v2/key/my, key/set, chat/my
- 消息加密返回 JSON envelope {"round":N,"data":"<base64>"}
- 消息解密兼容 JSON envelope + legacy raw base64
集成到消息流:
- SendMessageUseCase: 发送前加密 content → wireContent
- WsMessageService: 收到消息后解密 content + lastMsg
- 无密钥时 fallback 到明文(对齐 iOS 行为)
注意:/app/api/cipher/v2/key/set 仍为预发布接口,仅测试阶段使用
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
171 lines
4.5 KiB
Dart
171 lines
4.5 KiB
Dart
import 'package:networks_sdk/networks_sdk.dart';
|
|
|
|
/// 对齐老项目 /app/api/cipher/v2/* API 端点
|
|
///
|
|
/// /app/api/cipher/v2/key/set 是预发布接口,仅测试阶段使用。
|
|
|
|
// ── GET /app/api/cipher/v2/key/my — 获取自己的公钥 ────────────────────────
|
|
|
|
class CipherMyKeyResponse {
|
|
final String publicKey;
|
|
final String? encPrivate;
|
|
final int? uid;
|
|
|
|
const CipherMyKeyResponse({
|
|
required this.publicKey,
|
|
this.encPrivate,
|
|
this.uid,
|
|
});
|
|
|
|
factory CipherMyKeyResponse.fromJson(Map<String, dynamic> json) =>
|
|
CipherMyKeyResponse(
|
|
publicKey: (json['public_key'] ?? '') as String,
|
|
encPrivate: json['enc_pk'] as String?,
|
|
uid: json['uid'] as int?,
|
|
);
|
|
}
|
|
|
|
class CipherGetMyKeyRequest extends ApiRequestable<CipherMyKeyResponse?> {
|
|
@override
|
|
String get path => '/app/api/cipher/v2/key/my';
|
|
|
|
@override
|
|
HttpMethod get method => HttpMethod.get;
|
|
|
|
@override
|
|
Map<String, dynamic> get parameters => {};
|
|
|
|
@override
|
|
CipherMyKeyResponse? decodeResponse(dynamic response) {
|
|
final data = (response as dynamic).data;
|
|
if (data is! Map<String, dynamic>) return null;
|
|
return CipherMyKeyResponse.fromJson(data);
|
|
}
|
|
}
|
|
|
|
// ── POST /app/api/cipher/v2/key/set — 上传公钥 ─────────────────────────────
|
|
|
|
class CipherSetKeyRequest extends ApiRequestable<void> {
|
|
final String publicKey;
|
|
final String encPk;
|
|
|
|
CipherSetKeyRequest({required this.publicKey, required this.encPk});
|
|
|
|
@override
|
|
String get path => '/app/api/cipher/v2/key/set';
|
|
|
|
@override
|
|
HttpMethod get method => HttpMethod.post;
|
|
|
|
@override
|
|
Map<String, dynamic> get parameters => {
|
|
'public_key': publicKey,
|
|
'enc_pk': encPk,
|
|
};
|
|
|
|
@override
|
|
void decodeResponse(dynamic response) {}
|
|
}
|
|
|
|
// ── GET /app/api/cipher/v2/chat/my — 获取所有聊天的加密密钥 ─────────────────
|
|
|
|
class CipherChatKeyItem {
|
|
final int? chatId;
|
|
final String? session;
|
|
final int? round;
|
|
|
|
const CipherChatKeyItem({this.chatId, this.session, this.round});
|
|
|
|
factory CipherChatKeyItem.fromJson(Map<String, dynamic> json) =>
|
|
CipherChatKeyItem(
|
|
chatId: json['chat_id'] as int?,
|
|
session: json['session'] as String?,
|
|
round: json['round'] as int?,
|
|
);
|
|
}
|
|
|
|
class CipherGetMyChatKeysRequest
|
|
extends ApiRequestable<List<CipherChatKeyItem>?> {
|
|
@override
|
|
String get path => '/app/api/cipher/v2/chat/my';
|
|
|
|
@override
|
|
HttpMethod get method => HttpMethod.get;
|
|
|
|
@override
|
|
Map<String, dynamic> get parameters => {};
|
|
|
|
@override
|
|
List<CipherChatKeyItem>? decodeResponse(dynamic response) {
|
|
final data = (response as dynamic).data;
|
|
if (data is! List) return null;
|
|
return data
|
|
.cast<Map<String, dynamic>>()
|
|
.map(CipherChatKeyItem.fromJson)
|
|
.toList();
|
|
}
|
|
}
|
|
|
|
// ── GET /app/api/cipher/v2/key/gets — 获取其他用户的公钥 ─────────────────────
|
|
|
|
class CipherUserKeyResponse {
|
|
final int? uid;
|
|
final String? publicKey;
|
|
|
|
const CipherUserKeyResponse({this.uid, this.publicKey});
|
|
|
|
factory CipherUserKeyResponse.fromJson(Map<String, dynamic> json) =>
|
|
CipherUserKeyResponse(
|
|
uid: json['uid'] as int?,
|
|
publicKey: json['public_key'] as String?,
|
|
);
|
|
}
|
|
|
|
class CipherGetUsersKeysRequest
|
|
extends ApiRequestable<List<CipherUserKeyResponse>?> {
|
|
final List<int> userIds;
|
|
|
|
CipherGetUsersKeysRequest({required this.userIds});
|
|
|
|
@override
|
|
String get path => '/app/api/cipher/v2/key/gets';
|
|
|
|
@override
|
|
HttpMethod get method => HttpMethod.get;
|
|
|
|
@override
|
|
Map<String, dynamic> get parameters => {
|
|
'uids': userIds.join(','),
|
|
};
|
|
|
|
@override
|
|
List<CipherUserKeyResponse>? decodeResponse(dynamic response) {
|
|
final data = (response as dynamic).data;
|
|
if (data is! List) return null;
|
|
return data
|
|
.cast<Map<String, dynamic>>()
|
|
.map(CipherUserKeyResponse.fromJson)
|
|
.toList();
|
|
}
|
|
}
|
|
|
|
// ── POST /app/api/cipher/v2/chat/update — 更新聊天加密密钥 ──────────────────
|
|
|
|
class CipherUpdateChatKeysRequest extends ApiRequestable<void> {
|
|
final List<Map<String, dynamic>> sessions;
|
|
|
|
CipherUpdateChatKeysRequest({required this.sessions});
|
|
|
|
@override
|
|
String get path => '/app/api/cipher/v2/chat/update';
|
|
|
|
@override
|
|
HttpMethod get method => HttpMethod.post;
|
|
|
|
@override
|
|
Map<String, dynamic> get parameters => {'sessions': sessions};
|
|
|
|
@override
|
|
void decodeResponse(dynamic response) {}
|
|
}
|