Brave Sync version 2 aims to make a wire compatible server side protocol which understands components/sync/protocol/sync.proto used by the official Google sync service. Server code is at https://github.com/brave/go-sync.
- Enforce client side encryption
- Doesn't require sign-in to use sync (we use the same "Sync Chain" concept from v1)
- Uses a Brave-operated sync server so no data is sent to Google servers
A "Sync Chain" is configured using a 32-byte random seed generated by the initial client. Then the seed is encoded using BIP39. If another client wants to join the sync chain, they can enter the BIP39 key phrase from the initial client by entering the words manually or scanning a QR code. HKDF-SHA512 is used to derive the Ed25519 signing keypair used for authentication to the sync server.
Client will compose a sync server access token following the format base64(timestamp_hex|signed_timestamp_hex|public_key_hex)
.
Timestamp is fetched from network time; if network time is not available at the moment, client uses local time. And every server response contains Sane-Time-Millis
header which will update the network time. Timestamps over 1 day old are considered expired.
We use the built-in custom passphrase
feature from Chromium sync and encrypt everything client-side. Instead of letting the user pick a passphrase, which may be weak, we force the passphrase to be the BIP39 encoding of the sync seed.
The rest of the encryption is handled by Chromium as follows:
- BIP39 phrase is key-stretched using
scrypt
(N = 2^13, r = 8, p = 11). The first client generates a random salt and sends it to the server. Future clients will receive the salt from the server so that they can derive the same key. - Then the stretched key is used directly as a AES128-CTR-HMAC encryption key.
In components/sync/protocol/sync.proto
, each SyncEntity
contains EntitySpecifics
which is the actual data of each data type. For example,
message BookmarkSpecifics {
optional string url = 1;
optional bytes favicon = 2;
// Contains legacy title which is truncated and may contain escaped symbols.
optional string legacy_canonicalized_title = 3;
// Corresponds to BookmarkNode::date_added() represented as microseconds since
// the Windows epoch.
optional int64 creation_time_us = 4;
optional string icon_url = 5;
repeated MetaInfo meta_info = 6;
reserved 7;
reserved 8;
reserved 9;
// Introduced in M81, it represents a globally unique and immutable ID.
//
// If present, it must be the same as originator_client_item_id in lowercase,
// unless originator client item ID is not a valid GUID. In such cases (which
// is the case for bookmarks created before 2015), this GUID must match the
// value inferred from the combination of originator cache GUID and
// originator client item ID, see InferGuidForLegacyBookmark().
//
// If not present, the value can be safely inferred using the very same
// methods listed above.
optional string guid = 10;
// Contains full title as is. |legacy_canonicalized_title| is a prefix of
// |full_title| with escaped symbols.
optional string full_title = 11;
}
That is the field that will get encrypted and can only be seen by the client.
Other essential fields used for communication and sync conflict resolution will remain plaintext, like device name and type, opaque IDs for synced items and their parents, item version, creation time, etc.
Sync v2 is fully live on Desktop and Android.
iOS supports most of the sync types too: Bookmarks / History / Passwords / Open tabs.