Using Themis in WebAssembly instructions

Note: For non-Wasm JavaScript instructions, see NodeJS how-to page.

Introduction

The WasmThemis wrapper uses WebAssembly to provide access to the features and functions of the Themis cryptographic library:

  • Key generation: creation of public/private key pairs, used in Secure Message and Secure Session.
  • Secure Message: secure exchange of messages between two parties. RSA + PSS + PKCS#7 or ECC + ECDSA (based on key choice), AES GCM container.
  • Secure Storage (aka Secure Cell): provides secure storage of record-based data through symmetric encryption and data authentication. AES GCM / AES CTR containers.
  • Secure Session: a session between two peers, within which the data can be securely exchanged with higher security guarantees. EC + ECDH, AES container.
  • Secure Comparator: comparison of a secret between two parties without leaking anything related to the secret: Zero-Knowledge Proof-based authentication system. Hardened Socialist Millionaire Protocol + ed25519.

There are example console utilities available in JavaScript for WasmThemis (see the full list here). They help understand the specific mechanics of encryption/decryption processes with WasmThemis.

Supported versions

WasmThemis is available for modern web browsers supporting WebAssembly and ECMAScript 6. The browsers checked to be supporting WasmThemis are: Google Chrome, Mozilla Firefox, Apple Safari, Microsoft Edge.

WasmThemis is tested and supported on current versions of Node.js (v8+) and can be used in Electron framework (v4+).

Quickstart

Installing stable version from npm

WasmThemis wrapper is available on npm:

npm install wasm-themis

Building latest version from source

If the stable package version does not suit your needs, you can manually build and install the latest version of Themis from source code.

NOTE: WasmThemis uses BoringSSL as its cryptographic backend. BoringSSL is included as a submodule in the Themis repository so you don't have to install anything additionally to use it.

1. Install and activate Emscripten toolchain as described in documentation here.

2. Build and install WasmThemis wrapper from the source code.

Importing Themis

Add the following lines to your code:

const themis = require('wasm-themis')

themis.initialized.then(function() {
    // You may use Themis once this promise is resolved.
})

⚠️ IMPORTANT: WebAssembly code is loaded and compiled asynchronously so you have to wait for the themis.initialized promise to complete before using any Themis functions. If Themis is called too early, you will see an error message like this: Assertion failed: you need to wait for the runtime to be ready (e.g. wait for main() to be called)

Deploying applications

WasmThemis is distributed as Node.js package which is compatible with many JavaScript module bundlers like Browserify, webpack, and Electron framework packager.

However, in addition to JavaScript code, WasmThemis also includes the compiled WebAssembly bytecode. The bytecode is located in node_modules/wasm-themis/src/libthemis.wasm file, which will be loaded by the browser separately. Make sure that libthemis.wasm is placed next to the produced JavaScript bundle by copying it from node_modules directory when building your application.

Bundler specifics

If you are using webpack, you may also need to add the following declaration to your webpack.config.js:

node: {
  fs: 'empty'
}

Web server MIME types

Web servers should use MIME type application/wasm for serving WebAssembly files. This enables more efficient streaming compilation process with shorter startup times. The servers are usually pre-configured to use proper MIME types, but if you see the following warning message:

wasm streaming compile failed: TypeError: Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'.

then you might need to check your web server configuration.

Nginx MIME types are configured via /etc/nginx/mime.types configuration file.
Apache and other servers usually use /etc/mime.types configuration file.
Reload configuration or restart the server for the change to take effect.

Examples

Some code samples for Themis objects are available in docs/examples/js folder.

Using Themis

Keypair generation

Themis supports both Elliptic Curve and RSA algorithms for asymmetric cryptography. Algorithm type is chosen according to the generated key type. Asymmetric keys are necessary for Secure Message and Secure Session objects.

⚠️ WARNING: When you distribute private keys to your users, make sure the keys are sufficiently protected. You can find the guidelines here.

NOTE: When using public keys of other peers, make sure they come from trusted sources.

For learning purposes, you can play with Themis Interactive Simulator (Themis Server) to get the keys and simulate whole client-server communication.

