npm package: @raytio/maxcryptor
Description: The Maxcryptor was developed for [rayt.io](https://rayt.io) for use in the web client. It has been made open source primarily for the purpose of transparency and auditing of the cryptography used by rayt.io, but there's no reason it can't be used for othe
Latest version: 3.2.0

# Maxcryptor

## What is the Maxcryptor?

The Maxcryptor was developed for [rayt.io](https://rayt.io) for use in the web client.
It has been made open source primarily for the purpose of transparency and auditing of the
cryptography used by rayt.io, but there's no reason it can't be used for other projects.

It exposes a "safe" set of cryptographic functions that can be used to encrypt, decrypt,
sign, verify and share data, without the user of the library having to worry about getting
the crypto correct.

## How does it work?

The Maxcryptor uses the browser's built in [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Crypto)
under the hood. As a result it is only supported by browsers that support the spec.

The two main entities and their operations are described below.

### User

A user is designed to be a single user account.
A user can perform the following operations:

- *Encrypt* JSONable data
- *Decrypt* data encrypted by the encrypt operation
- *Update* data which has already been encrypted
- *Share* data encrypted by the encrypt operation with an application
- *Sign* data so that other parties can verify it
- *Verify* data was signed by themself
- *Verify* data was signed by another user or application
- *Create application* for receiving shared data
- *Load application* that they have created, or has been shared with them

### Application

An application is an entity designed to be used by many users.

Users can share data with an application.

Users who have been granted access to an application can:

- *Decrypt* data shared with the application
- *Share data* that has been shared with this application with another application
- *Share access* to that application

## What crypto does it use?

A user has two key pairs, one for encryption/decryption and one for signing/verifying.
The public keys are stored unencrypted in JWK format, and the private keys are encrypted with AES-GCM with a key length of 256 bits.
The key used to encrypt the private keys is derived from the user's password using PBKDF2 with a hash of SHA-512 and
1,000,000 iterations.
A separate IV is used for each encrypted key, and is stored with the encrypted keys.

Data is encrypted with AES-GCM with a key length of 256 bits.
A new key is generated for each piece of data that is encrypted.
This key is then encrypted with RSA-OAEP with a modulus length of 2048 bits, and SHA-512 as the hash function.
The advantage of this is that the data doesn't have to be re-encrypted to be shared, the key can just be re-encrypted
with the public key of the party which the data is to be shared with.
Note that unless the data is signed before being encrypted, or it is enforced with application logic the encrypted
data could be modified by any of the parties that the data has been shared with.

Signing is performed with RSA-PSS with a modulus length of 2048 bits, and a hash of SHA-512.
JSON structures are first serialized with a canonical JSON spec as described here: <http://gibson042.github.io/canonicaljson-spec/> ,
then encoded in UTF-8 to ensure that semantically equivalent data will always verify correctly.

Crypto within an application is identical to a user in every way other than how it's private key is stored.
Rather than belonging to a single user, an application can belong to several.
The application's private key is encrypted exactly like data, (first AES, then RSA) so that it can be shared.

## Known issues

- Because we don't sign data before encrypting it, there is no way to verify that encrypted data was created by a specific party,
  only that they had access to the key
- We don't have a key chain, so signing and verifying data is of limited use. You would have to trust that the public
  key belongs to the user or application that you think it does.
- While some work has been done to enable upgrading ciphers as time passes, this is far from complete
- We don't currently have a way to cycle keys, and this will have to be done in application code if it is desired

## Which browsers are supported?

Any browser that supports the relevant parts of the [Web Crypto API spec](https://www.w3.org/TR/WebCryptoAPI/),
and several other browser specs.

This includes Chrome, Firefox, Safari and Edge (79+).
Most common mobile browsers should be supported, however extensive testing has not yet been performed.

Internet Explorer is not supported as it only has partial support for the Web Crypto API.

### What about polyfilling unsupported browsers?

In theory this could be done, but it would be very difficult to verify that any polyfill is not compromised.
For this reason it is unlikely that we will ever support browsers without the WebCrypto API.

## How do I use it?

Firstly you'll want to create a new user:

### Creating a user encryptor

``` javascript
import { newUser } from '@raytio/maxcryptor';

const { encryptor, userDoc } = await newUser('my very secure password');
```

This returns an encryptor and some userDoc. You'll want to store user data somewhere so that the encryptor can
be retrieved again later.

The userDoc is a JSON object, with the structure:

``` javascript
{
    kek_derivation_config: KeyDerivationConfigParams;
    private_key_encryption_config: KeyEncryptionConfigParams;
    encryption_key_pair: EncryptedKeyPair;
    signing_key_pair: EncryptedKeyPair;
}
```

### Getting an encryptor for an existing user

The encryptor can be retrieved from the data we saved when we created a new user, and the password that was used to
create it.

``` javascript
import { encryptorFromExistingUser } from '@raytio/maxcryptor';

const { encryptor } = await encryptorFromExistingUser(userDoc, 'my very secure password');
```

### Supporting "Remember Me" login persistence

If we want to support "Remember Me" login persistence then we have to store enough information to reconstruct
an encryptor at a future date. We could store:
    1. The user's password
    2. The key (kek) used to decrypt the user's encryption and signing keys
    3. The encryption and signing keys themselves

While all of these situations are likely to lead to encrypted data being read if the persisted authentication data is
compromised, there are reasons why options 1 and 3 are worse:
    - If the persisted data is compromised in option 1, the user's password (which is used for API access, and may be
used for other services, even though it discouraged) is compromised.
    - In option 3 there is a lot more data to store, which could theoretically exceed what is allowed the persistence mechanism.
    - In option 3 we have to ensure that the authentication data that is persisted is up to date.
    - If the persisted data is compromised in option 3, then encrypted data will be readable even after a password change.

When we first log the user in, we'll want to store the kek:

``` javascript
import { encryptorFromExistingUser } from '@raytio/maxcryptor';

const { encryptor, kek } = await encryptorFromExistingUser(userDoc, 'my very secure password');
// In this example we'll save to local storage
localStorage.setItem('kek', kek);
```

When the user returns, we'll load the kek and build our encryptor:

``` javascript
import { encryptorFromExistingUserKek } from '@raytio/maxcryptor';

const kek = localStorage.getItem('kek');
const { encryptor } = await encryptorFromExistingUserKek(userDoc, kek);
```

When the user logs out it is very important that we discard the kek:

``` javascript
const kek = localStorage.removeItem('kek');
```

### Changing a user's password

This works by decrypting their keys with their existing password, and re-encrypting them with the new one.
This means no pasword reset is possible!
You'll want to save the new userDoc, as the existing data is not modified.

``` javascript
import { changePassword } from '@raytio/maxcryptor';

const newUserDoc = await changePassword(
  userDoc,
  'my very secure password',
  'my much more secure password'
);
```

### Exporting a user's keys

A user can export their keys to JSON so that they can recover their data even if they forget their password.
These keys will need to be kept very secure.

``` javascript
import { exportKeys } from '@raytio/maxcryptor';

const exportedKeys = await exportKeys(
  userDoc,
  'my very secure password'
);
```

### Importing a user's keys

In the case when the user forgets their password but has exported their keys, they can restore their user
document with a new password, and still have access to their previously encrypted data.

``` javascript
import { importKeys } from '@raytio/maxcryptor';

const newUserDoc = await importKeys(
  exportedKeys,
  'i hope i don\'t forget this password!'
);
```


### Encrypting and decrypting a user's own data

What good is this library if you can't encrypt or decrypt data?
Any JSONable data can be encrypted with the encryptor you get for a new or existing user.

``` javascript
const dataToEncrypt = "string, but this could be any jsonable data type";

const encryptedData = await encryptor.encrypt(dataToEncrypt);
const decryptedData = await encryptor.decrypt(encryptedData);

console.log(dataToEncrypt === decryptedData); // Will print true
```

### Updating existing encrypted data

A user can "update" their encrypted data. This is useful when the original data has been shared,
and the user wants these changes to be visible to applications which the data has been shared with.
If this is not desirable then use encryptor.encrypt() instead.

Note: This won't modify the input data, but rather return a new encrypted data object.

```  javascript
const dataToEncrypt = "string, but this could be any jsonable data type";
const originalEncryptedData = await encryptor.encrypt(dataToEncrypt);

const newDataToEncrypt = "something different";
const newEncryptedData = await encryptor.update(
    newDataToEncrypt,
    originalEncryptedData
);
```

### Creating an application

An application is an entity that users can share data with. We use an
ApplicationEncryptor, which is very similar to a regular DataEncryptor, but with a
slightly different set of operations.
You'll want to export the public key so that other users can share data with it,
and encrypt the private key so that you can decrypt data that has been shared with it.

Notice that you need to supply a public key when encrypting the private key.
This allows you to share them with other users, allowing multiple users to decrypt the
shared data.

``` javascript
const applicationEncryptor = await encryptor.createApplicationEncryptor();

// Share this with anyone you want to be able to share data with
const applicationPublicKey = await applicationEncryptor.exportPublicKey();

// Only users that you encrypt the private key for will be able to load the
// application for decryption. This includes your user!
const applicationEncryptedPrivateKey = await applicationEncryptor.encryptPrivateKey(
  // Note that we encrypt with the user's public key
  userDoc.encryption_key_pair.public_key
);
```

### Sharing data with an application

If you wish to share data with an application, it must first be encrypted.
You will then receive a key that can be used by the application (and only that application)
to decrypt that data.

```  javascript
const dataToEncrypt = "string, but this could be any jsonable data type";
const encryptedData = await encryptor.encrypt(dataToEncrypt);

const encryptedDecryptionKey = await encryptor.share(
  encryptedData,
  applicationPublicKey
);
```

### Loading an application for decryption

If you wish to obtain an instance of the application that you have created previously, or
that has been shared with you, you can do so with your user encryptor, the application's public
key, and the private key that has been encrypted for your user.

``` javascript
const applicationEncryptor = await encryptor.loadApplicationEncryptorForDecryption(
  applicationPublicKey,
  applicationEncryptedPrivateKey
);
```

### Decrypting data with an application

Now that you have the application and some data shared with it, you can decrypt it.

``` javascript
const decryptedData = await applicationEncryptor.decrypt(
  encryptedData,
  encryptedDecryptionKey
);

console.log(dataToEncrypt === decryptedData); // Will print true
```

### On-sharing data shared with an application

An application can shared data which has been shared with it on to another application.
The recipient application can decrypt as it would if it had been shared directly with them.

Note: This operation is identical to the share operation on the user encryptor, other than
taking a third argument.

``` javascript
const encryptedDecryptionKey = await applicationEncryptor.share(
  encryptedData,
  application2PublicKey,
  encryptedDecryptionKey
);
```

### Other operations

Both user and application encryptors are capable of other operations, however they aren't
documented because we don't currently use them and they are likely to change.

## License

This project is under the MIT license, as documented here: [https://www.rayt.io/mit](https://www.rayt.io/mit)