DEV Community

loading...
Cover image for Encryption without passwords
Wault

Encryption without passwords

Gál Péter
Computer science student @ ELTE, Budapest
・6 min read

In modern encryption, you need a key/password/secret to be able to securely encrypt/decrypt data. This is the idea behind symmetric encryption. In this article, I'm going to drive you through how is the security provided without ever needing to remember a password.

How can we encrypt something in the first place?

There are lots of ways to encrypt something. When it comes to storing your information on a Safe, we are using an asymmetric encryption method, called AES-256 (Advanced Encryption Standard).

When we create a new safe, we generate a new secret key with them. This will be then used to encrypt all of our data in that particular safe. We are using a 128-character long hex string as our secret (512 bits), which is much more secure, than any password that we could remember.

Then this encryption key is stored in the device's secure storage.

Here is how this works out in code:

// Generating a new secret
const secret = generateSecret();

// Storing it in the device's secure storage
await SecureStorage.store(secret);

// This is the data, that we want to store
const user = {
    platform: "dev.to",
    email: "john@doe.com",
    password: "secret123",
};

// Then we encrypt the data
const encrypted = AES.encrypt(JSON.stringify(user), secret);

// Sending the user to the database
// The safe is an object, where we want to store the item inside
await API.item.create(safe, encrypted);

// And finally, on a remote device, when we want to access the data, we use the secret to decrypt
const decrypted = AES.decrypt(encrypted, secret);

// It would print the original user object ({ platform: "dev.to", username.... })
console.log(decrypted);
Enter fullscreen mode Exit fullscreen mode

Note: this is just a presentation of how things work... you should just consider it as a pseudo-code, instead of an actual working example

How can we transfer this key from one device to another safely?

This is a tricky one. If we would just send the encryption key through our database unencrypted, it would mean that there is no point of encryption as we would store the secret key next to the encrypted data. It would mean that you can just use that key to decrypt the data that is stored in the database.

Instead, we use an asymmetric encryption method, called RSA-2048. When you generate a new RSA key, then it is generating a keypair. A public one and a private one.

The public one can be only used to encrypt data. It can be safely shared on the network, as it provides no way for a potential attacker to retrieve data from the hash, as it is not eligible for that.

The private one will stay on the original device without ever sharing it with another device. Then when we get data from the server, we can use this key to decrypt the hash, that we received.

Let's see, how does it work out in practice:

  1. When a device is added to the network, we generate this key pair and send the public key to the database. It will be stored in the future.
  2. We create a new safe on one of our devices and generate a secret, that is stored locally.
  3. We query all of our devices from the database with their RSA public keys.
  4. Then we encrypt the secret, that we generated at the #2 step with this key.
  5. Then we add an entry called KeyExchange, which contains information about which device and safe is the key meant for and the encrypted secret.
  6. Finally when we refresh our data on another device, we query all of our key exchanges and decrypt them with our private RSA key.
  7. After the device received the encryption key, we can safely delete the key from our database as it will be no longer needed.

How can I log in from another device?

Compared to ordinary systems, when we log in, we also exchange a lot of data between the Authenticator and the Authenticated device.

Authenticator: The device, that is already logged in.
Authenticated: The device, that we want to log in, but is not yet authenticated.

  1. The Authenticated device generates an RSA key pair and sends a signal to the remote server to start the authentication process with the public key.
  2. The remote server then generate an authentication id (e.g.: ckqz9n52r000001la810jfjee) and a secret (e.g.: 11879182178653d376fc6b129d1d315b).
  3. Then the server stores the id with the bcrypt hashed version of the secret in the database and sends back the secret and the id to the Authenticated device.
  4. The Authenticated device, then generates a QR code, that stores only the ID, that it got from the server.
  5. Now on the Authenticator device, we scan this QR code. This will send a signal to the remote server about who scanned the QR code (first).
  6. On the Authenticated device, we will see the username of the user, who scanned this QR code.
  7. On the Authenticator device, the user presses a Verify button. This will send the encryption keys from that device to the Authenticated one (as described above) and this will also send a signal, to allow the remote server, to generate an access token and a refresh token for the user.
  8. While these things happen, the Authenticated device pings the server, if the state has changed. If the auth process went through successfully, then it will just download all data, that has been sent to this device.
  9. On the Authenticated device, we use the private RSA key, to decrypt the keys.
  10. Profit! We have successfully gained an access_token, a refresh_token, generated and exchanged the RSA keys, and also received all of the keys necessary, to decrypt the safes associated with this device.

On the Authenticated device:

// Generate a public and a private RSA key
const { publicKey, privateKey } = RSA.generate(2048);

// Send this publicKey to the server and receive the id and the secret
const { id, secret } = await API.authentication.start({
    publicKey,
});

// Store the private RSA key for future use
await SecureStorage.store("rsa_private_key", privateKey);

// Check the server periodically for response
setInterval(async () => {
    const response = await API.authentication.check({
        id,
        secret,
    });

    if(response.state === "not_yet_scanned") {
        // If noone scanned the code yet do nothing
    } else if(response.state === "scanned_but_not_verified") {
        // If the code has been scanned, but the code has not been verified just show the username for the user, to be able to verify, that they are allowing the right device in
        displayUsername(response.user.username);
    } else {
        // Finally, if everything has been sent and verified, we can do the real job
        for(const exchange of response.keyExchanges) {
            EncryptionKey.save(exchange.safeid, RSA.decrypt(exchange.content));
        }

        AccessToken.save(response.access_token);
        RefreshToken.save(response.refresh_token);
    }
}, 1000);
Enter fullscreen mode Exit fullscreen mode

On the Authenticator device:

// Initialize a new QR code scanner instance
const scanner = new QRCodeScanner();

// Add an event listener for scan
scanner.on("scan", async (id) => {
    // Tell the remote server, that the QR code has been scanned on this device
    const { rsaPublicKey } = await API.authentication.onScan(id);

    // Show the username for the user
    await waitForVerification();

    // Get all the encryption keys
    const keys = await EncryptionKeys.getAll();

    // Iterate over all keys and encrypt them with the RSA key
    const enrypted = [];

    for(const key of keys) {
        encrypted.push({
            safeid: key.safeid,
            content: RSA.encrypt(key.content, publicKey),
        }
    }

    // Send the keys to the device
    await API.authentication.send({
        // The ID, that we scanned with the QR code
        id,
        encryptedKeys: encrypted,
    });

    // Show success screen!
    showSuccessScreen();
});

// Show the QRCode scanner for the user
scanner.start();
Enter fullscreen mode Exit fullscreen mode

Note: This code is also just serves the purpose of demonstration, implementation may vary from platform to platform

What if I lose my device? Will I lose access to my data?

By design, that would be the case. But we can create a backup key, that can be used in the future, to regain access to our data after we lost our phones.

This backup works just like an ordinary device because it is technically a device. It will have a refresh token, to gain access to our vault and it will have an RSA private key, to decrypt the key exchanges sent to it. Ohh... and also, when we create a new vault, we will send a key exchange to this backup key.

With this backup, we can provide the ability, to gain access after you lost your phone.

Thank you for reading!
If you have any questions, feel free to ask them below.

Github organization: github.com/wault-app
Discord: discord.gg/NxhdAf4azz

Discussion (0)