File encryption
To encrypt & decrypt files, you can use:
encryptFile
anddecryptFile
methods of the SDK, or;- the
encryptFile
anddecryptFile
methods of anEncryptionSession
.
For use of the EncryptionSession#encryptFile
and EncryptionSession#decryptFile
methods, refer to the dedicated section of the EncryptionSession
guide.
Format of an encrypted file
A file encrypted with Seald consists of two parts:
- a header containing the identifier of the
EncryptionSession
; - the result of the encryption with the key of the
EncryptionSession
of a tar containing the file to be encrypted.
Thus the encrypted file is slightly larger than the original file, by about 2 kB.
Function description
encryptFile
The encryptFile
method has 4 arguments: clearFile
, filename
, recipients
and opts
.
It returns a Promise
which resolves to the same type as used for clearFile
.
clearFile
: the file to encrypt
encryptFile
supports several types for clearFile
and returns the result with the same type:
string
;Buffer
;Blob
in the browser only;ReadableStream
of Web Streams;Readable
of Node.JS Streams).
Examples of use with these different types are available at the end of this page.
filename
: the name of the file
This is a String
which is used to:
- be the default
metadata
of theEncryptionSession
that will be created; - be registered as the name of the file to be encrypted in the tar described above.
TIP
The filename
is not available at decryption, it will be in a future update.
recipients
: users authorized to decrypt
This is an object with properties describing the users authorized to decrypt:
sealdIds
: an array containing the Seald IDs of users allowed to decrypt. This is the recommended way to list allowed user.connectors
: an array containing connectors referencing users of your app, as declared during the generation of identities.userIds
: an array containing user IDs in your application as declared during the generation of identities. This option is deprecated. It is not advised to use it in new code.
TIP
It is possible to encrypt for a connector
that is not yet assigned to a Seald identity if opts.allowUnregisteredUsers
is set to true
.
In this case, it is not end-to-end encryption: the plaintext encryption key is put in escrow on the Seald servers until a Seald identity is created and associated to this connector
.
opts
: options
This is an object with several optional properties. Three of them are of interest (to see the full list, see the reference):
fileSize
: size of the file to be encrypted, required for use with theReadableStream
andReadable
types.metadata
: themetadata
of theEncryptionSession
that will be created (the default is the value offilename
).encryptForSelf
: if you are encrypting for a group of which the user is a member, you can set it tofalse
so that you do not encrypt directly for their own identities. The user will still be able to decrypt the data thanks to their group membership. This can allow you to improve the performance of the encryption.
decryptFile
The decryptFile
method has a single argument: encryptedFile
, the file to be decrypted.
decryptFile
supports the same types for encryptedFile
as encryptFile
does for clearFile
.
It returns a Promise
which resolves to an Object
with the following properties:
data
: the decrypted file, is of the same type as used forencryptedFile
;type
: one of'string'
,'buffer'
,'node-stream'
,'blob'
,'readable-stream'
;filename
: the original file name;size
: the file size in bytes;sessionId
: theEncryptionSession
's identifier.
Examples
With String
The simplest signature to encrypt a file is with a String
:
const encryptedString = await seald.encryptFile(
'Secret file content',
'SecretFile.txt',
{ sealdIds: [mySealdId, user2SealdId] }
)
In this case, the output type will be a base64 encoded String
of the encrypted file. It will then be possible to decrypt it as follows:
const {
data, // 'Secret file content'
type, // 'string'
size, // 19
sessionId, // the encryption session ID
filename // 'SecretFile.txt'
} = await seald.decryptFile(encryptedString)
console.log(data) // This will log 'Secret file content'
info
The content of clearFile
is interpreted as utf-8.
TIP
If only text is to be encrypted, it is recommended to use the other encryption format: encryptMessage
/ decryptMessage
which is much lighter.
WARNING
The output of encryptFile
used with a String
is about 33% heavier than the original file due to the base64 encoding.
With Buffer
The SDK can use Buffer
in the sense of Node.js (which are polyfilled outside of a Node.js environment):
const encryptedBuffer = await seald.encryptFile(
Buffer.from('Secret file content', 'utf8'),
'SecretFile.txt',
{ sealdIds: [mySealdId, user2SealdId] }
)
In this case, the output type will be a Buffer
. It will then be possible to decrypt it in the following way:
const {
data, // An instance of Buffer containing the clear text data
type, // 'buffer'
size, // 19
sessionId, // the encryption session ID
filename // 'SecretFile.txt'
} = await seald.decryptFile(encryptedBuffer)
console.log(data.toString('utf8')) // This will log 'Secret file content'
With Blob
In a browser, the SDK can use Blob
:
const encryptedBlob = await seald.encryptFile(
new Blob(['Secret file content']),
'SecretFile.txt',
{ sealdIds: [mySealdId, user2SealdId] }
)
In this case, the output type will be a Blob
. It will then be possible to decrypt it in the following way:
const {
data, // An instance of Blob containing the clear text data
type, // 'blob'
size, // 19
sessionId, // the encryption session ID
filename // 'SecretFile.txt'
} = await seald.decryptFile(encryptedBlob)
console.log(await data.text()) // This will log 'Secret file content'
TIP
The files obtained through fields <input type="file">
are of type File
which inherits from Blob
. They are therefore treated in the same way by the SDK.
The decrypted Blob
has no Blob#type
set but it can be inferred from the file extension (using the mime-types
package for example).
You can also re-create an instance of File
using its constructor from the decrypted Blob
and the filename
(and optionally the inferred mime type).
WARNING
Since React-Native does not implement the Blob#arrayBuffer
or Blob#stream
methods, even though they are standard, it is not possible to use the Blob
type in a React-Native environment. It is necessary to use another type (like Buffer
using a polyfill or String
).
With ReadableStream
In a browser, the SDK can use ReadableStream
. For example, with the method Blob#stream
:
WARNING
In this mode, the encryptFile
function must be given the size of the plaintext file via the options.fileSize
parameter.
const blob = new Blob(['Secret file content'])
const encryptedReadableStream = await seald.encryptFile(
blob.stream(),
'SecretFile.txt',
{ sealdIds: [mySealdId, user2SealdId] },
{ fileSize: blob.size }
)
In this case, the output type will be a ReadableStream
. It will then be possible to decrypt it in the following way:
const {
data, // An instance of ReadableStream which will stream the clear text data
type, // 'readable-stream'
size, // 19
sessionId, // the encryption session ID
filename // 'SecretFile.txt'
} = await sealdSession.decryptFile(encryptedReadableStream)
let result = ''
const reader = data.getReader()
while (true) {
const read = await reader.read()
if (read.done) break
else result += read.value
}
console.log(result) // This will log 'Secret file content'
Stream a file upload
Except on Chrome since version 95, it is not possible to use fetch
with a ReadableStream
as a request body.
To work around this, a solution is to split the encrypted file on the client side into chunks of e.g. 5MB, send them individually to the server, and the server will be responsible for reconstructing the different chunks in order.
With Node.js' Readable
streams
The SDK can use Readable
streams from Node.js. In a browser, this also works if you use a polyfill. For example, to read & encrypt a file then write it:
WARNING
In this mode, the encryptFile
function must be given the size of the plaintext file via the options.fileSize
parameter.
const { createReadStream, createWriteStream, promises: fsPromises } = require('fs')
const { stat } = fsPromises
const inputFile = './path/to/fileToEncrypt'
const outputFile = './path/to/encryptedFile'
const readable = createReadStream(inputFile)
const writable = createWriteStream(outputFile)
const encryptedStream = await seald.encryptFile(
readable,
'SecretFile.txt',
{ sealdIds: [mySealdId, user2SealdId] },
{ fileSize: (await stat(inputFile)).size }
)
encryptedStream
.on('error', () => { console.error('errored') })
.pipe(writable)
.on('error', () => { console.error('errored') })
.on('end', () => { console.log('finished') })
It will then be possible to decrypt it in the following way:
const { createReadStream, createWriteStream, promises: fsPromises } = require('fs')
const { stat } = fsPromises
const inputFile = './path/to/encryptedFile'
const outputFile = './path/to/decryptedFile'
const readable = createReadStream(inputFile)
const writable = createWriteStream(outputFile)
const {
data, // An instance of Readable which will stream the clear text data
type, // 'node-stream'
size, // 19
sessionId, // the encryption session ID
filename // 'SecretFile.txt'
} = await seald.decryptFile(readable)
data
.on('error', () => { console.error('errored') })
.pipe(writable)
.on('error', () => { console.error('errored') })
.on('end', () => { console.log('finished') })
On React-Native
Reading files on React-Native is usually done with the package react-native-fs
, or with expo-file-system
In both cases, reading a binary file returns a base64 string.
In order to encrypt the raw file data, and not its base64 encoding, you need to convert it into a binary format, for example a Buffer
.
import RNFS from 'react-native-fs'
import { Buffer } from '@craftzdog/react-native-buffer'
const inputStringB64 = await RNFS.readFile(inputFilePath, 'base64')
const inputBuffer = Buffer.from(inputStringB64, 'base64') // Convert the base64 before encrypting
const encryptedBuffer = await seald.encryptFile(
inputBuffer,
'fileToEncrypt',
{ sealdIds: [mySealdId, user2SealdId] }
)
import * as FileSystem from 'expo-file-system'
import { Buffer } from '@craftzdog/react-native-buffer'
const inputStringB64 = await FileSystem.readAsStringAsync(
inputFilePath,
{ encoding: FileSystem.EncodingType.Base64 }
)
const inputBuffer = Buffer.from(inputStringB64, 'base64') // Convert the base64 before encrypting
const encryptedBuffer = await seald.encryptFile(
inputBuffer,
'fileToEncrypt',
{ sealdIds: [mySealdId, user2SealdId] }
)
In the same way, to decrypt a file from the file system, you will also need to decode the base64 in the same way.