8.7 KiB
Encrypt Messages Using Waku Message Version 1
The Waku Message format provides an easy way to encrypt messages using symmetric or asymmetric encryption. The encryption comes with several handy design requirements: confidentiality, authenticity and integrity.
You can find more details about Waku Message Payload Encryption in 26/WAKU-PAYLOAD.
What data is encrypted
With Waku Message Version 1, the entire payload is encrypted.
Which means that the only discriminating data available in clear text is the content topic and timestamp (if present). Hence, if Alice expects to receive messages under a given content topic, she needs to try to decrypt all messages received on said content topic.
This needs to be kept in mind for scalability and forward secrecy concerns:
- If there is high traffic on a given content topic then all clients need to process and attempt decryption of all messages with said content topic;
- If a content topic is only used by a given (group of) user(s) then it is possible to deduce some information about said user(s) communications such as sent time and frequency of messages.
Key management
By using Waku Message Version 1, you will need to provide a way to your users to generate and store keys in a secure manner. Storing, backing up and recovering key is out of the scope of this guide.
If key recovery is important for your dApp, then check out SubtleCrypto.wrapKey() which can be used to securely store or export private keys.
An example to save and load a key pair in local storage, protected with a password, can be found in Eth-PM.
Which encryption method should I use?
Whether you should use symmetric or asymmetric encryption depends on your use case.
Symmetric encryption is done using a single key to encrypt and decrypt.
Which means that if Alice knows the symmetric key K
and uses it to encrypt a message,
she can also use K
to decrypt any message encrypted with K
,
even if she is not the sender.
Group chats is a possible use case for symmetric encryption:
All participants can use an out-of-band method to agree on a K
.
Participants can then use K
to encrypt and decrypt messages within the group chat.
Participants MUST keep K
secret to ensure that no external party can decrypt the group chat messages.
Asymmetric encryption is done using a key pair: the public key is used to encrypt messages, the matching private key is used to decrypt messages.
For Alice to encrypt a message for Bob, she needs to know Bob's Public Key K
.
Bob can then use his private key k
to decrypt the message.
As long as Bob keep his private key k
secret, then he, and only he, can decrypt messages encrypted with K
.
Private 1:1 messaging is a possible use case for asymmetric encryption: When Alice sends an encrypted message for Bob, only Bob can decrypt it.
Symmetric Encryption
Generate Key
To use symmetric encryption, you first need to generate a key.
Use generateSymmetricKey
for secure key generation:
import { generateSymmetricKey } from 'js-waku';
const symmetricKey = generateSymmetricKey();
Encrypt Message
To encrypt a message with the previously generated key,
pass the key in the symKey
property to WakuMessage.fromBytes
.
Same as Waku Messages version 0 (unencrypted),
payload
is your message payload and contentTopic
is the content topic for your dApp.
See Receive and Send Messages Using Waku Relay for details.
import { WakuMessage } from 'js-waku';
const message = await WakuMessage.fromBytes(payload, contentTopic, {
symKey: symmetricKey
});
The Waku Message can then be sent to the Waku network using Waku Relay or Waku Light Push:
await waku.lightPush.push(message);
Decrypt Messages
To decrypt messages, whether they are received over Waku Relay or using Waku Store, add the symmetric key as a decryption key to your Waku instance.
waku.addDecryptionKey(symmetricKey);
Alternatively, you can pass the key when creating the instance:
import { Waku } from 'js-waku';
const waku = Waku.create({ decryptionKeys: [symmetricKey] });
It will attempt to decrypt any message it receives using the key, for both symmetric and asymmetric encryption.
You can call addDecryptionKey
several times if you are using multiple keys,
symmetric key and asymmetric private keys can be used together.
Messages that are not successfully decrypted are dropped.
Asymmetric Encryption
Generate Key Pair
To use asymmetric encryption, you first need to generate a private key and calculate the corresponding public key.
Use generatePrivateKey
for secure key generation:
import { generatePrivateKey, getPublicKey } from 'js-waku';
const privateKey = generatePrivateKey();
const publicKey = getPublicKey(privateKey);
The private key must be securely stored and remain private. If leaked then other parties may be able to decrypt the user's messages.
The public key is unique for a given private key and can always be recovered given the private key, hence it is not needed to save it as long as as the private key can be recovered.
Encrypt Message
The public key is used to encrypt messages;
to do so, pass it in the encPublicKey
property to WakuMessage.fromBytes
.
Same as clear Waku Messages,
payload
is your message payload and contentTopic
is the content topic for your dApp.
See Receive and Send Messages Using Waku Relay for details.
import { WakuMessage } from 'js-waku';
const message = await WakuMessage.fromBytes(payload, contentTopic, {
encPublicKey: publicKey
});
The Waku Message can then be sent to the Waku network using Waku Relay or Waku Light Push:
await waku.lightPush.push(message);
Decrypt Messages
The private key is needed to decrypt messages.
To decrypt messages, whether they are received over Waku Relay or using Waku Store, add the private key as a decryption key to your Waku instance.
waku.addDecryptionKey(privateKey);
Alternatively, you can pass the key when creating the instance:
import { Waku } from 'js-waku';
const waku = Waku.create({ decryptionKeys: [privateKey] });
It will attempt to decrypt any message it receives using the key, for both symmetric and asymmetric encryption.
You can call addDecryptionKey
several times if you are using multiple keys,
symmetric key and asymmetric private keys can be used together.
Messages that are not successfully decrypted are dropped.
Handling WakuMessage
instances
When creating a Waku Message using WakuMessage.fromBytes
with an encryption key (symmetric or asymmetric),
the payload gets encrypted.
Which means that wakuMessage.payload
returns an encrypted payload:
import { WakuMessage } from 'js-waku';
const message = await WakuMessage.fromBytes(payload, contentTopic, {
encPublicKey: publicKey
});
console.log(message.payload); // This is encrypted
However, WakuMessage
instances returned by WakuRelay
or WakuStore
are always decrypted.
WakuRelay
and WakuStore
never return messages that are encrypted.
If a message was not successfully decrypted, then it will be dropped from the results.
Which means that WakuMessage
instances returned by WakuRelay
and WakuStore
always have a clear payload (in regard to Waku Message version 1):
import { Waku } from 'js-waku';
const waku = Waku.create({ decryptionKeys: [privateKey] });
const messages = await waku.store.queryHistory([contentTopic]);
if (messages && messages[0]) {
console.log(messages[0].payload); // This payload is decrypted
}
waku.relay.addObserver((message) => {
console.log(message.payload); // This payload is decrypted
}, [contentTopic]);
Code Example
The Eth-PM Web App example demonstrates both the use of symmetric and asymmetric encryption.
Asymmetric encryption is used for private messages so that only the intended recipient can read said messages.
Symmetric encryption is used for the public key messages. In this instance, the same key is used for all users: the Keccak-256 hash of the content topic (which results in 32 bytes array). While this does not add functional value, it does demonstrate the usage of symmetric encryption in a web app.
A live version of Eth-PM can be found at https://status-im.github.io/js-waku/eth-pm/.
The specifications of the protocol it implements can be found at 20/TOY-ETH-PM.