Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to encrypt messages with NEAR's account system

Tags:

nearprotocol

NEAR's accounts can have many different key pairs accessing the same account. The keys can also change and rotate. Which means the default way of encrypting messages for specific user with their public key doesn't work.

What is the best pattern to encrypt a message for specific user?

like image 929
ilblackdragon Avatar asked Jun 09 '20 20:06

ilblackdragon


People also ask

How do I encrypt messages?

Encrypt a single message In message that you are composing, click File > Properties. Click Security Settings, and then select the Encrypt message contents and attachments check box. Compose your message, and then click Send.


Video Answer


2 Answers

NEAR account keys are not intended for this use case.

Generally, having end-to-end encrypted messages (in the most specific sense a end-to-end encrypted chat, but in general any application that exchanges encrypted messages) with each participant having multiple devices is not trivial. E.g. it is for a reason that in Telegram private chats are attached to a device, and are not available on the other device.

The reason is that generally that would require sharing private keys between devices, doing which securely is a challenge on its own.

Here's a verbatim proposal of how to build a end-to-end encrypted chat with

a) Each participant potentially participating from multiple devices
b) Messages not only shared with someone directly, but also with "groups" of participants.

The design goal was that sending a message should be constant time (not depend on the number of devices target users use / number of people in the group it is sent to), while some operations can be linear.

There's a plan to add is as a library to NEAR, but work on it is not started and is not scheduled to start yet.

Proposal

Problem statement: We want group chats into which new members can be added, and old members can be removed; New members being able to see messages posted before they joined is a wish-list feature; Old members should not be able to see new messages after they left; Users should be able to use multiple devices and see all the messages in all their group chats from all the devices; Each message must be stored once (not once per participant of the group);

The proposed solution:

  1. There are three kinds of key pairs in the system: account key (not to be confused with NEAR account keys), device key and a message key.

  2. Each account has exactly one account key. It is generated the first time an account uses the service.

    account_keys: PersistentMap

  3. Each device has its own device key generated the first time the chat is accessed from the device (or each time the local storage is erased)

    class DeviceKey { name: string, device_public_key: PublicKey, encrypted_account_secret_key: EncryptedSecretKey?, }

    device_keys[account]: PersistentVector

    The persistent vector is per account, and each such persistent vector contains device public key (device private key only exists on the device), and the account secret key encrypted with such a public key, or null if the secret key was not encrypted with such public key yet.

    There are three methods to manage the device keys:

    addDeviceKey(device_public_key: PublicKey, name: string): void

Adds the new key, and associates null as the corresponding encrypted account secret key.

    removeDeviceKey(device_public_key: PublicKey): void

Removes the device key

authorizeDeviceKey(device_public_key: PublicKey, encrypted_account_secret_key: EncryptedSecretKey): void

Sets the encrypted account secret key for the device key.

The flow for the user thus will be:

a) Launch the chat from a new device, give it a name.
b) Open chat from some other device that already has the encrypted account key, go to Devices setting and authorize the new device.

  1. All the messages keys are stored in a large persistent vector:
    all_message_public_keys: PersistentVector<PublicKey>

And in all other places are referenced using u32 indexes into the vector. Each user knows some message secret keys:

encrypted_message_secret_keys[account]: PersistentMap<u32, EncryptedSecretKey>
encrypted_mesasge_secret_keys_indexes[account]: PersistentVector<u32>

The map and the vector are per account. The vector is only needed so that when the user changes their account key, we know all the message keys that we need to reencrypt. The keys are encrypted with the account key.

Each channel has exactly one message key associated with it at each moment, though the keys might change throughout the lifetime of the channel.

channel_public_keys: PersistentMap<u32, u32>

Where the key is the channel id and the value is the message key ID.

  1. Each message has a u32 field that indicates what message key was used to encrypt it. If it is not encrypted, the value is u32::max. Whenever a message is sent to a channel, it is encrypted with the current channel message key.

  2. The flow is then the following:

    When a channel is created with the initial set of participants, the creator of the channel creates the message key pair, encrypts the secret key with the account keys of each participant, and calls to

    createChannel(channel_name: string,
                  accounts: AccountId[],
                  message_public_key: PublicKey,
                  encrypted_message_secret_keys: EncryptedSecretKey[])

That registers the message key, adds the encrypted secret keys to the corresponding collections, and creates the channel.

If a new user needs to be added, the addUserToChannel(account: AccountId, encrypted_message_secret_key) adds the user to the list of channel users, and grants him access to the latest message access key.

If a user needs to be deleted, the deleteUserFromChallen(account: AccountId) removes the user. In such a case, or if otherwise the channel participant believe their message key was compromised, they call to

updateChannelMessageKey(message_public_key: PublicKey, 
                        encrypted_message_secret_keys: EncryptedSecretKey[])

Note that since each message has the associated key with it, and the channel participants didn’t lose access to the old message keys, the existing channel participants will be able to read all the history, without having to re-encrypt it. However, new users who join the channel will only see the messages since the last time the key was updated.

  1. When a user needs to update the account key, they need to:
    a) Encrypt it with all the device keys;
    b) Encrypt all their message keys with the new account key;
    c) Supply (a) and (b) into a contract method that will update the corresponding collections.

    After such a procedure the user will have access to all their old messages from all the devices with the new account key.

like image 54
Ishamael Avatar answered Sep 30 '22 04:09

Ishamael


Indeed there is no default way to do this. The easiest way is if specific application, like chat needs to encrypt messages is to require user to "Login with NEAR" - which will create a new key pair on the application's side and authorize this public key in the user's account for the app.

Now any other user can scan recipient's account and find key that authorized for this app and use it for encryption. This will behave similarly to Telegram secret chats, where they only can be decrypted on a single device that started the chat.

To make this work across devices (domains, applications), one can create a key pair, where public key is known and attached to given account. Private key is also stored on chain but encrypted with all the access keys from different devices. When new device / app is added, an existing app needs to authorize this and this will allow to decrypt the private key within this session and re-encrypt with access key of this session.

like image 41
ilblackdragon Avatar answered Oct 01 '22 04:10

ilblackdragon