Keypair generation interface

class KeyPair {
    constructor()
    constructor(private, public)
    get privateKey()
    get publicKey()
}
class PrivateKey extends Uint8Array {
    constructor(array)
}
class PublicKey extends Uint8Array {
    constructor(array)
}

Description:

  • new themis.KeyPair()
    Generate and returns a new Elliptic Curve key pair.
    Raises ThemisError on failure.

  • new themis.KeyPair(private, public)
    Construct an Elliptic Curve key pair from private and public keys.
    Raises ThemisError on failure.

  • privateKey
    Get private key of the pair as PrivateKey.

  • publicKey
    Get public key of the pair as PublicKey.

  • new themis.PrivateKey(array)
    Wrap Uint8Array and ensure that it contains a valid private key.
    Raises ThemisError on failure.

  • new themis.PublicKey(array)
    Wrap Uint8Array and ensure that it contains a valid public key.
    Raises ThemisError on failure.

Example

let keyPair = new themis.KeyPair()
let privateKey = keyPair.privateKey
let publicKey = keyPair.publicKey

let privateKey
try {
    privateKey = new themis.PrivateKey(byteArray)
} catch (error) {
    // See error.message for a human-readable text message
    // and error.code for integer code like themis.ThemisErrorCode.INVALID_PARAMETER
}

Secure Message

The Secure Message functions provide a sequence-independent, stateless, contextless messaging system. This may be preferred in cases that don't require frequent sequential message exchange and/or in low-bandwidth contexts. This is secure enough to exchange messages from time to time, but if you'd like to have Perfect Forward Secrecy and higher security guarantees, please consider using Secure Session instead.

The Secure Message functions offer two modes of operation:

