Skip to content

Brave Sync v2

Brian Clifton edited this page Sep 1, 2023 · 16 revisions

Sync v2 overview

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.

Differences from chromium sync

  1. Enforce client side encryption
  2. Doesn't require sign-in to use sync (we use the same "Sync Chain" concept from v1)
  3. Uses a Brave-operated sync server so no data is sent to Google servers

Authentication

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.

Client side encryption

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:

  1. 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.
  2. Then the stretched key is used directly as a AES128-CTR-HMAC encryption key.

What gets encrypted

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.

What sync server is able to see

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.

Current status

Sync v2 is fully live on Desktop and Android.

iOS supports most of the sync types too: Bookmarks / History / Passwords / Open tabs.

Clone this wiki locally