OpenSSLNTRU

This demo was announced 2020.04.16 on the pqc-forum mailing list, updated 2020.04.23 from OpenSSL 1.1.1f to OpenSSL 1.1.1g, updated 2021.06.08 from OpenSSL 1.1.1g to OpenSSL 1.1.1k, including additional support for sntrup857, updated 2021.09.30 from OpenSSL 1.1.1k to OpenSSL 1.1.1l, alongside an update of the instructions to use stunnel 5.60 and glib-networking 2.60.4, updated 2021.11.02 to cover usage of tls_timer and suggestions regarding its use for experiments, and updated 2021.12.14 from OpenSSL 1.1.1l to OpenSSL 1.1.1m.

Our patches work for versions of OpenSSL from 1.1.1f to 1.1.1m.


This is a demo of OpenSSLNTRU web browsing taking just 156317 Haswell cycles to generate a new one-time sntrup761 public key for each TLS 1.3 session. This demo uses

  1. the Gnome web browser (client) and stunnel (server) using
  2. a patched version of OpenSSL 1.1.1m using
  3. a new OpenSSL ENGINE using
  4. a fast new sntrup761 library.

The TLS 1.3 integration in OpenSSLNTRU uses the same basic data flow as the CECPQ2 experiment carried out by Google and Cloudflare. Compared to the cryptography in CECPQ2, the cryptography in OpenSSLNTRU has a higher security level and better performance. Furthermore, OpenSSLNTRU's new software layers decouple the fast-moving post-quantum software ecosystem from the TLS software ecosystem. OpenSSLNTRU also supports a second NTRU Prime parameter set, sntrup857, optimizing computation costs at an even higher security level.

Demo overview

Warning: This demo comes with no cryptographic warranties and no other security warranties. The software here is experimental, and is built upon other software with a long history of security problems, such as OpenSSL. The purpose of this demo is purely to show the sntrup761 and sntrup857 performance achievable with a CECPQ2-type data flow for TLS 1.3.

The demo has two parts: a server side and a client side. We recommend running each side in its own VM.

The server side uses stunnel for SSL termination. It receives TLS connections, including sntrup761 and sntrup857 connections, and passes along the answers provided by a preexisting back-end web server, which does not need to support sntrup761/sntrup857 connections. For example, the demo site https://test761.cr.yp.to looks just like the preexisting site https://ntruprime.cr.yp.to, but with the extra feature of supporting sntrup761 connections. Internally, https://test761.cr.yp.to passes requests along through a local connection to the preexisting back-end web server for ntruprime.cr.yp.to. You can use https://test761.cr.yp.to as the server side of this demo, or you can set up the server side for a web server of your choice.

The client side uses Epiphany, the Gnome web browser, with no modifications to the Epiphany source code. The glib-networking library used inside Epiphany already supports OpenSSL as an option for outgoing connections, and is configured below to use this option.

Both sides use a version of OpenSSL 1.1.1m patched inside libssl to support sntrup761 and sntrup857 respectively as experimental groups 0xfe00 and 0xfe01 for TLS 1.3. We also provide a separate optional patch for libcrypto to include a reference implementation of sntrup761. Our new engntru library then overrides this reference implementation providing a faster implementation, which in turn is built on top of our new libsntrup761. engntru also provides access to a fast implementation of sntrup857 built on top of our new libsntrup857. This way of using the OpenSSL ENGINE feature allows OpenSSL to take advantage of fast software implementations while allowing those implementations to be developed in separate libraries; see https://eprint.iacr.org/2018/354.

Various other applications that use OpenSSL have been verified to transparently work with libsntrup761 and libsntrup857 via engntru. This demo focuses on stunnel on the server side and Epiphany on the client side.

Server side

The following instructions for setting up the server side have been tested in a VM running Debian 11 (Bullseye) on a CPU supporting AVX2. You can skip down to the client side if you simply want to try https://test761.cr.yp.to as the server.

As root:

    apt install -y wget python3 build-essential clang cmake pkg-config
    adduser --disabled-password --gecos opensslntru opensslntru