In Sign/Verify mode, the message is signed using the sender's private key and is verified by the receiver using the sender's public key. The message is packed in a suitable container and ECDSA is used by default to sign the message (when RSA key is used, RSA+PSS+PKCS#7 digital signature is used).

In Encrypt/Decrypt mode, the message will be encrypted with a randomly generated key (in RSA) or a key derived by ECDH (in ECDSA), via symmetric algorithm with Secure Cell in seal mode (keys are 256 bits long).

The mode is selected at construction time. SecureMessage objects provide encrypt and decrypt methods for encrypt/decrypt mode. A valid public key of the receiver and a private key of the sender are required in this mode. For sign/verify mode SecureMessageSign and SecureMessageVerify objects provide sign and verify methods respectively. They only require a private key for signing and a public key for verification.

Read more about the Secure Message's cryptographic internals here.

Secure Message interface

Encrypt/Decrypt mode
class SecureMessage {
    constructor(keyPair)
    constructor(privateKey, publicKey)
    encrypt(message)
    decrypt(message)
}

Description:

  • new themis.SecureMessage(keyPair)
    Create a new Secure Message with keyPair holding your private key and public key of the peer.
    Raises ThemisError on failure.

  • new themis.SecureMessage(privateKey, publicKey)
    Create a new Secure Message with your privateKey and publicKey of the peer separately.
    Raises ThemisError on failure.

  • encrypt(message)
    Encrypt message, return encrypted Secure Message container as Uint8Array.
    Raises ThemisError on failure.

  • decrypt(message)
    Decrypt message, return decrypted original message as Uint8Array.
    Raises ThemisError on failure.

Sign/Verify mode
class SecureMessageSign {
    constructor(privateKey)
    sign(message)
}
class SecureMessageVerify {
    constructor(publicKey)
    verify(message)
}

Description:

  • new themis.SecureMessageSign(privateKey)
    Create a new Secure Message for signing with your privateKey.
    Raises ThemisError on failure.

  • sign(message)
    Sign message, return signed Secure Message container as Uint8Array.
    Raises Error on failure.

  • new themis.SecureMessageVerify(publicKey)
    Create a new Secure Message for verifying with publicKey of the peer.
    Raises ThemisError on failure.

  • verify(message)
    Verify message signature, return Uint8Array with original message.
    Raises ThemisError on failure.

Example

Encrypt/Decrypt mode

Initialise encrypter and decypter:

let smessage_alice = new themis.SecureMessage(alice.privateKey, bob.publicKey)

let smessage_bob = new themis.SecureMessage(bob.privateKey, alice.publicKey)

Always make sure that you use keys from the same keypair. For decryption, you need to use a private key matching the public one used for encryption.

Encrypt message:

try {
    let encryptedMessage = smessage_alice.encrypt(message)
} catch (error) {
    // See error.message for a human-readable text message
    // and error.code for integer code like themis.ThemisErrorCode.FAIL
}

Decrypt message:

try {
    let message = smessage_bob.decrypt(encryptedMessage)
} catch (error) {
    // See error.message for a human-readable text message
    // and error.code for integer code like themis.ThemisErrorCode.FAIL
}
Sign/Verify mode

Initialise signer and verifier:

let smessage_sign = new themis.SecureMessageSign(alice.privateKey)

let smessage_verify = new themis.SecureMessageVerify(alice.publicKey)

Always make sure that you use keys from the same keypair. You need to use a matching public key to verify messages signed by the private key.

Sign message:

try {
    let signedMessage = smessage_sign.sign(message)
} catch (error) {
    // See error.message for a human-readable text message
    // and error.code for integer code like themis.ThemisErrorCode.FAIL
}

Verify message:

try {
    let message = smessage_verify.verify(signedMessage)
} catch (error) {
    // See error.message for a human-readable text message
    // and error.code for integer code like themis.ThemisErrorCode.INVALID_SIGNATURE
}

Secure Cell

The Secure Сell functions provide the means of protection for arbitrary data contained in stores, i.e. database records or filesystem files. These functions provide both strong symmetric encryption and data authentication mechanisms.

The general approach is that given:

  • input: some source data to protect,
  • key: a password,
  • context: plus an optional "context information",

Secure Cell functions will produce:

  • cell: the encrypted data,
  • authentication tag: some authentication data.

The purpose of the optional "context information" (i.e. a database row number or file name) is to establish a secure association between this context and the protected data. In short, even when the password is known, if the context is incorrect, the decryption will fail.

The purpose of the authentication data is to verify that given a correct password (and context), the decrypted data is indeed the same as the original source data.

The authentication data must be stored somewhere. The most convenient way is to simply append it to the encrypted data, but this is not always possible due to the storage architecture of an application. The Secure Cell functions offer different variants that address this issue.

By default, the Secure Cell uses the AES-256 encryption algorithm. The generated authentication data is 16 bytes long.

Secure Cell is available in 3 modes:

  • Seal mode: the most secure and user-friendly mode. Your best choice most of the time.
  • Token protect mode: the most secure and user-friendly mode. Your best choice most of the time.
  • Context imprint mode: length-preserving version of Secure Cell with no additional data stored. Should be used with care and caution.

You can learn more about the underlying considerations, limitations, and features here.

Secure Cell interface

Seal mode
class SecureCellSeal {
    constructor(masterKey)
    encrypt(message)
    encrypt(message, context)
    decrypt(message)
    decrypt(message, context)
}

Description:

  • new themis.SecureCellSeal(masterKey)
    Create a Secure Cell in seal mode with masterKey (a non-empty Uint8Array).
    Raises ThemisError on failure.

  • encrypt(message), encrypt(message, context)
    Encrypt message with additional context (optional argument).
    Returns encrypted message as Uint8Array.
    Raises ThemisError on failure.

  • decrypt(message), decrypt(message, context)
    Decrypt message with additional context (optional argument).
    Returns original message as Uint8Array.
    Raises ThemisError on failure.

Tokan Protect mode
class SecureCellTokenProtect {
    constructor(masterKey)
    encrypt(message)
    encrypt(message, context)
    decrypt(message, token)
    decrypt(message, token, context)
}

Description:

  • new themis.SecureCellTokenProtect(masterKey)
    Create a Secure Cell in token-protect mode with masterKey (a non-empty Uint8Array).
    Raises ThemisError on failure.

  • encrypt(message), encrypt(message, context)
    Encrypt message with additional context (optional argument).
    Returns an object with two Uint8Array fields: encrypted message (data) and authentication token (token).
    Raises ThemisError on failure.

  • decrypt(message, token), decrypt(message, token, context)
    Decrypt message with token and additional context (optional argument).
    Returns original message as Uint8Array.
    Raises ThemisError on failure.

Context Imprint mode
class SecureCellContextImprint {
    constructor(masterKey)
    encrypt(message, context)
    decrypt(message, context)
}

Description:

  • new themis.SecureCellSeal(masterKey)
    Create a Secure Cell in context-imprint mode with masterKey (a non-empty Uint8Array).
    Raises ThemisError on failure.

  • encrypt(message, context)
    Encrypt message with mandatory context argument.
    Returns encrypted message as Uint8Array.
    Raises ThemisError on failure.

  • decrypt(message, context)
    Decrypt message with mandatory context argument.
    Returns original message as Uint8Array.
    Raises ThemisError on failure.

Examples

Secure Cell seal mode

let scell = new themis.SecureCellSeal(key)

let encrypted_message = scell.encrypt(message, context)
let decrypted_message = scell.decrypt(encrypted_message, context)

Secure Cell token-protect mode:

let scell = new themis.SecureCellTokenProtect(key)

let encrypted = scell.encrypt(message, context)
let encrypted_message = encrypted.data
let encrypted_token = encrypted.token

let decrypted_message = scell.decrypt(encrypted.data, encrypted.token, context)

Secure Cell context-imprint mode:

let scell = new themis.SecureCellContextImprint(key)

// "context" is mandatory for context-imprint mode
let encrypted_message = scell.encrypt(message, context)
let decrypted_message = scell.decrypt(encrypted_message, context)

Secure Session

Secure Session is a sequence- and session- dependent, stateful messaging system. It is suitable for protecting long-lived peer-to-peer message exchanges where the secure data exchange is tied to a specific session context.

Secure Session operates in two stages:

  • session negotiation where the keys are established and cryptographic material is exchanged to generate ephemeral keys,
  • and data exchange where exchanging of messages can be carried out between peers.

You can read a more detailed description of the process here.

Put simply, Secure Session takes the following form:

  • Both clients and server construct a Secure Session object, providing:
    • an arbitrary identifier,
    • a private key, and
    • a callback function that enables it to acquire the public key of the peers with which they may establish communication.
  • A client will generate a "connect request" and by whatever means it will dispatch that to the server.
  • A server will enter a negotiation phase in response to a client's "connect request".
  • Clients and servers will exchange messages until a "connection" is established.
  • Once a connection is established, clients and servers may exchange secure messages according to whatever application-level protocol was chosen.

Secure Session interface

class SecureSession {
    constructor(sessionID, privateKey, keyCallback)
    destroy()
    established()
    connectionRequest()
    negotiateReply(message)
    wrap(message)
    unwrap(message)
}

Description:

  • new themis.SecureSession(sessionID, privateKey, keyCallback)
    Create Secure Session with sessionID, privateKey and a keyCallback.
    The callback is function(peerID) that gets a Uint8Array with remote session ID and should return either the PublicKey of the peer, or null if the peer is unknown.
    Raises ThemisError on failure.

  • destroy()
    Destroy Secure Session and deallocate resources used by it.
    You must call this method when Secure Session is no longer needed.

  • established()
    Returns true if the connection has been established and messages can be exchanged.

  • connectionRequest()
    Initiate connection. This is the first method to be called by the client. Send result to the server.
    Returns Uint8Array with connection initialisation message.
    Raises ThemisError on failure.

  • negotiateReply(message)
    Process connection initialisation or negotiation message. Send result to the peer if established() returns false.
    Returns Uint8Array with connection negotation message.
    Raises ThemisError on failure.

  • wrap(message)
    Encrypts message and returns a Uint8Array with encrypted message.
    Raises ThemisError on failure.

  • unwrap(message)
    Decrypts encrypted message and returns the original one as Uint8Array.
    Raises ThemisError on failure.

Secure Session workflow

Secure Session has two parties called "client" and "server" for the sake of simplicity, but they could be more precisely called "initiator" and "acceptor" — the only difference between them is in who starts the communication.

Secure Session relies on a callback functions to retrieve the keys from local storage (see more in Secure Session cryptosystem description).

Secure Session client

First you need to prepare a session:

let clientSession = new themis.SecureSession(clientID, clientPrivateKey,
    function(peerID) {
        // Get public key for the specified ID from local storage,
        // read it from file, etc.
        if (!found) {
            return null
        }
        return serverPublicKey
    })

var request = clientSession.connectionRequest()
while (!clientSession.established()) {
    // Send "request" to the server, receive "reply"
    request = clientSession.negotiateReply(reply)
}

After the loop finishes, Secure Session is established and is ready to be used.

To encrypt the outgoing message, use:

var encryptedMessage = clientSession.wrap(message)
// Send "encryptedMessage" to the server

To decrypt the received message, use:

// Receive "encryptedMessage" from the server
var message = clientSession.unwrap(encryptedMessage)

Secure Session server

First you need to prepare a session:

let serverSession = new themis.SecureSession(serverID, serverPrivateKey,
    function(peerID) {
        // Get public key for the specified ID from local storage,
        // read it from file, etc.
        if (!found) {
            return null
        }
        return clientPublicKey
    })

while (!serverSession.established()) {
    // Receive "request" from the client
    reply = serverSession.negotiateReply(request)
    // Send "reply" to the client
}

Secure Session is ready. See the full example available in docs/examples/js

Secure Comparator

Secure Comparator is an interactive protocol for two parties that compares whether they share the same secret or not. It is built around a Zero Knowledge Proof-based protocol (Socialist Millionaire's Protocol), with a number of security enhancements.

Secure Comparator is transport-agnostic and only requires the user(s) to pass messages in a certain sequence. The protocol itself is ingrained into the functions and requires minimal integration efforts from the developer.

Secure Comparator interface

class SecureComparator {
    constructor(sharedSecret...)
    destroy()
    append(secret)
    begin()
    proceed(request)
    complete()
    compareEqual()
}

Description:

  • new themis.SecureComparator(sharedSecret...)
    Prepare a new secure comparison of sharedSecrets which are Uint8Arrays of data to compare.
    Specifying multiple secrets is the same as calling append() for them sequentially.
    You must provide at least one byte of data to be compared.
    Raises ThemisError on failure.

  • destroy()
    Destroy Secure Comparator and deallocate resources used by it.
    You must call this method when Secure Comparator is no longer needed.

  • append(secret)
    Append more secret data to be compared before you begin the comparison.
    Raises ThemisError on failure.

  • begin()
    Return a Uint8Array with initial client message. Send it to the server.
    Raises ThemisError on failure.

  • proceed(request)
    Process request and return a Uint8Array with reply. Send it to the peer.
    Raises ThemisError on failure.

  • complete()
    Returns true if the comparison is complete.

  • compareEqual()
    Returns boolean result of the comparison.
    Raises ThemisError is the comparison is not complete.

Secure Comparator workflow

Secure Comparator has two parties — called "client" and "server" — the only difference between them is in who starts the comparison.

Secure Comparator client

let clientComparison = new themis.SecureComparator(new Buffer("shared secret"))

// Client initiates comparison
var request = clientComparison.begin()
while (!clientComparison.complete()) {
    // Send "request" to server and receive "reply"
    request = clientComparison.proceed(reply)
}

After the loop finishes, the comparison is over and you can check if the secrets compareEqual():

if (clientComparison.compareEqual()) {
    // secrets match
} else {
    // secrets don't match
}

Secure Comparator server

The server part can be described in any language, but let's pretend here that both client and server are using JavaScript.

let serverComparison = new themis.SecureComparator(new Buffer("shared secret"))

while (!serverComparison.complete()) {
    // Receive "request" from the client
    reply = serverComparison.proceed(request)
    // Send "reply" to the client
}

After the loop finishes, the comparison is over and you can check if the secrets compareEqual():

if (serverComparison.compareEqual()) {
    // secrets match
} else {
    // secrets don't match
}

This is it. See the full examples in docs/examples/js.