Stingle Photos is a secure, zero-knowledge photo/video encryption and backup/sync application. It aims to provide maximum security and privacy for end users to confidently store, backup and sync their photos and videos, without ever fearing of losing them or being compromised and without sacrificing convenience at the same time.
Currently, there are many mainstream photo backup and sync solutions like Google Photos, Apple iCloud, Amazon Photos, etc., but they all have one major problem. All of them are not end to end encrypted services. It means that they (provider) can absolutely see your content and do with it whatever they want. Furthermore, in the last few years they all started using machine learning to analyze your photos and videos, identify what’s in them, who is in them, what are you doing, where you took these photos and videos and much more. They aggregate all this data and use it for their business needs, cooperation with government agencies and other purposes. So in fact you don’t own your own data anymore when using these mainstream services and you have no control over it.
Stingle Photos aims to solve that problem. All files are encrypted locally with the key that only user has and then only encrypted blobs of data are sent to the cloud.
Nobody, neither we as a provider, nor anybody else can see your data, it’s completely secure and it belongs only to you.
One can argue that person’s photos and videos are one of the most private and sensitive data that he/she has and all major services are not giving sufficient privacy guarantees if any.
From the first day system was designed in such a way that in the unlikely event that whole cloud infrastructure is hacked and hackers steal all information, including encrypted files, source codes and databases they should not get anything usable out of that, just nothing!
And finally we have created Stingle Photos initially for ourselves, because at the moment there is no such app on the market that is 100% secure and sufficiently convenient to use it in everyday life. We are security paranoiacs and we don’t trust any cloud provider, so Stingle Photos is designed in such a way that we’re not relying on cloud providers privacy practices at all.
We believe that great security comes from good, solid security architecture and not from obscurity. That’s why we are as open and transparent as we can regarding our security design, technical details and algorithms used.
So let’s dive into more technical details, so you can better understand how Stingle Photos work and be more confident using it for your private, confidential and casual photos and videos.
Stingle Photos uses Libsodium at it’s core.
For symmetric cipher that encrypts file data we use XChaCha20 with Poly1305 authentication. ChaCha20 is a very secure and robust algorithm that gives the same security as AES256, but works considerably faster. AES256 is faster only on CPUs which has AES specific hardware instructions for accelerating AES algorithm. However that’s not the case for mobile devices.
For public key crypto we use ECC crypto. Specifically we use Daniel J. Bernstein’s X25519 curve.
For key derivation we use Argon2 with different difficulty settings for different purposes. We will discuss that in more detail below.
For signup Stingle Photos requires only the bare minimum: email and password. We try to keep it as anonymous as possible. We really don’t need your email either, we just thought that average user will likely to forget his/her username, that’s why we decided to go with the email. Also it will be useful for receiving email notifications when someone shares photos/videos with you.
During signup app creates pair of ECC X25519 keys: private and public. Then it creates random 128 bit salt for password derivation. Then it derives 256 bit encryption key using salt and Argon2 function using ARGON2ID_OPSLIMIT_INTERACTIVE difficulty. When 256 encryption key is ready it encrypts private key with it using XSalsa20-Poly1305 construction and saves encrypted private key and public key in the apps private storage directory.
After that it repeats derivation of encryption key from the password using Argon2 with ARGON2ID_OPSLIMIT_MODERATE difficulty. After encrypting plain private key again with stronger encryption key, it uploads that alongside with public key and a salt to the cloud. User can prevent private key upload by selecting that option during sign up, but in that case user is becoming responsible for not losing it. Additionally if you don’t backup your private key, logging in from a new device will require you to provide your private key, which creates a little bit of hassle.
Public key is always sent to the cloud, so that others can share photos with you and for you to be able to backup only your private key.
Argon2 is doing both computational and memory hard operation. This is required to dramatically slow down brute force attacks. ARGON2ID_OPSLIMIT_INTERACTIVE for one password guessing try, requires 64 MiB of dedicated RAM. This mode is used for in device key storage and it’s pretty fast for everyday use. ARGON2ID_OPSLIMIT_MODERATE for one password guessing try, requires 256 MiB of dedicated RAM, and takes about 0.7 seconds on a 2.8 Ghz Core i7 CPU. This mode is used for backing up private key to the server. This ensures that even in the unlikely event that private keys are stolen from the cloud it will take nearly 1000 years to brute force 6 character password containing lowercase letters, uppercase letters and digits. And the beauty of Argon2 is that you can’t accelerate it by using FPGAs and ASICs, because for each try it requires lots of RAM.
Now about sending credentials to server so it can authenticate logins without knowing user’s password and/or keys. During the signup process, app generates another salt and runs the password through Argon2 function with ARGON2ID_OPSLIMIT_MODERATE difficulty. Result is sent to the server as the authentication token. Server upon receiving sign up request with aforementioned token, generates another salt for itself and runs them through SHA512 PBKDF2 function and then saves the result to DB.
After all of this is complete server issues device specific token for authentication with Stingle Photos API service, which app needs to send with every request from now on.
Just to recap. Here is how signup process looks in steps:
Login operation consists of 2 phases: pre-login and login itself. App issues request to the server for pre-login and sends only entered email address. Server looks up email address in the database and if found returns salt which is required for the app to generate login token. Then app generates login token using Argon2 with moderate difficulty and passing to it entered password and salt received from server. After that app issues second request to login sending email and generated login token. Server hashes login token using SHA512 PBKDF2 and Salt2 (previously saved during the signup process, see Sign Up section). Then it compares that with the value stored in the database and if they match then login is allowed and server issues device specific authentication token. Login response from the server will also include key bundle. Key bundle will contain encrypted private key if user have backed it up during the signup process. App receives key bundle, decrypts private key and reencrypts it with key derived from a password using interactive Argon2 and saves it to the app’s private storage for fast unlocking.
Plain private key is never stored anywhere on the device, it's only kept in memory during app use and it gets wiped when app locks. When user enters the app to view his/her photo and videos Stingle Photos asks for the password. After entering it, app runs Argon2 with interactive difficulty and derives decryption key. Then it reads encrypted key which is stored in apps private storage directory and decrypts it using derived key. After that decrypted private key is stored in memory and is available for file decryption.
When user enables fingerprint authentication, app generates 256 bit key and encrypts user’s password with that key using AES256 with PKCS7 padding. Then it stores that key into Android’s secure keystore which unlocks only by using fingerprint. After that app stores encrypted password in the shared preferences. Upon unlock app presents a dialog to touch the fingerprint sensor, which unlocks the keystore, which gives ability to decrypt stored encrypted password. After decrypting password process continues as if user entered password manually. Newer android devices have a separate hardware security module for keystore and thus are more secure.
Each file encrypted by Stingle Photos has a defined internal structure.
Let’s talk about each field in more detail.
"SP" - is just a letter SP. It’s there in the beginning of each file to quickly identify that we are dealing with our file or not. "File version" - 1 byte field for identifying file version in case in future versions of Stingle Photos file format will change. "FileID" - Unique 32 byte number identifying each file. It’s only purpose is to match encrypted files with their encrypted thumbnail versions both locally and on the cloud. "Encrypted header length" - 4 byte field for identifying how many bytes encrypted header is. Encrypted header - Encrypted header stores decryption key for data alongside with other metadata. It’s length is mentioned in the last field. "Encrypted header" - Encrypted header stores decryption key for data alongside with other metadata. It’s length is mentioned in the last field. "Encrypted data" - Encrypted file contents.
Using user’s private key app decrypts encrypted header and its contents are the following:
"Header version" - 1 byte field for identifying header version in case in future versions of Stingle Photos header format will change. "Chunk size" - 4 bytes field for identifying how big each encryption block should be. Used for XChaCha20-Poly1305 algorithm. "Data size" - Size of the original data before encryption. "Data encryption key" - 32 byte master symmetric key for decrypting data. "File type" - 1 byte field identifying file type (1-General file, 2-Photo, 3-video). "Filename size" - size in bytes of the upcoming filename field. "Filename" - Original filename of the encrypted file. "Video duration" - If file is a video this field keeps it’s duration in seconds, otherwise just 0.
Please note that all sensitive metadata is kept in the encrypted header. This supports the idea to make Stingle Photos as private and secure as possible, so even file type or it’s real size is unknown to the outside viewer.
When taking photo or video with camera or importing existing photos from gallery following happens.
This construction allows us to do several good things. First, the user is able to take photos and videos without unlocking the app, as user’s public key is always available on the device. Second this allows to decrypt each block of encrypted data independently, which allows seeking video (like jumping to the end without needing to decrypt the whole file, because videos can be huge in size).
To decrypt file app does the following:
Each file has its thumbnail. When encrypting file app generates thumbnail for it (if it’s photo then downscaled version of the photo, if it’s video the thumbnail is the first frame of the video). Thumbnails are encrypted exactly the same way as the original files. The only difference is that thumbnail always has the same File ID, File Type, Filename and Video Duration fields as the original file. Having the same File ID helps server to match files with their corresponding thumbnails. Having the same File Type, Filename and Video Duration helps gallery to show thumbnails with video icons and video duration quickly without needing to decrypt original files, which will degrade gallery performance.
When creating a new album app generates a unique X25519 key pair for the album. Private key is being encrypted with user's public key using crypto_box_seal, public key is kept open in the database. There is also album metadata, which for the moment contains only album name. Album metadata is being encrypted with album's public key. All files in the album are being encrypted with album's public key, instead of user's key. When user wants to read contents of the album, he decrypts album's private key with his own private key, then, having the album's private key he is able to decrypt album files. This construction allows to share albums without ever needing to touch files themselves.
Let's imagine Bob wants to share album "Party" to Alice. The process looks like this:
Note that Bob wasn't required to reupload files, or even do any operations with them. He just reencrypts the album key and it's done. That's why sharing works so fast.
When registration of new user is complete, server generates unique X25519 key pair for every user. Public portion of the key is sent to the user's device and public is kept on the server side.
When user's device makes a request to the API server (which is of course done using TLS), all request data is serialized and the result is encrypted with the server's public key and user's private key using crypto_box_easy authenticated encryption. Server receives the request, decrypts the message using it's private key and user's public key and then executes it. This is a added layer of security on top of standard TLS(https). Using this approach server can guarantee that user who had access to user's private key made the request, plus it's added layer of encryption in case TLS is compromised.
This is only valid for requests that are made while app is unlocked, so app has access to user's private key. For example sync is not encrypted this way, because it should work in background while app is locked. All other requests are encrypted.
We have done everything possible to know as little as we can about you. Information we have is the bare minimum that allows app to function and deliver it’s features. So here is what we can see from the server side.
We don’t keep any IP logs, don’t have any analytics at all. Stingle photos doesn’t include any third party libraries, don’t have any ads. It’s completely free and you only pay for extra cloud storage that you use.