Browse Source

encrypt (not complete)

ignalxy 4 years ago
parent
commit
d016ddd844
7 changed files with 209 additions and 71 deletions
  1. 117 28
      lib/common/cipher.dart
  2. 2 0
      lib/main.dart
  3. 57 26
      lib/model/message.dart
  4. 12 10
      lib/model/message.g.dart
  5. 19 6
      lib/presenter/user_chat.dart
  6. 1 1
      pubspec.lock
  7. 1 0
      pubspec.yaml

+ 117 - 28
lib/common/cipher.dart

@@ -1,46 +1,135 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
+import 'dart:math';
 
+import 'package:cookie_jar/cookie_jar.dart';
+import 'package:crypto/crypto.dart';
+import 'package:encrypt/encrypt.dart' as encrypt;
 import 'package:encrypt/encrypt_io.dart';
 import 'package:flutter_secure_storage/flutter_secure_storage.dart';
-import 'package:encrypt/encrypt.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:pointycastle/asymmetric/api.dart';
+import 'package:pointycastle/export.dart';
+import 'package:pointycastle/src/platform_check/platform_check.dart';
 
+import 'api.dart';
 import 'global.dart';
 
-Future<RSAPrivateKey?> getRSAPrivateKey(String username) async {
-  try {
-    final storage = FlutterSecureStorage();
-    final _key = 'e2ee chat private key of $username';
-    String? s = await storage.read(key: _key);
-    if (s == null) {
-      // TODO: 如果没有私钥,需要生成
-      storage.write(key: _key, value: s);
+
+class RSAPublicKeyStore extends RSAPublicKey {
+  RSAPublicKeyStore(BigInt modulus, BigInt exponent) : super(modulus, exponent);
+
+  Map<String, dynamic> toJson() => {"modulus": modulus.toString(), "exponent": exponent.toString()};
+
+  factory RSAPublicKeyStore.fromJson(Map<String, dynamic> map) {
+    return RSAPublicKeyStore(BigInt.parse(map["modulus"]), BigInt.parse(map["exponent"]));
+  }
+
+
+  Future<void> save(String key) async {
+    try {
+      final storage = FlutterSecureStorage();
+      storage.write(key: key, value: jsonEncode(toJson()));
+    } catch (e) {
+      debug('save $key error: $e');
+    }
+  }
+
+  static Future<RSAPublicKeyStore?> load(String key) async {
+    RSAPublicKeyStore? result;
+    try {
+      final storage = FlutterSecureStorage();
+      final json = await storage.read(key: key);
+      result = RSAPublicKeyStore.fromJson(jsonDecode(json!));
+    } catch (e) {
+      debug('load $key error: $e');
+    }
+    return result;
+  }
+}
+
+class RSAPrivateKeyStore extends RSAPrivateKey {
+  RSAPrivateKeyStore(BigInt modulus, BigInt privateExponent, BigInt p, BigInt q)
+      : super(modulus, privateExponent, p, q);
+  Map<String, dynamic> toJson() => {
+        "modulus": modulus.toString(),
+        "privateExponent": privateExponent.toString(),
+        "p": p.toString(),
+        "q": q.toString(),
+      };
+  factory RSAPrivateKeyStore.fromJson(Map<String, dynamic> map) {
+    return RSAPrivateKeyStore(
+      BigInt.parse(map["modulus"]),
+      BigInt.parse(map["privateExponent"]),
+      BigInt.parse(map["p"]),
+      BigInt.parse(map["q"]),
+    );
+  }
+
+
+  Future<void> save(String key) async {
+    try {
+      final storage = FlutterSecureStorage();
+      storage.write(key: key, value: jsonEncode(toJson()));
+    } catch (e) {
+      debug('save $key error: $e');
     }
-    Directory tempDir = await getTemporaryDirectory();
-    String tempPath = tempDir.path;
-    String filePath = '$tempPath/private.pem';
-    final file = File(filePath);
-    final privateKey = await parseKeyFromFile<RSAPrivateKey>(filePath);
-    file.delete();
-    return privateKey;
-  } catch (e) {
-    debug('get rsa private key failed: $e');
+  }
+
+  static Future<RSAPrivateKeyStore?> load(String key) async {
+    RSAPrivateKeyStore? result;
+    try {
+      final storage = FlutterSecureStorage();
+      final json = await storage.read(key: key);
+      result = RSAPrivateKeyStore.fromJson(jsonDecode(json!));
+    } catch (e) {
+      debug('load $key error: $e');
+    }
+    return result;
   }
 }
 
-Future<RSAPublicKey?> getRSAPublicKey(String contents) async {
+AsymmetricKeyPair<RSAPublicKeyStore, RSAPrivateKeyStore> generateRSAKeyPair({SecureRandom? secureRandom, int bitLength = 2048}) {
+  // Create an RSA key generator and initialize it
+
+  secureRandom ??= getSecureRandom();
+
+  final keyGen = RSAKeyGenerator()
+    ..init(ParametersWithRandom(RSAKeyGeneratorParameters(BigInt.parse('65537'), bitLength, 64), secureRandom));
+
+  // Use the generator
+
+  final pair = keyGen.generateKeyPair();
+
+  // Cast the generated key pair into the RSA key types
+
+  final myPublic = pair.publicKey as RSAPublicKeyStore;
+  final myPrivate = pair.privateKey as RSAPrivateKeyStore;
+
+  return AsymmetricKeyPair<RSAPublicKeyStore, RSAPrivateKeyStore>(myPublic, myPrivate);
+}
+
+SecureRandom getSecureRandom() {
+  final secureRandom = SecureRandom('Fortuna')..seed(KeyParameter(encrypt.SecureRandom(32).bytes));
+  return secureRandom;
+}
+
+Future<AsymmetricKeyPair<RSAPublicKeyStore, RSAPrivateKeyStore>> getRSAKeyPair(String username) async {
+  final _privateKey = 'e2ee chat private key of $username';
+  final _publicKey = 'e2ee chat public key of $username';
   try {
-    Directory tempDir = await getTemporaryDirectory();
-    String tempPath = tempDir.path;
-    String filePath = '$tempPath/public.pem';
-    final file = File(filePath);
-    file.writeAsString(contents);
-    final publicKey = await parseKeyFromFile<RSAPublicKey>(filePath);
-    return publicKey;
+    final privateKey = await RSAPrivateKeyStore.load(_privateKey);
+    final publicKey = await RSAPublicKeyStore.load(_publicKey);
+    if (privateKey != null && publicKey != null) {
+      return AsymmetricKeyPair<RSAPublicKeyStore, RSAPrivateKeyStore>(publicKey, privateKey);
+    }
   } catch (e) {
-    debug('get rsa public key failed: $e');
+    debug('get rsa Public key failed: $e');
   }
-}
+  final pair = generateRSAKeyPair();
+  pair.publicKey.save(_publicKey);
+  pair.privateKey.save(_privateKey);
+  await Api().addPublicKey(jsonEncode(pair.publicKey.toJson()));
+  return pair;
+}

+ 2 - 0
lib/main.dart

@@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
 import 'package:provider/single_child_widget.dart';
 import 'package:flutter_localizations/flutter_localizations.dart';
 
+import 'common/cipher.dart';
 import 'common/global.dart';
 import 'l10n/localization_intl.dart';
 import 'presenter/locale.dart';
@@ -17,6 +18,7 @@ import 'view/theme.dart';
 import 'view/home.dart';
 
 void main() {
+  // generateRSAKeyPair();
   WidgetsFlutterBinding.ensureInitialized();
   Global.init().then((e) => runApp(MyApp()));
 }

+ 57 - 26
lib/model/message.dart

@@ -1,49 +1,80 @@
 import 'dart:convert';
 
+import 'package:crypto/crypto.dart';
 import 'package:e2ee_chat/common/cipher.dart';
 import 'package:json_annotation/json_annotation.dart';
 import 'package:dash_chat/dash_chat.dart';
 import 'package:encrypt/encrypt.dart';
 import 'package:pointycastle/asymmetric/api.dart';
+import 'package:crypto/crypto.dart';
 
 part 'message.g.dart';
 
 @JsonSerializable()
 class Message {
-  Message(this.from, this.to, this.cipher, this.cipherText, this.publicKey, this.iv);
-
-  factory Message.encrypt(String from, String to, String plaintext, String publicKey) {
-    // TODO: encrypt
-    String cipher = "";
-    String cipherText = "";
-    String publicKey = "";
-    String iv = "";
-    return Message(from, to, cipher, cipherText, publicKey, iv);
-  }
+  Message(
+      {required this.from,
+      required this.to,
+      required this.encKey64,
+      required this.cipherText64,
+      required this.publicKeyJson,
+      required this.sign64,
+      required this.iv64});
 
   factory Message.fromJson(Map<String, dynamic> json) => _$MessageFromJson(json);
   Map<String, dynamic> toJson() => _$MessageToJson(this);
 
   String from;
   String to;
-  String cipher;
-  String cipherText;
-  String publicKey;
-  String iv;
+  String encKey64;
+  String cipherText64;
+  String publicKeyJson;
+  String sign64;
+  String iv64;
+
+  static Future<Message> encrypt(String from, String to, String plaintext, RSAPublicKeyStore publicKey) async {
+    final key = Key.fromSecureRandom(1024); // aes 密钥
+
+    final keyEnc = Encrypter(RSA(publicKey: publicKey));
+    final encKey = keyEnc.encrypt(key.base64); // rsa 加密 aes 密钥
+
+    final iv = IV.fromLength(64);
+    final textEnc = Encrypter(AES(key));
+    final cipherText = textEnc.encrypt(plaintext, iv: iv); // 密文
+
+    final pair = await getRSAKeyPair(from);
+    final signEnc = Encrypter(RSA(privateKey: pair.privateKey));
+    final digest = sha512.convert(utf8.encode(plaintext)); // hash
+    final sign = signEnc.encrypt(digest.toString()); // hex  签名
+
+    return Message(
+        from: from,
+        to: to,
+        encKey64: encKey.base64,
+        cipherText64: cipherText.base64,
+        publicKeyJson: jsonEncode(pair.publicKey.toJson()),
+        sign64: sign.base64,
+        iv64: iv.base64);
+  }
 
   Future<ChatMessage?> get chatMessage async {
     ChatMessage? _message;
-    final RSAPrivateKey? _privateKey = await getRSAPrivateKey(from);
-    if (_privateKey != null) {
-      final _publicKey = (await getRSAPublicKey(publicKey))!;
-      final _rsaEncrypter = Encrypter(
-          RSA(publicKey: _publicKey, privateKey: _privateKey));
-      final _keyBase64 = _rsaEncrypter.decrypt64(cipher);
-      final _key = Key.fromBase64(_keyBase64);
-      final _iv = IV.fromBase64(iv);
-      final _aesEncrypter = Encrypter(AES(_key));
-      final _decrypted = _aesEncrypter.decrypt64(cipherText, iv: _iv);
-      _message = ChatMessage.fromJson(jsonDecode(_decrypted));
+    final pair = await getRSAKeyPair(from);
+    final keyEnc = Encrypter(RSA(privateKey: pair.privateKey));
+    final keyBase64 = keyEnc.decrypt64(encKey64);
+    final key = Key.fromBase64(keyBase64); // 获取key
+
+    final iv = IV.fromBase64(iv64);
+    final textEnc = Encrypter(AES(key));
+    final plaintext = textEnc.decrypt64(cipherText64, iv: iv);
+
+    final publicKey = RSAPublicKeyStore.fromJson(jsonDecode(publicKeyJson));
+    final signEnc = Encrypter(RSA(publicKey: publicKey));
+    final digestHex = signEnc.decrypt64(sign64);
+    final digest = sha512.convert(utf8.encode(plaintext));
+
+    if (digest.toString() == digestHex) {
+      _message = ChatMessage.fromJson(jsonDecode(plaintext));
     }
     return _message;
   }
@@ -59,4 +90,4 @@ class GroupMessage {
   @Transient()
   ChatMessage get chatMessage => ChatMessage.fromJson(jsonDecode(plaintext));
 }
-*/
+*/

+ 12 - 10
lib/model/message.g.dart

@@ -8,20 +8,22 @@ part of 'message.dart';
 
 Message _$MessageFromJson(Map<String, dynamic> json) {
   return Message(
-    json['from'] as String,
-    json['to'] as String,
-    json['cipher'] as String,
-    json['cipherText'] as String,
-    json['publicKey'] as String,
-    json['iv'] as String,
+    from: json['from'] as String,
+    to: json['to'] as String,
+    encKey64: json['encKey64'] as String,
+    cipherText64: json['cipherText64'] as String,
+    publicKeyJson: json['publicKeyJson'] as String,
+    sign64: json['sign64'] as String,
+    iv64: json['iv64'] as String,
   );
 }
 
 Map<String, dynamic> _$MessageToJson(Message instance) => <String, dynamic>{
       'from': instance.from,
       'to': instance.to,
-      'cipher': instance.cipher,
-      'cipherText': instance.cipherText,
-      'publicKey': instance.publicKey,
-      'iv': instance.iv,
+      'encKey64': instance.encKey64,
+      'cipherText64': instance.cipherText64,
+      'publicKeyJson': instance.publicKeyJson,
+      'sign64': instance.sign64,
+      'iv64': instance.iv64,
     };

+ 19 - 6
lib/presenter/user_chat.dart

@@ -4,6 +4,7 @@ import 'dart:io';
 
 import 'package:dash_chat/dash_chat.dart';
 import 'package:e2ee_chat/common/api.dart';
+import 'package:e2ee_chat/common/cipher.dart';
 import 'package:e2ee_chat/common/global.dart';
 import 'package:e2ee_chat/model/message.dart';
 import 'package:e2ee_chat/model/user.dart';
@@ -104,13 +105,25 @@ class UserChatPresenter extends ChatListPresenter {
     bool _result = true;
     chatMessages.add(chatMessage);
     notifyListeners();
-    if (from != null && to != null) {
+    final _from = from;
+    final _to = to;
+    if (_from != null && _to != null && _from == LoginPresenter().user) {
       try {
-        String plaintext = jsonEncode(chatMessage.toJson());
-        // TODO: encrypt
-        String publicKey = "";
-        final message = Message.encrypt(from!.username, to!.username, plaintext, publicKey);
-        _result = await Api().sendMessage(to!.username, jsonEncode(message.toJson()));
+        String chatMessageJson = jsonEncode(chatMessage.toJson());
+        debug('user chat presenter send msg: chatMessageJson: $chatMessageJson');
+        String? publicKeyJson = (await Api().getPublicKey(_to.username))?.first;
+        debug('user chat presenter send msg: publicKeyJson: $publicKeyJson');
+        if (publicKeyJson != null)
+        {
+          final publicKey = RSAPublicKeyStore.fromJson(jsonDecode(publicKeyJson));
+          final message = await Message.encrypt(_from.username, _to.username, chatMessageJson, publicKey);
+          final messageJson = jsonEncode(message.toJson());
+          debug('user chat presenter send msg: messageJson $messageJson ');
+          _result = await Api().sendMessage(_to.username, messageJson);
+        } else {
+          debug('user chat presenter send msg: get public key failed.');
+          _result = false;
+        }
       } catch (e) {
         debug(e);
         _result = false;

+ 1 - 1
pubspec.lock

@@ -498,7 +498,7 @@ packages:
     source: hosted
     version: "2.0.1"
   pointycastle:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: pointycastle
       url: "https://pub.flutter-io.cn"

+ 1 - 0
pubspec.yaml

@@ -32,6 +32,7 @@ dependencies:
   flutter_secure_storage: ^4.2.0
   crypto: ^3.0.1
   encrypt: ^5.0.0
+  pointycastle: ^3.1.3
   path_provider: ^2.0.2
 
   json_serializable: ^4.1.3