Using Themis in Node.js

Introduction

The Node.js extension provides 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 also example console utils available for the NodeJS wrapper for Themis (as well as for some other wrappers — see the full list here). They help understand the specific mechanics of encryption/decryption processes of this specific wrapper. You can find the example console utils for the NodeJS wrapper here.

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

Supported versions

jsthemis v0.11.0 is tested and supported on Node.js v9+. It depends on nan ^2.8.0 package.

If you need compatibility with older Node.js or nan, please refer to the older jsthemis versions.

Quickstart

Installing stable version from npm

JsThemis extension is available on npm. In order to use the wrapper you still need to have the core library installed.

  1. Install Themis Core as a system library using your system's package manager.

⚠️ IMPORTANT: JsThemis requires core Themis headers to be installed, therefore you need to install the development package: libthemis-dev for Debian and Ubuntu, libthemis-devel for RHEL and CentOS.

  1. Install JsThemis extension via npm: bash npm install jsthemis

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.

⚠️ IMPORTANT: Remember that in addition to the common set of build tools, Themis currently requires either the OpenSSL or LibreSSL package with the developer version of the package (as it provides header files). In either case, we strongly recommend you using the most recent version of these packages.

1. Build and install Themis Core library into your system.
2. Build and install JsThemis extension from source code.

### Importing Themis

Add to your code:

var themis = require('jsthemis')

and you're good to go!

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.

Keypair generation interface

function KeyPair() {
    function private();
    function public();
}
function KeyPair(private, public);

Description:

  • new KeyPair()
    Generates and returns a new Elliptic Curve key pair.
    Raises Error on failure.

  • private()
    Returns private key as Buffer.

  • public()
    Returns public key as Buffer.

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

Example

var key_pair = new themis.KeyPair()
var private_key = key_pair.private().toString("base64")
var public_key = key_pair.public().toString("base64")

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 by using appropriate methods. The sender uses wrap and unwrap 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 sign and verify methods should be used. They only require a private key for signing and a public key for verification respectively.

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

Secure Message interface:

function SecureMessage(private_key, peer_public_key) {
   function encrypt(message);
   function decrypt(message);
   function sign(message);
   function verify(message);
}

Description:

  • new SecureMessage(private_key, peer_public_key)
    Create a new Secure Message with private_key and peer_public_key.
    Raises Error on failure.

  • encrypt(message)
    Encrypt message, return encrypted Secure Message container as Buffer.
    Requires both private_key and peer_public_key to be set.
    Raises Error on failure.

  • decrypt(message)
    Decrypt message, return decrypted original message as Buffer.
    Requires both private_key and peer_public_key to be set.
    Raises Error on failure.

  • sign(message)
    Sign message, return signed Secure Message container as Buffer.
    Requires private_key to be set.
    Raises Error on failure.

  • verify(message)
    Verify message signature, return Buffer with original message.
    Requires peer_public_key to be set. Peer public key should be from the same keypair as private key.
    Raises Error on failure.

Example

Initialise encrypter:

var smessage = new themis.SecureMessage(private_key, peer_public_key)
// or
var smessage = new themis.SecureMessage(new Buffer(private_key, "base64"),
                                        new Buffer(peer_public_key, "base64"))

For signing/verifying make sure that you use keys from the same keypair – private key for signing message and public key for verifying message.

Encrypt message:

try {
    var encrypted_message = smessage.encrypt(message)
} catch (error) {
   // handle error
}

Decrypt message:

try {
   var decrypted_message = smessage.decrypt(encrypted_message)
} catch (error) {
   // handle error
}

Sign message:

try {
   var signed_message = smessage.sign(message)
} catch (error) {
   // handle error
}

Verify message:

try {
   var message = smessage.verify(signed_message)
} catch (error) {
   // handle error
}

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:
function SecureCellSeal(key) {
   function encrypt(message, [context]);
   function decrypt(message, [context]);
}

function SecureCellTokenProtect(key) {
   function encrypt(message, [context]);
   function decrypt(message, token, [context]);
}

function SecureCellContextImprint(key) {
   function encrypt(message, context);
   function decrypt(message, context);
}

Description:

  • new SecureCellSeal(key)
    Initialise Secure Cell in seal mode with master key Buffer (must be non-empty).

  • encrypt(message, [context])
    Encrypt message with additional context (optional argument).
    Return encrypted Buffer.
    Raises Error on failure.

  • decrypt(message, [context])
    Decrypt message with additional context (optional argument).
    Return decrypted Buffer.
    Raises Error on failure.

  • SecureCellTokenProtect(key)
    Initialise Secure Cell in token-protect mode with master key Buffer (must be non-empty).

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

  • decrypt(message, token, [context])
    Decrypt message with token and additional context (optional argument).
    Return decrypted Buffer.
    Raises Error on failure.

  • SecureCellContextImprint(key) Initialise Secure Cell in context-imprint mode with master key Buffer (must be non-empty).

  • encrypt(message, context)
    Encrypt message with mandatory context.
    Return encrypted Buffer.
    Raises Error on failure.

  • decrypt(message, context)
    Decrypt message with mandatory context.
    Return decrypted Buffer.
    Raises Error on failure.

Examples

Secure Cell seal mode

var scell = new themis.SecureCellSeal(key)

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

Secure Cell token-protect mode:

var scell = new SecureCellTokenProtect(key)

var encrypted_array  = scell.encrypt(message, context)
var encrypted_message = encrypted_array.data
var encrypted_token = encrypted_array.token

var decrypted_message = scell.decrypt(encrypted_message, encrypted_token, context)

Secure Cell context-imprint mode:

var scell = new themis.SecureCellContextImprint(key)

// "context" is mandatory for context-imprint mode
var encrypted_message = scell.encrypt(message, context)
var 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

function SecureSession(id, private_key, get_pub_key_by_id) {
  function isEstablished();
  function connectRequest();
  function wrap(message);
  function unwrap(message);
}

Description:

  • new SecureSession(id, private_key, get_pub_key_by_id)
    Initialise Secure Session with peer id, private_key and a get_pub_key_by_id callback.
    The callback is function(peer_id) that gets Buffer with remote peer ID and should returns public key for it (or null).
    Raises Error on failure.

  • isEstablished()
    Checks if the connection has been established.

  • connectRequest()
    Return connection initialisation message as a Buffer. Send it to the server.
    Raises Error on failure.

  • wrap(message)
    Encrypts message and returns encrypted Buffer.
    Raises Error on failure.

  • unwrap(message)
    Decrypts message.
    Returns Buffer that is either decrypted message (if the connection is established), or a connection message that must be sent as is to the peer.
    Raises Error 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 the user's passing a number of callback functions to send/receive messages — and the keys are retrieved from local storage (see more in Secure Session cryptosystem description).

Secure Session client

First, initialisation:

var client_session = new themis.SecureSession(client_id, client_keypair.private(),
  function(id) {
      // Get public key for specified ID from file, database, etc.
      return public_key
  });

var data = client_session.connectRequest()
do {
    // send "data" to server, receive "response"
    data = client_session.unwrap(response)
} while (!client_session.isEstablished());

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

To encrypt the outgoing message use:

var encrypted_message = client_session.wrap(message)
// send "encrypted_message" by any preferred method

To decrypt the received message, use:

// receive "encrypted_message" from peer 
var message = client_session.unwrap(encrypted_message)

Secure Session server

First, initialise everything:

var server_session = new themis.SecureSession(server_id, server_keypair.private(),
  function(id) {
      // Get public key for specified ID from file, database, etc.
      return public_key
  });

while (!server_session.isEstablished()) {
    // receive "request" from client
    reply = server_session.unwrap(request)
    // send "reply" to 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

function SecureComparator(sharedSecret) {
  function beginCompare();
  function proceedCompare(message);
  function isCompareComplete();
  function isMatch();
}

Description:

  • new SecureComparator(shared_secret)
    Initialises secure comparison for provided shared_secret.
    Raises Error on failure.

  • beginCompare()
    Returns an initial message for the client, send it to the server.
    Raises Error on failure.

  • proceedCompare(message)
    Processes message and returns a reply to send to the peer.
    Raises Error on failure.

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

  • isMatch()
    Returns boolean result of the comparison.

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

var client = new themis.SecureComparator(new Buffer("shared secret"))

// Client initiates comparison
var data = client.beginCompare()

while (!client.isCompareComplete()) {
    // Send data to server and receive response
    sendDataToServer(data)
    data = receiveFromServer()

    // Process response and prepare reply
    data = client.proceedCompare(data)
}

After the loop finishes, the comparison is over and its result can be checked by calling isMatch():

if (client.isMatch()) {
    // 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.

var server = new themis.SecureComparator(new Buffer("shared secret"))

// Receive request, process it, and send back reply (until completion)
while (!server.isCompareComplete()) {
    var data = receiveFromClient()
    data = server.proceedCompare(data)
    sendToClient(data)
}

After the loop finishes, the comparison is over and its result can be checked by calling isMatch:

if (server.isMatch()) {
    // secrets match
} else {
    // secrets don't match
}

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