As the new opensslntru user (change the first three lines for your own demo server name, demo server address, and preexisting back-end server address—of course, you should use your favorite VPN to protect the connection from this SSL terminator to the back-end server):

    EXTERNALNAME=test761.cr.yp.to
    EXTERNALADDRESS=1.2.3.4:65024  # provide TLS service on this address
    INTERNALADDRESS=5.6.7.8:80     # use existing server on this address

    export OPENSSLNTRU_PREFIX=$HOME/local
    export PATH=$OPENSSLNTRU_PREFIX/bin:$PATH

    cd
    wget https://www.openssl.org/source/openssl-1.1.1m.tar.gz
    wget https://opensslntru.cr.yp.to/openssl-1.1.1m-ntru.patch
    # Following patch is optional, to embed a reference
    # implementation of sntrup761 in libcrypto:
    # wget https://opensslntru.cr.yp.to/openssl-1.1.1m-ntru-add_ref_sntrup761.patch
    tar -xf openssl-1.1.1m.tar.gz
    mv openssl-1.1.1m openssl-1.1.1m-ntru
    cd openssl-1.1.1m-ntru
    patch -p1 < ../openssl-1.1.1m-ntru.patch
    # Optionally, to embed a portable reference implementation
    # of sntrup761 in libcrypto:
    # patch -p1 < ../openssl-1.1.1m-ntru-add_ref_sntrup761.patch
    ./config shared --prefix=$OPENSSLNTRU_PREFIX --openssldir=$OPENSSLNTRU_PREFIX -Wl,-rpath=$OPENSSLNTRU_PREFIX/lib
    make update
    make -j8   # a few minutes
    make test  # more minutes
    make install_sw

    cd
    wget https://opensslntru.cr.yp.to/libsntrup761-20210608.tar.gz
    tar -xf libsntrup761-20210608.tar.gz
    cd libsntrup761-20210608
    env CPATH="${OPENSSLNTRU_PREFIX}/include" LIBRARY_PATH="${OPENSSLNTRU_PREFIX}/lib" make USE_RPATH=RUNPATH OPENSSL_PREFIX="${OPENSSLNTRU_PREFIX}" PREFIX="${OPENSSLNTRU_PREFIX}" all test install

    cd
    wget https://opensslntru.cr.yp.to/libsntrup857-20210608.tar.gz
    tar -xf libsntrup857-20210608.tar.gz
    cd libsntrup857-20210608
    env CPATH="${OPENSSLNTRU_PREFIX}/include" LIBRARY_PATH="${OPENSSLNTRU_PREFIX}/lib" make USE_RPATH=RUNPATH OPENSSL_PREFIX="${OPENSSLNTRU_PREFIX}" PREFIX="${OPENSSLNTRU_PREFIX}" all test install

    cd
    wget https://opensslntru.cr.yp.to/engntru-20210608.tar.gz
    tar -xf engntru-20210608.tar.gz
    cd engntru-20210608
    mkdir build
    cd build
    cmake -DCMAKE_BUILD_TYPE=Release -Dprovider="batch_lib" -DCMAKE_PREFIX_PATH="${OPENSSLNTRU_PREFIX}" ..
    make
    make install

    cd
    wget https://www.stunnel.org/archive/5.x/stunnel-5.60.tar.gz
    tar -xf stunnel-5.60.tar.gz
    cd stunnel-5.60
    ./configure --prefix="${OPENSSLNTRU_PREFIX}" --with-ssl="${OPENSSLNTRU_PREFIX}" LDFLAGS=-Wl,-rpath="${OPENSSLNTRU_PREFIX}/lib"
    make
    make install

    cd
    mkdir service
    cd service
    openssl req -x509 -sha256 -nodes -newkey rsa:2048 -keyout "$EXTERNALNAME.key" -days 730 -out "$EXTERNALNAME.crt" -subj "/CN=$EXTERNALNAME" -config /etc/ssl/openssl.cnf
    (
      echo "key = $EXTERNALNAME.key"
      echo "cert = $EXTERNALNAME.crt"
      echo 'foreground = yes'
      echo 'engine = engntru'
      echo 'engineDefault = ALL'
      echo '[forward]'
      echo "accept = $EXTERNALADDRESS"
      echo "connect = $INTERNALADDRESS"
      echo 'curves = SNTRUP761:SNTRUP857:X25519:P-256'
      echo 'config = MinProtocol:TLSv1.2'
      echo 'ciphers = ECDHE+CHACHA20:ECDHE+AES256:ECDHE+AES128:!aNULL:!eNULL:!LOW:!EXPORT:!DES:!3DES:!RC4:!MD5:!PSK:!SRP:!DSS:!aECDSA'
    ) > stunnel.conf

As root:

    (
      echo '[Unit]'
      echo 'Description=opensslntru forwarding'
      echo 'DefaultDependencies=no'
      echo 'After=network.target'
      echo ''
      echo '[Service]'
      echo 'Type=simple'
      echo 'User=opensslntru'
      echo 'Group=opensslntru'
      echo 'WorkingDirectory=/home/opensslntru/service'
      echo 'ExecStart=/home/opensslntru/local/bin/stunnel stunnel.conf'
      echo ''
      echo '[Install]'
      echo 'WantedBy=default.target'
    ) > /etc/systemd/system/opensslntru.service
    ln -s /etc/systemd/system/opensslntru.service /etc/systemd/system/multi-user.target.wants
    systemctl restart opensslntru

