Encryption
Overview
Safe messaging is one of key features. It is achieved by a combination symmetric and asymmetric encryption.
Asymmetric encryption uses Elliptic Curve. It provides a way to compute same secret for two user’s communication. Also it provides a way to generate a sign of a message to confirm authencity of an author.
Symmetric encryption uses Blowfish. It provides a way to encrypt data with a secret, so that it can be decrypted with the same secret only. It can enctrypt messages of any length. Blowfish may become vulnerable ecrypting more than 4Gb of data. We mitigate this providing secret change after each Gb of data.
Further on we use term encryption/decryption
for asymmetric encryption, and cipher/decipher
for symmetric encryption.
Message transfer example
Alice wants to send a message to Bob. To do so Alice needs to know Bob’s public key.
- Alice calculates secret from Alice private key and Bob public key
- Alice ciphers the message with secret
- Alice signs the message with her private key
- Alice sends to Bob ciphered message and signature
graph LR;
secret[Conversation Secret]
b_pb{{Bob public key}} --> secret
a_pr{{Alice private key}} --> secret
msg[/Message/]
ciphered[[Ciphered Message]]
sign[[Message Sign]]
secret --> ciphered
msg ---> ciphered
msg ---> sign
a_pr ---> sign
bob(((Bob)))
ciphered ---> bob
sign ---> bob
When Bob receives a message heneeds to know Alice public key to read and authenicate the message.
- Bob calculates secret from Bob private key and Alice public key
- Bob deciphers Ciphered Message with secret
- Bob verifies Alice signature with the message and Alice public key
graph LR;
ciphered[[Ciphered Message]]
sign[[Message Sign]]
secret[Conversation Secret]
b_pr{{Bob private key}}
a_pb{{Alice public key}}
msg[/Message/]
ver(Confirmed Signature)
b_pr --> secret
a_pb --> secret
secret --> msg
ciphered ----> msg
msg --> ver
sign -----> ver
a_pb --> ver
The flow
Each user is identified by keypair generated on first visit. Public key and user name gets stored in DB. Private key is stored on user device. We never store user private keys in DB.
Chats
We use users public keys to make chats(dialogs) between users. Anyone can chat to user that already visited the system (and got his public key stored in DB).
Messages exchange happpens the same way as showed in diagrams above. Sharing the same secret makes it possible to read own messages and write messages with it. Signing each message makes possible to authenicate the author.
DB stores public keys of both sides upon chat initiation. Messages store ciphered message, its signature, authors timestamp, and message index.
flowchart LR
a(Alice)
b(Bob)
a --> b
b --> a
Rooms
Each room has its own keypair. It is given to every member of a room. This way many users can write to room (using their key and room key) and read messages of other users (using room key).
Room private keys stored on user devices only. DB stores public key and room name. Also, room keeps a list of request in it. Room messages store ciphered message, its signature, author’s timestamp, message index and author’s public key.
flowchart TD
a(Alice)
b(Bob)
c(Charlie)
r((room))
a --> r
b --> r
c --> r
Messaging security
All long messages (more than 150 symbols) and files are stored separatly from chat or room messages. Short messages ciphered with conversation secret (computed from private and public key) and stored inside a message.
This keeps ciphered conversation under Blowfish 4Gb vulnerability limit.
Memo
Long text (memo) gets ciphered with random Memo secret
. Resulting Ciphered memo
gets stored in the DB under Memo key
.
Memo secret
and Memo key
forms payload
. Which is ciphered with Conversation secret
and stored as part of a message in the DB.
flowchart LR;
m[/memo/]
ms[Memo secret]
mk[Memo key]
p(payload)
cm[[Ciphered memo]]
s((fa:fa-key))
mng((fa:fa-key))
cs[Conversation secret]
db[(DB)]
m --> s --> cm -...-> db
ms --> s
ms --> p
mk --> p
cs --> mng
p --> mng
mng -. part of a message .-> db
Deciphering message we are getting Memo key
and Memo secret
. Memo key
allows us to find Ciphered memo
in the DB. Decipering it with Memo secret
we are recovering original memo
Files
Files represent image, audio, video and file content. They are stored in 10Mb chunks
-
File key
generated as a hash of a file metadata, user uploading a file and destination chat or room. -
File secret
is generated.Additional secrets
are generated to have different secret for every 1000Mb of data. I.e.File secret
ciphers first 1000Mb of data, first ofAdditional secrets
ciphers second 1000Mb of data, and so on. -
Additional secrets
cipered withFile secret
and stored in the DB underFile key
-
Every file chunk ciphered with
File secret
or one ofAdditional secrets
-
Upon upload completion file metadata is ciphered with
File secret
and stored in the DB -
Payload is formed from
File key
andFile secret
-
Payload ciphered with
Conversation secret
and stored in the DB as a part of a message.
graph LR;
db[(DB)]
fk[File key]
fs[File secret]
as[Additional secrets]
as_s((fa:fa-key))
c[/Chunks/]
p(Payload)
cs[Conversation secret]
c_s{choose secret}
p_s((fa:fa-plus))
m_s((fa:fa-key))
fs --> as_s -.-> db
as ---> as_s
c --> c_s -.-> db
as --> c_s
fs --> c_s
fs --> p_s --> p
fk --> p_s
p --> m_s -.-> db
cs --> m_s
Bird’s-eye view
Private key usage scheme
Private keys are stored on user browser only. For operational needs the keys are copied in memory of a device.
The system uses private keys for
- Messaging - ecrypting and decrypting messages
- Rooms approval - encrypting room key for a new member of a room
- Upload reviving - encrypting File secret while file is being uploaded to make possible upload continuation after user disconnect.
On new room approval for the user new room key is sent to a browser.
graph TD;
uk --> ukc;
rk <--> rkc
subgraph Browser
direction LR
uk(User key)
rk(Room keys)
end
subgraph RPi
ukc(User key)
rkc(Room keys)
m{{Messaging}}
upl{{Revived upload}}
r{{Rooms approval}}
ukc -.-> m
rkc -.-> m
rkc -.-> r -.new room.-> rkc
ukc -.-> upl
end
Client side private keys
We may move private key usage on client side for stronger security. This way private keys never leave client.
The server will send encrypted messages to the client. Client will decrypt them (having private key). To send new message client will need to encrypt it first, then send to server side.
Room approval will need client to encode room private key for a particular user.
Upload secret may be stored on client side while upload is in progress.
Messages export as zip-file has messages decrypted. To do so on server-side we wouuld need to ask client to provide decrypted messages. Or consiider moving this functionality on client side.
graph TD;
subgraph Browser
direction TB;
uk(User key)
rk(Room keys)
m{{Messaging}}
upl{{Revived upload}}
uk -.-> m
uk -.-> upl
rk -.-> m
end
subgraph RPi
direction TB;
r{{Rooms approval}}
db[(DB)]
end
m <--encrypted messasages--> db
rk <--encrypted room approval----> r
Shamir’s Secret Sharing
graph TD
direction LR
scrt --> shrs
amnt --> shrs
thrsld --> shrs
shrs -.share part 1.-> shrprt1
shrs -.share part 2.-> shrprt2
shrs -.share part 3.-> shrprt3
shrs -.share part 4.-> shrprt4
subgraph User Secret Owner
scrt[Secret Key]
amnt[Amount : 4]
thrsld[Threshold : 3]
end
subgraph SecretSharesGenerator
shrs{Generate Shares}
end
subgraph User 1
shrprt1[Share Part]
end
subgraph User 2
shrprt2[Share Part]
end
subgraph User 3
shrprt3[Share Part]
end
subgraph User 4
shrprt4[Share Part]
end
If we need to protect our secret, we could use secret sharing.
This is how it works.
We take 3 parameters: our secret, a total amount of shares we need to generate and a threshold, that indicates how many shares would be enough to recover our secret down the line.
Imagine that our secret is represented by a number. This number could be placed on the Oy axis. If we wanted to hide that point, we could represent it via a number of other points that would form a line, that intercepts Oy axis. The complexity of that line determines how easy it would be to calculate, which function represents that line, and by proxy, our secret. For instance, if our line was straight, 2 points would be enough to know where our secret lies on Oy axis.
The threshold shouldn’t be bigger than a total amount of shares, you can’t really recover your secret if you don’t have enough points to form a needed line.
graph TD
shrprt1 --> r
shrprt2 --> r
shrprt3 --> r
r -. recovered secret .-> rec
subgraph Threshold
subgraph User 1
shrprt1(Share Part)
end
subgraph User 2
shrprt2(Share Part)
end
subgraph User 3
shrprt3(Share Part)
end
end
subgraph User 4
shrprt4(Share Part)
end
subgraph Key Recovery
r{Recover Secret}
end
subgraph User Key Owner
rec(Recovered Secret Key)
end
And that’s basically it, the output of the share generation should be a list of shares, which we can handle out each to different users.
Once we need to recover our secret, we can ask these users what share they have, and after getting enough of them, our secret should be safely recovered.
Developer guidelines
No secret, no private key, no message, no file get written unprotected into DB.
Every bit of client content should be protected by private key (user or room), eventualy.
Tech details
Hash: SHA3-256
Cipher: Blowfish CFB
Encryption: ECC secp256k1