Awesome
SecureNotes ZKA app
What is Zero Knowledge Architectures
ZKA is a design principle.
In simple words, everything you do on a Zero Knowledge system is encrypted before it is sent to the server and the key to the encryption is also never revealed to the vendor.
Zero knowledge algorithms and protocols ensure that no keys, passwords, files, or any other sensitive material ever gets transferred in an unencrypted or reversible form. There is no point in time when encryption keys or unencrypted files are visible to the servers or service administrators.
Principles
- End-to-end encrypted clients.
- All operations are on encrypted data.
- Server knows nothing about the nature of data.
Where it's used?
- Messaging (like e2ee chats)
- Authentication (Zero Knowledge Proof Protocol enables two parties to compare a secret without exposing it, efficiently avoiding leakage of secrets during transmission)
- Data sharing (medical data sharing)
Read more in Medium post.
Making Secure Notes app
How app works?
This is a simple notes application, that uses Firebase as backend. Users should sign up / login on start, then they can post notes. Own notes are visible on My Posts
page, notes from all users – on All Posts
page.
This app is remake of Firebase Database example app.
Firebase setup
Before continue:
- Change bundle name.
- Follow Firebase setup tutorial.
- Register app in the Firebase app console.
- Receive
GoogleService-Info.plist
and add it to the project. - In your Firebase console, app settings, enable Authentication via email.
- Enable relatime database in a test mode.
Encrypting own notes
Of course, notes might be rather sensitive, so users asked us to encrypt their notes, making them visible only to authors. We selected symmetric encryption for this purpose: use one key to encrypt and decrypt note. Each user has its own unique key.
Encrypting post:
- Generate user secret key (SK). Keep it in a secure place! Use KDF to make secret key stronger.
- Encrypt post body using SK and AES.
- Encode post body to base64 with percent encoding (for safe transfer via network).
- Save post to the backend.
Decrypting post:
- Retrieve user secret key (SK).
- Decode post body from base64 and remove percent encoding.
- Decrypt post body using SK and AES.
- Show decrypted post.
Implementation details
In our example we will use Themis Secure Cell in Seal mode for post encryption. It has pre-built KDF, and uses AES256 GCM. Data will be encrypted and appended by auth tag, saving it from tampering.
Create SecureCell:
// 1. create encryptor SecureCell with own secret key
guard let cellSeal = TSCellSeal(key: secretKey.data) else {
print("Failed to encrypt post: error occurred while initializing object cellSeal")
throw EncryptionError.cantCreateSecureCell
}
Encrypting post body:
// 2. encrypt data
let encryptedMessage: Data
do {
encryptedMessage = try cellSeal.wrap(postBody.data(using: .utf8)!,
context: nil)
} catch let error as NSError {
print("Failed to encrypt post: error occurred while encrypting body \(error)")
throw EncryptionError.cantEncryptPostBody
}
return EncryptedData(data: encryptedMessage)
Decrypting encrypted post body:
// 2. decrypt encryptedPost
var decryptedMessage: Data = Data()
do {
decryptedMessage = try cellSeal.unwrapData(encryptedPost.data,
context: nil)
} catch let error as NSError {
print("Failed to decrypt post: error occurred while decrypting: \(error)")
throw EncryptionError.cantDecryptPostBody
}
Decode decrypted post:
// 3. encode decrypted post from Data to String
guard let decryptedBody = String(data: decryptedMessage, encoding: .utf8) else {
print("Failed to decrypt post: error occurred while encoding decrypted post body")
throw EncryptionError.cantEncodeDecryptedPostBody
}
return decryptedBody
See example in Themis repo.
App code
Check EncryptionEngine
and all its extensions.
Fill empty methods in EncryptionEngine+OwnPost
, EncryptionEngine+DecryptOtherPost
(in without-encryption
branch).
Screenshots
For demonstration purposes I decrypt posts only on PostDetails
page, leaving them encrypted in the list:
<img src="pics/posts-encrypted.png" width=40%> <img src="pics/decrypt-own-post.png" width=40%>
<img src="pics/fb-db.png" width=80%>Sharing encrypted posts between users
Suddenly our users asked about sharing feature: they want to see notes of their friends. But each note is encrypted, so we should share users' secret keys. But these keys are very sensitive, we can't sharing them plaintext. So we use asymmetric encryption to wrap SK using own private key and friend's public key. Then share wrapped key with a friend.
Technically saying:
User Alice shares her encrypted post and encrypted secret key for user Bob:
-
Has own secret key
SK-a
. -
Encrypts own posts with
SK-a
:EncrPost-a = SecureCell(Post-a, SK-a)
-
Shares her public key with Bob (
Pub-a
). -
Encrypts
SK-a
for her friend BobSharedKey-(a->b)
using own private key (Priv-a
) and Bob's public key (Pub-b
):SharedKey-(a->b) = SecureMessage(SK-a, Priv-a, Pub-b)
User Bob wants to read message from User Alice:
-
Shares his public key with Alice (
Pubk-b
). -
Decrypts Alice secret key using own private key (
Priv-b
) and her public key (Pub-a
):SK-a = SecureMessage(SharedKey-(a->b), Priv-b, Pub-a)
-
Decrypts Alice post using her secret key:
Post-a = SecureCell(EncrPost-a, SK-a)
And vice versa :)
Here is the scheme:
<img src="pics/scheme-alice-bob-db.png" width=70%>Where to find public keys?
Usually public keys are stored in PKI (public key infrastructure), where they are saved from tampering. User Alice should trust PKI service to be sure that Pub-b
really belongs to Bob. There are different ways how to setup PKI, for example using mediator service like keybase.io.
In our example we will save public key to the same backend (Note: don't do like this in production).
Implementation details
In our example we will use Themis Secure Message for asymmetric keys encryption. Each key will be encrypted and signed by user using EC keypair.
Encrypting own SK:
- Generate own Keypair. Prepare own private key as Data. Prepare own secret key as Data.
- Get other user public key as String, decode it from base64 and remove percent escaping.
- Create SecureMessage container with own private key and other user public key.
- Encrypt own secret key using Secure Message for other user.
- Encode SK to String (add base64 and percent encoding).
- Share to the backend
Generate keypair:
func generateMyKeyPair() throws -> KeyPair {
guard let keyGeneratorEC: TSKeyGen = TSKeyGen(algorithm: .EC) else {
print("Error occurred while initializing object keyGeneratorEC")
throw EncryptionError.cantCreateKeyGenerator
}
let privKey = Key(data: keyGeneratorEC.privateKey as Data)
let pubKey = Key(data: keyGeneratorEC.publicKey as Data)
return KeyPair(privateKey: privKey, publicKey: pubKey)
}
Create SecureMessage container with own private key and other user public key:
// 2. create Asym encryptor using own private key and other user' public key
guard let encrypter = TSMessage.init(inEncryptModeWithPrivateKey: myPrivateKey.data,
peerPublicKey: userPublicKey.data) else {
print("Error occurred while creating TSMessage Encryptor")
throw EncryptionError.cantCreateSecureMessage
}
Encrypt own secret key for other user:
// 3. encrypt own secret key for another user
do {
return EncryptedData(data: try encrypter.wrap(mySecretKey().data))
} catch let error as NSError {
print("Failed to encrypt own SK: error occurred while encrypting: \(error)")
throw EncryptionError.cantEncryptOwnSecretKey
}
Decrypting other user post:
- Retrieve own Keypair. Prepare own private key as Data.
- Prepare other user encrypted SK as String
- Create SecureMessage container with own private key and other user public key.
- Decode encryped SK to Data (remove percent encoding, create Data object from base64-encoded string).
- Decrypt other user SK using Secure Message.
- Encode SK to String (user base64-encoding and percent encoding).
- Decrypt other user post using his secret key.
- Show decrypted post.
Create SecureMessage container with own private key and other user public key:
// 2. create Asym decrypter using own private key and other user' public key
guard let decrypter = TSMessage.init(inEncryptModeWithPrivateKey: myPrivateKey.data,
peerPublicKey: userPublicKey.data)
else {
print("Error occurred while creating TSMessage Decryptor")
throw EncryptionError.cantCreateSecureMessage
}
Decrypting other user SK:
// 3. decrypt own secret key for another user
var decryptedSecretKeyData: Data = Data()
do {
decryptedSecretKeyData = try decrypter.unwrapData(encryptedSecretKey.data)
} catch let error as NSError {
print("Failed to decrypt somebody's SK: error occurred while decrypting: \(error)")
throw EncryptionError.cantEncryptOwnSecretKey
}
Decode decrypted SK:
// 4. encode decrypted
guard let decryptedSKString = String(data: decryptedSecretKeyData, encoding: .utf8) else {
print("Failed to decrypt somebody's SK: error occurred while decoding decrypted SK")
throw EncryptionError.cantEncodeDecryptedPostBody
}
return decryptedSKString
See example in Themis repo.
App code
Check EncryptionEngine
and all its extensions. Fill empty methods in EncryptionEngine+Keypair
, EncryptionEngine+EncryptSK
, EncryptionEngine+DecryptSK
.
Screenshots
You need to run two applications (UserAlice and UserBob).
- UserAlice posts encrypted post.
- Now we want UserBob to be able to decrypt it, so UserAlice should share own encrypted SK to user Bob.
- UserBob opens
PublicKeys
list and posts own public key. - UserAlice opens
PublicKeys
and copies UserBob' public key (tap on the cell). - UserAlice opens
SharedKeys
, taps Add button. Inputs UserBob name into text field and pasts UserBob public key into textview. On pressing save UserAlice encrypts own SK using UserBob public key and own private key, and posts it to the backend. - UserBob opens
SharedKeys
, sees sharedKey from UserAlice, and saves this key locally (tap on the cell). Now he needs her public key to be able to decrypt sharedKey. - UserAlice opens
PublicKeys
list and posts own public key. - UserBob opens
PublicKeys
list, finds public key from UserAlice, saves it locally (tap on the cell). - UserBob opens encrypted post by Alice and decrypts it.
If Alice wants to read posts by Bob, Bob needs to share his SK with her.
For demonstration purposes I decrypt posts only on PostDetails
page, leaving them encrypted in the list:
<img src="pics/public-keys.png" width=33%> <img src="pics/copy-and-save-public-key.png" width=33%> <img src="pics/shareSK-to-userb.png" width=33%>
<img src="pics/posts-encrypted.png" width=40%> <img src="pics/decrypted-post-by-user-b.png" width=40%>
You can see video of app working in pics/ZKA-app-working.
Security warning
This example has many security flaws, especially in storing secret and public keys. Please don't use it as production-ready system. It's just an illustration of ZKA approach.
Of course, Firebase configuration should be changed: use private database, do not publish credentials (API keys).
Next steps
-
Make encryption scheme better:
- Currently each user post in encrypted by same Secret Key. Generate different SK for each post (store keys as structures
(post_id, SKi)
). - Share encrypted SKi per each post per user (update sharing algorithm to use
SKi
).
- Currently each user post in encrypted by same Secret Key. Generate different SK for each post (store keys as structures
-
Storing keys:
- Save keys (secret keys, user keypair, decrypted other user keys) in a Keychain.
- Make storing better: store keys encrypted with MasterKey (that is derived from user password after login). Minimize time of storing keys in plaintext in memory.
- Build proper PKI (authenticate users before storing their public keys).
- Remove encrypted keys from list after decrypting/using.
-
Login:
- Hash user password during sign up process.
- Add login via TouchID / FaceID on each app opening.
-
Put overlay view instead of system screenshot during app switching.
- Add view on
applicationDidEnterBackground
. - Remove view on
applicationDidBecomeActive
.
- Add view on
-
Networks (might be complicated to do because of Firebase library):
-
Add SSL pinning (get Firebase certificate, pin certificate, check on connection start) https://infinum.co/the-capsized-eight/ssl-pinning-revisited
-
Add transport encryption over SSL
-
-
Code obfuscation:
-
Monitor 3rd party dependencies for vulnerabilities and critical bugs:
Stay tuned
Wanna help with data security and encryption? Ping @cossacklabs, this is what we do for living :)
More my talks to read & watch in my-talks repo.
Thanks
Thanks @AlexeyDemedetskiy for useful suggestions and PR :)