Files
customer-im-client-dev/apps/im_app/lib/data/remote/cipher_api_requests.dart
pp-bot 52a3f0f45c
Some checks failed
CI / Lint (push) Has been cancelled
feat(e2e): 端对端加密完全对齐老项目 — cipher_guard_sdk 修正 + EncryptionManager 集成
修正 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>
2026-04-14 21:44:00 +09:00

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) {}
}