Skip to content

Chiffrement des fichiers

Pour chiffrer & déchiffrer des fichiers, on peut utiliser :

Pour l'utilisation des méthodes EncryptionSession#encryptFile et EncryptionSession#decryptFile, il faut se référer à la section dédiée du guide sur les EncryptionSession.

Format d'un fichier chiffré

Un fichier chiffré avec Seald est constitué de deux parties :

  • un en-tête contenant l'identifiant de l'EncryptionSession ;
  • le résultat du chiffrement avec la clé de l'EncryptionSession d'un tar contenant le fichier à chiffrer.

Ainsi le fichier chiffré est légèrement plus volumineux que le fichier original, d'environ 2 kB.

Description des fonctions

encryptFile

La méthode encryptFile a 4 arguments : clearFile, filename, recipients et opts.

Elle retourne un Promise qui se résout en le même type qu'utilisé pour clearFile.

clearFile: le fichier à chiffrer

encryptFile supporte plusieurs types pour clearFile et retourne le résultat avec le même type :

Des exemples d'utilisation avec ces différents types sont disponibles à la fin de cette page.

filename: le nom du fichier

Il s'agit d'un String qui est utilisé pour :

  1. être la metadata par défaut de l'EncryptionSession qui sera créée ;
  2. être enregistré comme nom du fichier à chiffrer dans le tar décrit ci-dessus.

TIP

Le filename n'est pas disponible lors du déchiffrement, cela fera l'objet d'une mise à jour.

recipients: les utilisateurs autorisés à déchiffrer

Il s'agit d'un objet décrivant les utilisateurs autorisés à déchiffrer :

  • sealdIds : un tableau contenant des identifiants Seald des utilisateurs autorisés à déchiffrer. Ceci est la méthode recommandée pour lister les utilisateurs autorisés.
  • connectors : un tableau contenant des connecteurs référençant des utilisateurs dans votre application, tels que déclarés lors de la génération des identités.
  • userIds : un tableau contenant des identifiants utilisateurs dans votre application tels que déclarés lors de la génération des identités. Cette option est dépréciée. Il est déconseillé de l'utiliser dans du nouveau code.

TIP

Il est possible de chiffrer pour un connector qui n'est pas encore assigné à une identité Seald si opts.allowUnregisteredUsers est à true.

Dans ce cas, il ne s'agit pas de chiffrement de bout-en-bout : la clé de chiffrement en clair est mise en séquestre sur les serveurs de Seald le temps qu'une identité Seald soit créée et associée à ce connector.

opts: options

Il s'agit d'un objet avec plusieurs propriétés optionnelles. Trois d'entre elles sont particulièrement intéressantes (pour avoir la liste complète, consultez la référence) :

  • fileSize : taille du fichier à chiffrer, nécessaire pour une utilisation avec les types ReadableStream and Readable.
  • metadata : metadata de l'EncryptionSession qui sera créée (la valeur de filename par défaut).
  • encryptForSelf : si vous chiffrez pour un groupe dont l'utilisateur fait partie, vous pouvez mettre cette option à false afin de ne pas chiffrer directement pour ses propres identités. L'utilisateur pourra toujours déchiffrer la donnée grâce à son appartenance au groupe. Ceci peut vous permettre d'améliorer la performance du chiffrement.

decryptFile

La méthode decryptFile a un seul argument : encryptedFile, le fichier à déchiffrer.

decryptFile supporte les mêmes types pour encryptedFile qu'encryptFile pour clearFile.

Elle retourne une Promise qui se résout en un Object qui a les propriétés suivantes :

  • data: le fichier déchiffré, est du même type que celui utilisé pour encryptedFile ;
  • type: un des types suivants 'string', 'buffer', 'node-stream', 'blob', 'readable-stream' ;
  • filename: le nom du fichier original ;
  • size: la taille du fichier en octets ;
  • sessionId: l'identifiant de l'EncryptionSession.

Exemples

Avec des String

La signature la plus simple pour chiffrer un fichier est un String:

js
const encryptedString = await seald.encryptFile(
  'Secret file content',
  'SecretFile.txt',
  { sealdIds: [mySealdId, user2SealdId] }
)
const encryptedString = await seald.encryptFile(
  'Secret file content',
  'SecretFile.txt',
  { sealdIds: [mySealdId, user2SealdId] }
)

