-
Notifications
You must be signed in to change notification settings - Fork 89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Enhancement]: Stabilize collaboration #795
Comments
For end to end encryption to easily allow relay server we could use these examples: Example Dart code
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:cryptography/cryptography.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
/// Encrypts a message using AES-GCM.
/// The key is derived by hashing the secret (SHA-256) to produce a 256-bit key.
/// A random 12-byte nonce is generated for each message. The output is:
/// base64( nonce || ciphertext || mac )
Future<String> encryptMessage(String message, String secret) async {
// Derive a 256-bit key from the secret:
final keyBytes = utf8.encode(secret);
final hash = await Sha256().hash(keyBytes);
final secretKey = SecretKey(hash.bytes);
// AES-GCM with 256-bit key:
final algorithm = AesGcm.with256bits();
// Generate a random 12-byte nonce:
final nonce = _generateRandomNonce(12);
// Encrypt the message:
final secretBox = await algorithm.encrypt(
utf8.encode(message),
secretKey: secretKey,
nonce: nonce,
);
// Concatenate nonce, ciphertext, and MAC:
final combined = <int>[];
combined.addAll(nonce);
combined.addAll(secretBox.cipherText);
combined.addAll(secretBox.mac.bytes);
// Return as a Base64-encoded string.
return base64.encode(combined);
}
/// Decrypts a Base64-encoded string that contains nonce || ciphertext || mac.
Future<String> decryptMessage(String encryptedMessage, String secret) async {
// Decode from Base64:
final combined = base64.decode(encryptedMessage);
// AES-GCM typically uses a 12-byte nonce and 16-byte MAC:
const nonceLength = 12;
const macLength = 16;
if (combined.length < nonceLength + macLength) {
throw Exception('Invalid encrypted message');
}
final nonce = combined.sublist(0, nonceLength);
final macBytes = combined.sublist(combined.length - macLength);
final cipherText = combined.sublist(nonceLength, combined.length - macLength);
// Derive the same key from the secret:
final keyBytes = utf8.encode(secret);
final hash = await Sha256().hash(keyBytes);
final secretKey = SecretKey(hash.bytes);
final algorithm = AesGcm.with256bits();
final secretBox = SecretBox(
cipherText,
nonce: nonce,
mac: Mac(macBytes),
);
// Decrypt:
final clearTextBytes = await algorithm.decrypt(
secretBox,
secretKey: secretKey,
);
return utf8.decode(clearTextBytes);
}
/// Generates a list of random bytes of the specified [length].
List<int> _generateRandomNonce(int length) {
final random = Random.secure();
return List<int>.generate(length, (_) => random.nextInt(256));
}
/// ChatClient using WebSockets with end-to-end encryption.
class ChatClient {
final WebSocketChannel channel;
final String encryptionKey; // e.g. the secret extracted from the room URL
ChatClient(String url, this.encryptionKey)
: channel = WebSocketChannel.connect(Uri.parse(url)) {
// Listen for incoming messages.
channel.stream.listen((data) async {
try {
// Assume the incoming data is a Base64 string.
final decrypted = await decryptMessage(data, encryptionKey);
print("Received decrypted: $decrypted");
// Process the decrypted message (update UI, etc.)
} catch (e) {
print("Decryption error: $e");
}
}, onError: (error) {
print("Socket error: $error");
});
}
/// Encrypts the [message] and sends it over the WebSocket.
Future<void> sendMessage(String message) async {
try {
final encrypted = await encryptMessage(message, encryptionKey);
channel.sink.add(encrypted);
print("Sent encrypted: $encrypted");
} catch (e) {
print("Encryption error: $e");
}
}
void dispose() {
channel.sink.close();
}
}
void main() async {
// For example, your room URL might be:
// https://excalidraw.com/#room=968bc76e9d3fa44fbcbd,6TSIcavZwRpKdl7cNLL61w
// Extract the secret part ("6TSIcavZwRpKdl7cNLL61w") to use as the encryption key.
final roomWebSocketUrl = "wss://your-relay-server.example.com";
final secretKey = "6TSIcavZwRpKdl7cNLL61w";
final chatClient = ChatClient(roomWebSocketUrl, secretKey);
// Send an encrypted message:
await chatClient.sendMessage("Hello, end-to-end encrypted world!");
// The client remains active to receive messages.
} For inspiration: ID and secret can be inside the url with the relay server that will be used. When connecting we need to confirm the user if we really want to connect since we then communicate with the relay server. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Which feature is your request related to?
No response
Describe your request for enhancements!
Currently collaboration is experimental since a long time.
We should finally stabilize it.
Some things which should be added
In the future maybe:
Additional context
No response
Code of Conduct
The text was updated successfully, but these errors were encountered: