Skip to content

Go HowTo

Alexei Lozovsky edited this page Mar 29, 2019 · 22 revisions

Using Themis with Go

Introduction

The gothemis package provides access to the features and functions of the Themis cryptographic library:

  • Key generation: the creation of public/private key pairs, used in Secure Message and Secure Session.
  • Secure Message: the 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: the establishment of a session between two peers, within which the data can be securely exchanged with higher security guarantees. EC + ECDH, AES container.
  • Secure Comparator: compare the secret between two parties without leaking anything related to the secret: Zero-Knowledge Proof-based authentication system. Hardened Socialist Millionaire Protocol + ed25519.

You can learn more about the Themis library in our general documentation.

There are also example console utils available for the Go 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 Go wrapper here.

Supported versions

GoThemis is tested and supported on Go 1.9+

Quickstart

Installing stable version from packages

The easiest way to install Themis is to use package managers.

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

    ⚠️ IMPORTANT: GoThemis 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.

  2. Use go get to install GoThemis wrapper:

    go get github.com/cossacklabs/themis/gothemis/...

Switching version of GoThemis

If you have proper Go installation and GOPATH set, visit the GoThemis package in GOPATH and check out the specific branch. For example, to get the latest stable version of GoThemis:

cd $GOPATH/src/github.com/cossacklabs/themis/gothemis
git checkout stable

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.

  1. Build and install Themis Core library into your system.
  2. Use go get -u to install the latest version of GoThemis wrapper:
    go get -u github.com/cossacklabs/themis/gothemis/...

Importing Themis into your project

Add relevant modules to your code:

import "github.com/cossacklabs/themis/gothemis/cell"
import "github.com/cossacklabs/themis/gothemis/compare"
import "github.com/cossacklabs/themis/gothemis/keys"
import "github.com/cossacklabs/themis/gothemis/message"
import "github.com/cossacklabs/themis/gothemis/session"

and you're good to go!

Examples

  • gothemis test files are simple self-describing and easy-to-understand examples of our APIs usage scenarios. Feel free to explore them.

  • More code samples for Themis objects are available in the docs/examples/go 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 needed 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.

Generating key pairs

You can generate key pairs like this:

import "github.com/cossacklabs/themis/gothemis/keys"

keyPair, err := keys.New(keys.TypeEC) /* or keys.TypeRSA */
priv := keyPair.Private
pub := keyPair.Public

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).

You can read more about Secure Message's cryptographic internals.

Processing messages

  1. Create SecureMessage object with your PrivateKey and recipient's PublicKey:
import "github.com/cossacklabs/themis/gothemis/message"

// for encryption & decryption
encryptor := message.New(yourPrivateKey, peerPublicKey)

// for signing messages
signer := message.New(yourPrivateKey, nil)

// for signature verification
verifier := message.New(nil, peerPublicKey)
  1. Process each outgoing message:
encryptedMessage, err := encryptor.Wrap(messageToSend)
signedMessage, err := signer.Sign(messageToSign)
  1. Process each incoming message:
receivedMessage, err := encryptor.Unwrap(encryptedMessage)
verifiedMessage, err := verifier.Verify(signedMessage)

Secure Cell

The Secure Сell functions provide the means of protection for arbitrary data contained in stores, such as 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 filename) 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 using the authentication data is to validate 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 your application. The Secure Cell functions offer variants that address this issue in different ways.

The encryption algorithm used by Secure Cell (by default) is AES-256. The length of the generated authentication data is 16 bytes.

Secure Cell is available in 3 modes:

  • Seal mode: the mode that is the most secure and easy to use. This is your best choice most of the time.
  • Token protect mode: the mode that is the most secure and easy to use. Also your best choice most of the time.
  • Context imprint mode: length-preserving version of Secure Cell with no additional data stored. Should be used carefully.

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

Initialising Secure Cell

Create SecureCell object to protect your data:

import "github.com/cossacklabs/themis/gothemis/cell"

secureCell := cell.New(secretKey, cell.ModeSeal)
// or in other modes:
//   - cell.ModeTokenProtect
//   - cell.ModeContextImprint

🧩 NOTE: Read more about Secure Cell modes and which to choose here.

Secure Cell Seal Mode

Initialise cell:

secureCell := cell.New(secretKey, cell.ModeSeal)

Encrypt:

// context may be "nil"
protectedData, _, err := secureCell.Protect(data, context)

Second result _ is additional data that is always nil in this mode.

Decrypt:

The context should be same as in the protect function call for successful decryption (and may be nil).

data, err := secureCell.Unprotect(protectedData, nil, context)

Secure Cell Token-Protect Mode

Initialise cell:

secureCell := cell.New(secretKey, cell.ModeTokenProtect)

Encrypt:

// context may be "nil"
protectedData, additionalData, err := secureCell.Protect(data, context)

In this mode result has additional data (which is opaque to the user, but is necessary for successful decryption).

Decrypt:

The context should be same as in the protect function call for successful decryption.

data, err := secureCell.Unprotect(protectedData, additionalData, context)
Secure Cell Context-Imprint Mode

Initialise cell:

secureCell := cell.New(secretKey, cell.ModeContextImprint)

Encrypt:

// context is *required* in context-imprint mode
protectedData, _, err := secureCell.Protect(data, context)

Second parameter _ is additional data that is nil in this mode.

Decrypt:

The context should be same as in the protect function call for successful decryption.

data, err := secureCell.Unprotect(protectedData, nil, 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 bound 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 the 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 Workflow

Secure Session has two parties that are 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).

Using Secure Session

  1. Implement session.SessionCallbacks interface:
  • GetPublicKeyForId which will return peer's trusted public key when needed by the system
  • StateChanged is just a notification callback. You may use it for informational purpose, to update your UI or just have a dummy (do-nothing) implementation
import "github.com/cossacklabs/themis/gothemis/session"
import "github.com/cossacklabs/themis/gothemis/keys"

type callbacks struct {
        // ...
}

func (clb *callbacks) GetPublicKeyForId(ss *session.SecureSession, id []byte) (*keys.PublicKey) {
        pub := getPublicKeyFromDatabaseOrSomeOtherStorageOrSource(id)

        return pub // or nil if key was not found
}

func (clb *callbacks) StateChanged(ss *session.SecureSession, state int) {
        // Do something if you wish.
        // state constants:
        //   - session.StateIdle
        //   - session.StateNegotiating
        //   - session.StateEstablished
}
  1. Create SecureSession object:
session, err := session.New(yourId, yourPrivateKey, &callbacks{})
  1. On the client side, initiate Secure Session negotiation by generating and sending connection request:
connectRequest, err = session.ConnectRequest();
// send connectRequest to the server
  1. Start receiving and parsing incoming data on both sides:
// receive some data and store it in receiveBuffer

// "receiveBuffer" contains encrypted data from your peer,
// try decrypting it...
data, sendPeer, err := session.Unwrap(receiveBuffer)
if err != nil {
        // handle error
}
if sendPeer {
        // "receiveBuffer" is a part of the negotiation protocol:
        // so "data" contains the response to this protocol,
        // which needs to be forwarded to your peer as is.

        // Just send "data" to your peer.
} else {
        // "data" may be nil on the client when the Secure Session
        // completes connection negotiation.
        if data != nil {
                // Now "data" contains decrypted data.
        
                // Process "data" according to your application.
        }
}
  1. When the protocol negotiation ends, you may send encrypted data to your peer:
wrappedData, err := session.Wrap(yourData);
// send wrappedData to your peer

Please refer to the Secure Session test and Secure Session Examples to get the complete vision how Secure Session works.

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 workflow

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

Secure Comparator client

import "github.com/cossacklabs/themis/gothemis/compare"

// create secure comparator and append secret
scomparator, err := compare.New()
err = scomparator.Append(sharedSecret) // some byte[]

// Client initiates secure comparison.
buffer, err := scomparator.Begin()

for {
        // Stop if the comparison is ready.
        res, err := scomparator.Result()
        if res != compare.NotReady {
                break
        }

        // Send "buffer" to the server and receive "reply".

        // Proceed with comparison...
        buffer, err = scomparator.Proceed(reply)
}

res, err := scomparator.Result()
if err != nil {
        // handle failed comparison
}
if res == compare.Match {
        fmt.Println("match")
} else {
        fmt.Println("not match")
}

After the loop finishes, the comparison is over and its result is checked calling scomparator.Result().

Secure Comparator server

Server part can be described in any language, let's pretend that both client and server are using Go.

import "github.com/cossacklabs/themis/gothemis/compare"

// create secure comparator and append secret
scomparator, err := compare.New()
err = scomparator.Append(sharedSecret) // byte[]

// The server does not initiate connection.

for {
        // Stop if the comparison is ready.
        res, err := scomparator.Result()
        if res != compare.NotReady {
                break
        }

        // Receive "buffer" from the client.

        // Proceed with comparison...
        reply, err = scomparator.Proceed(buffer)

        // Send "reply" back to the client, if not empty.
}

res, err := scomparator.Result()
if err != nil {
        // handle failed comparison
}
if res == compare.Match {
        fmt.Println("match")
} else {
        fmt.Println("not match")
}

After the loop finishes the comparison is over and its result is checked calling scomparator.Result().

Please refer to the Secure Comparator Examples to get the complete vision how Secure Comparator works.

Clone this wiki locally
You can’t perform that action at this time.