At this point the server should be working. Try any browser to connect to the server's external address. The certificate is self-signed; signing it with Let's Encrypt is recommended but is outside the scope of these instructions.

This stunnel configuration passes SNI along from the client to the server, so the client is free to access any server name provided by the server. For example, almost all *.cr.yp.to are hosted on the same back-end server and can now be retrieved through sntrup761, although for the moment this is announced to the client (and signed) only for test761.cr.yp.to. You can advertise multiple names on the same server through the same stunnel configuration by adding those names to DNS and creating an appropriate certificate. You can instead configure stunnel to forward different SNI choices to different servers with different certificates.

Client side

The following instructions for setting up the client side have been tested in a VM running Debian 10 (Buster) on a CPU supporting AVX2.

As root:

    apt install -y wget python3 build-essential clang cmake \
      pkg-config epiphany-browser meson gnome-pkg-tools \
      libglib2.0-dev libproxy-dev \
      gsettings-desktop-schemas-dev ca-certificates
    adduser --disabled-password --gecos opensslntru opensslntru

As the new opensslntru user:

    export OPENSSLNTRU_PREFIX=$HOME/local
    export PATH=$OPENSSLNTRU_PREFIX/bin:$PATH

    cd
    wget https://www.openssl.org/source/openssl-1.1.1m.tar.gz
    wget https://opensslntru.cr.yp.to/openssl-1.1.1m-ntru.patch
    # Following patch is optional, to embed a reference
    # implementation of sntrup761 in libcrypto:
    # wget https://opensslntru.cr.yp.to/openssl-1.1.1m-ntru-add_ref_sntrup761.patch
    tar -xf openssl-1.1.1m.tar.gz
    mv openssl-1.1.1m openssl-1.1.1m-ntru
    cd openssl-1.1.1m-ntru
    patch -p1 < ../openssl-1.1.1m-ntru.patch
    # Optionally, to embed a portable reference implementation
    # of sntrup761 in libcrypto:
    # patch -p1 < ../openssl-1.1.1m-ntru-add_ref_sntrup761.patch
    ./config shared --prefix=$OPENSSLNTRU_PREFIX --openssldir=$OPENSSLNTRU_PREFIX -Wl,-rpath=$OPENSSLNTRU_PREFIX/lib
    make update
    make -j8   # a few minutes
    make test  # more minutes
    make install_sw

    cd
    wget https://opensslntru.cr.yp.to/libsntrup761-20210608.tar.gz
    tar -xf libsntrup761-20210608.tar.gz
    cd libsntrup761-20210608
    env CPATH="${OPENSSLNTRU_PREFIX}/include" LIBRARY_PATH="${OPENSSLNTRU_PREFIX}/lib" make USE_RPATH=RUNPATH OPENSSL_PREFIX="${OPENSSLNTRU_PREFIX}" PREFIX="${OPENSSLNTRU_PREFIX}" all test install

    cd
    wget https://opensslntru.cr.yp.to/libsntrup857-20210608.tar.gz
    tar -xf libsntrup857-20210608.tar.gz
    cd libsntrup857-20210608
    env CPATH="${OPENSSLNTRU_PREFIX}/include" LIBRARY_PATH="${OPENSSLNTRU_PREFIX}/lib" make USE_RPATH=RUNPATH OPENSSL_PREFIX="${OPENSSLNTRU_PREFIX}" PREFIX="${OPENSSLNTRU_PREFIX}" all test install

    cd
    wget https://opensslntru.cr.yp.to/engntru-20210608.tar.gz
    tar -xf engntru-20210608.tar.gz
    cd engntru-20210608
    mkdir build
    cd build
    cmake -DCMAKE_BUILD_TYPE=Debug -Dprovider="batch_lib" -DCMAKE_PREFIX_PATH="${OPENSSLNTRU_PREFIX}" ..
    make
    make install

    cd
    git clone --branch 2.60.4 https://gitlab.gnome.org/GNOME/glib-networking.git
    cd glib-networking
    mkdir build
    cd build
    env PKG_CONFIG_PATH=$OPENSSLNTRU_PREFIX/lib/pkgconfig CPATH=$OPENSSLNTRU_PREFIX/include LIBRARY_PATH=$OPENSSLNTRU_PREFIX/lib meson --prefix=$OPENSSLNTRU_PREFIX -Dopenssl=enabled -Dgnutls=disabled ..
    env CPATH=$OPENSSLNTRU_PREFIX/include ninja
    ninja install

    cd
    wget https://opensslntru.cr.yp.to/tls_timer-20210608.tar.gz
    tar xf tls_timer-20210608.tar.gz
    cd tls_timer-20210608
    env PKG_CONFIG_PATH=$OPENSSLNTRU_PREFIX/lib/pkgconfig make PREFIX=$OPENSSLNTRU_PREFIX all install

    cd
    (
      echo 'openssl_conf = openssl_init'
      echo ''
      echo '[openssl_init]'
      echo 'ssl_conf = ssl_sect'
      echo 'engines = engine_section'
      echo ''
      echo '[ssl_sect]'
      echo 'system_default = system_default_sect'
      echo ''
      echo '[system_default_sect]'
      echo 'Groups = SNTRUP761:SNTRUP857:X25519:P-256'
      echo ''
      echo '[engine_section]'
      echo 'engntru = engntru_section'
      echo ''
      echo '[engntru_section]'
      echo 'engine_id = engntru'
      echo 'default_algorithms = ALL'
      echo 'init = 1'
    ) > openssl-engntru.cnf
    export OPENSSL_CONF=$HOME/openssl-engntru.cnf
    export LD_LIBRARY_PATH=$OPENSSLNTRU_PREFIX/lib
    export GIO_MODULE_DIR=$OPENSSLNTRU_PREFIX/lib/x86_64-linux-gnu/gio/modules
    export ENGNTRU_DEBUG=4  # to watch engntru activating
    ln -s /etc/ssl/certs $OPENSSLNTRU_PREFIX/certs

    epiphany https://test761.cr.yp.to

