workspaces/crypto/README.md
# @cogitojs/crypto
This package provides cryptographic primitives that are used by other Cogito
packages, such as the [Cogito Encryption][@cogitojs/cogito-encryption] package
which provides support for encryption & decryption using keys that are managed
by the Cogito mobile app.
## Usage
Add `@cogitojs/crypto` to your dependencies:
```bash
$ yarn add @cogitojs/crypto
```
## Initializing Sodium
Before using any of the other utilities provided in `@cogitojs/crypto`, you have to make sure that Sodium is initialized and ready to use. You do that by using `Sodium` class (also provided by the `@cogitojs/crypto` package):
```javascript
import { Sodium } from '@cogitojs/crypto'
// IMPORTANT!!! must be called before you can use any of the sodium functions
await Sodium.wait()
```
You can always check if Sodium is ready by checking value of `Sodium.ready`. It is `true` when Sodium is initialized correctly.
> If you try to use any function (including constructors) that depends on Sodium library when Sodium is not initialized (i.e. when `Sodium.ready === false`), an exception will be thrown. This currently apply to `Sodium`, `StreamEncoder` and `StreamDecoder` classes.
## Using stream encoder and decoder
Stream encoding/decoding is provided by the means of the `StreamEncoder` and `StreamDecoder` classes.
You initialize `StreamEncoder` by calling its constructor:
```javascript
const streamEncoder = new StreamEncoder()
```
Then, you `push` the chunks of data to be encrypted one by one, calling `end` for the last data chunk.
```javascript
const chunk1 = Uint8Array.from({length: 10}, (v, k) => k)
console.log(`chunk1=[${chunk1}]`) // [0,1,2,3,4,5,6,7,8,9]
const chunk2 = Uint8Array.from({length: 10}, (v, k) => k)
console.log(`chunk2=[${chunk2}]`) // [0,1,2,3,4,5,6,7,8,9]
const encrypted1 = streamEncoder.push(chunk1)
// Everytime different output!
// [255,252,110,195,141,98,144,46,132,235,208,156,31,156,18,71,65,202,166,234,145,0,91,170,206,200,41]
console.log(`encrypted1=[${encrypted1}]`)
const encrypted2 = streamEncoder.end(chunk2)
// Everytime different output!
// [48,160,224,222,153,218,1,75,145,208,231,40,184,242,102,58,196,90,154,238,46,53,218,76,163,149,222]
console.log(`encrypted2=[${encrypted2}]`)
```
Now to decrypt, you call constructor of `StreamDecoder` providing it with the crypto material that you retrieved from `StreamEncoder` instance:
```javascript
const cryptoMaterial = streamEncoder.cryptoMaterial
const streamDecoder = new StreamDecoder(cryptoMaterial)
```
Now you pull the decrypted chunks by calling `pull` with encrypted chunk as the argument:
```javascript
const {message: decrypted1, tag: tag1} = streamDecoder.pull(encrypted1)
const {message: decrypted2, tag: tag2} = streamDecoder.pull(encrypted2)
console.log(`decrypted1=[${decrypted1}]`) // [0,1,2,3,4,5,6,7,8,9]
console.log(`tag1=${tag1}`) // 0
console.log(`decrypted2=[${decrypted2}]`) // [0,1,2,3,4,5,6,7,8,9]
console.log(`tag2=${tag2}`) // 3
expect(decrypted1).toEqual(chunk1)
expect(tag1).toBe(Sodium.TAG_MESSAGE)
expect(decrypted2).toEqual(chunk2)
expect(tag2).toBe(Sodium.TAG_FINAL)
```
The `pull` function returns an object `{message, tag}`. All the tags except for the last one should have value `Sodium.TAG_MESSAGE`. The tag for the last data chunk of the stream should be equal to `Sodium.TAG_FINAL`.
> Please notice, you can only access `Sodium.TAG_MESSAGE` and `Sodium.TAG_FINAL` **after** Sodium has been initialized: so after you called `await Sodium.wait()`.
Below is the complete example you can use as a jest test:
```javascript
describe('Stream Encryption Decryption', () => {
beforeAll(async () => {
await Sodium.wait()
})
it('can encrypt and decrypt', () => {
const streamEncoder = new StreamEncoder()
const chunk1 = Uint8Array.from({length: 10}, (v, k) => k)
console.log(`chunk1=[${chunk1}]`)
const chunk2 = Uint8Array.from({length: 10}, (v, k) => k)
console.log(`chunk2=[${chunk2}]`)
const encrypted1 = streamEncoder.push(chunk1)
console.log(`encrypted1=[${encrypted1}]`)
const encrypted2 = streamEncoder.end(chunk2)
console.log(`encrypted2=[${encrypted2}]`)
const cryptoMaterial = streamEncoder.cryptoMaterial
const streamDecoder = new StreamDecoder(cryptoMaterial)
const {message: decrypted1, tag: tag1} = streamDecoder.pull(encrypted1)
const {message: decrypted2, tag: tag2} = streamDecoder.pull(encrypted2)
console.log(`decrypted1=[${decrypted1}]`)
console.log(`tag1=${tag1}`)
console.log(`decrypted2=[${decrypted2}]`)
console.log(`tag2=${tag2}`)
expect(decrypted1).toEqual(chunk1)
expect(tag1).toBe(Sodium.TAG_MESSAGE)
expect(decrypted2).toEqual(chunk2)
expect(tag2).toBe(Sodium.TAG_FINAL)
})
})
```
## Using the tags
If you are not interested in validating the final tag, you can decide not to use `end` for the final data chunk in encryption, and use `push` for all the data chunks. In the end the only difference between `push` and `end` is the tag value.
[@cogitojs/cogito-encryption]: https://cogito.mobi/components/cogito-encryption