Dans ce cas, le type de sortie sera un String encodé en base64 du fichier chiffré. Il sera alors possible de le déchiffrer de la façon suivante :

js
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'
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

Le contenu de clearFile est interprété comme étant de l'UTF-8.

TIP

S'il ne s'agit que de textes à chiffrer, il est recommandé d'utiliser l'autre format de chiffrement : encryptMessage / decryptMessage qui est beaucoup plus léger.

WARNING

La sortie de encryptFile utilisée avec un String est environ 33% plus lourde que le fichier original à cause de l'encodage base64.

Avec des Buffer

Le SDK peut utiliser des Buffer au sens de Node.js (qui sont polyfillés hors environnement Node.js) :

js
const encryptedBuffer = await seald.encryptFile(
  Buffer.from('Secret file content', 'utf8'),
  'SecretFile.txt',
  { sealdIds: [mySealdId, user2SealdId] }
)
const encryptedBuffer = await seald.encryptFile(
  Buffer.from('Secret file content', 'utf8'),
  'SecretFile.txt',
  { sealdIds: [mySealdId, user2SealdId] }
)

Dans ce cas, le type de sortie sera un Buffer. Il sera alors possible de le déchiffrer de la façon suivante :

js
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'
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'

Avec des Blob

Dans un navigateur, le SDK peut utiliser des Blob :

js
const encryptedBlob = await seald.encryptFile(
  new Blob(['Secret file content']),
  'SecretFile.txt',
  { sealdIds: [mySealdId, user2SealdId] }
)
const encryptedBlob = await seald.encryptFile(
  new Blob(['Secret file content']),
  'SecretFile.txt',
  { sealdIds: [mySealdId, user2SealdId] }
)

Dans ce cas, le type de sortie sera un Blob. Il sera alors possible de le déchiffrer de la façon suivante :

js
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'
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

Les fichiers obtenus au travers de champs <input type="file"> sont de type File qui hérite de Blob. Il sont donc traités de la même façon par le SDK.

Le Blob déchiffré n'a pas de Blob#type défini, mais il peut être déduit de l'extension du fichier (en utilisant le paquet mime-types par exemple).

Vous pouvez également recréer une instance de File en utilisant son constructeur à partir du Blob déchiffré et du filename (et éventuellement du type mime déduit).

WARNING

React-Native n'implémentant pas les méthodes Blob#arrayBuffer ou Blob#stream pourtant standards, il n'est pas possible d'utiliser le type Blob dans un environnement React-Native. Il faut utiliser un autre type (comme les Buffer en utilisant un polyfill ou les String).

Avec des ReadableStream

Dans un navigateur, le SDK peut utiliser des ReadableStream. Par exemple, avec la méthode Blob#stream :

WARNING

Dans ce mode, il faut passer à la fonction encryptFile la taille du fichier en clair via le paramètre options.fileSize.

js
const blob = new Blob(['Secret file content'])

const encryptedReadableStream = await seald.encryptFile(
  blob.stream(),
  'SecretFile.txt',
  { sealdIds: [mySealdId, user2SealdId] },
  { fileSize: blob.size }
)
const blob = new Blob(['Secret file content'])

const encryptedReadableStream = await seald.encryptFile(
  blob.stream(),
  'SecretFile.txt',
  { sealdIds: [mySealdId, user2SealdId] },
  { fileSize: blob.size }
)

Dans ce cas, le type de sortie sera un ReadableStream. Il sera alors possible de le déchiffrer de la façon suivante :

js
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'
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'

Streamer un envoi de fichier

Hormis sur Chrome depuis la version 95, il n'est pas possible d'utiliser fetch avec un ReadableStream comme corps de requête.

Pour contourner, une solution consiste à découper côté client le fichier chiffré en chunks de 5Mo par exemple, de les envoyer individuellement au serveur, et le serveur aura la charge de reconstituer les différents chunks dans l'ordre.

Avec des streams Readable de Node.js

Le SDK peut utiliser des streams Readable de Node.js. Dans un navigateur, cela fonctionne également en utilisant un polyfill. Par exemple, pour lire & chiffrer un fichier puis l'écrire :

WARNING

Dans ce mode, il faut passer à la fonction encryptFile la taille du fichier en clair via le paramètre options.fileSize.

js
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') })
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') })

Il sera alors possible de le déchiffrer de la façon suivante :

js
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') })
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') })