You should be able to browse to this demo server (using sntrup761 or sntrup857), whichever other demo servers you set up above (using sntrup761 or sntrup857), and other sites (typically not using sntrup761/sntrup857 yet). The ENGNTRU_DEBUG=4 log information in the terminal includes a message for each sntrup761/sntrup857 keygen, a message for each sntrup761/sntrup857 dec, and a message for each computation of a batch of 32 keys.

Alternatively on the client you can use tls_timer to measure the wall-clock execution time of a number of sequential TLS connections using different TLS groups: tls_timer -help shows a short usage message and lists available options. Common usage of tls_timer will be similar to: tls_timer -n 1024 -connect server:12345 -groups SNTRUP761 where the first parameter sets the number of consecutive connections to benchmark, the second parameter indicates the address and port of the TLS terminator (stunnel in this demo) and the value of the -groups option restricts the client to only use SNTRUP761 as the group for the key exchange. Alternative groups of interest include SNTRUP857, P-256 and X25519.

The client side instructions for the demo recommend using -DCMAKE_BUILD_TYPE=Debug when building engntru. This configuration is useful to observe individual key requests, decapsulations and batch computations setting ENGNTRU_DEBUG=4 as detailed above. The verbose logging information can affect measurements, so when running tls_timer to collect measures to be used in comparisons we recommend at least setting ENGNTRU_DEBUG=0. Although our experiments seem to suggest that the differences between Debug and Release builds of engntru have negligible effects on the collected measurements (as engntru is a shallow ENGINE and the actual cryptographic operations are provided via libsntrup761/libsntrup857, which are unaffected by the build profile used for engntru), we would further recommend to recompile (and reinstall) engntru on the client side using -DCMAKE_BUILD_TYPE=Release for fairer comparisons.

The output of tls_timer is formatted in JSON, and the key names should be self-explanatory, for example:

    opensslntru@client:~$ tls_timer -n 1024 -connect terminator:44443 -groups SNTRUP761
    { "connections": 1024, "wallclock_usec": 1960119, "key_type": "SNTRUP761", "key_bits": 9264 }
    opensslntru@client:~$ tls_timer -n 1024 -connect terminator:44443 -groups P-256
    { "connections": 1024, "wallclock_usec": 2280968, "key_type": "P-256", "key_bits": 256 }
    opensslntru@client:~$ tls_timer -n 1024 -connect terminator:44443 -groups X25519
    { "connections": 1024, "wallclock_usec": 2218708, "key_type": "X25519", "key_bits": 253 }

Notice that SNTRUP857 is available only through engntru (which is loaded by the configuration file indicated by the OPENSSL_CONF environment variable), while a reference implementation of SNTRUP761 would be included in the libcrypto installed following these instructions if the corresponding optional patch was enabled. To disable automatic loading of engntru, use your favorite editor to comment the line engntru = engntru_section prepending a # at the very beginning of the line. These instructions should allow to use tls_timer to compare our implementation of SNTRUP761 against the reference version optionally included in libcrypto, to SNTRUP857, and to popular optimized implementations of pre-quantum ECC primitives such as P-256 and X25519. Notice that tls_timer measures full end-to-end TLS connections, including a new key for each connection on the client side, encapsulation on the server side, decapsulation on the client side, validation against a traditional pre-quantum certificate, and communication over the network. An important caveat in setting up experiments and interpreting the data is that fast local network can mask most of the communication costs. See the full paper for more details.


Version: This is version 2021.12.14 of the "Demo" web page.