diff --git a/src/mod_sso/mod_sso.c b/src/mod_sso/mod_sso.c
index 20c5b7b027dbc03cb17991ab52d02b50bc037fe7..d2a3490fab831fb5e9c735d9ed36e2809fd7fffa 100644
--- a/src/mod_sso/mod_sso.c
+++ b/src/mod_sso/mod_sso.c
@@ -61,6 +61,12 @@ typedef struct {
   // Note: public_key is a binary buffer (non zero-terminated).
   const unsigned char *public_key;
 
+  // Encryption keys are fixed-size binary buffers too.
+  const unsigned char *encryption_public_key;
+  const unsigned char *local_encryption_public_key;
+  char *local_encryption_public_key_base64;
+  const unsigned char *local_encryption_secret_key;
+
   // Same for the session_key (not fixed size though, so we store the
   // size as well).
   const unsigned char *session_key;
@@ -70,6 +76,10 @@ typedef struct {
   apr_array_header_t *groups;
 } modsso_config;
 
+static int modsso_config_ticket_encryption_enabled(modsso_config *s_cfg) {
+  return (s_cfg->encryption_public_key && s_cfg->local_encryption_public_key && s_cfg->local_encryption_secret_key);
+}
+
 typedef const char *(*CMD_HAND_TYPE)();
 
 static char *groups_array_to_commasep_string(apr_pool_t *p,
@@ -105,6 +115,10 @@ static void *create_modsso_config(apr_pool_t *p, char *s) {
   newcfg->service = NULL;
   newcfg->domain = NULL;
   newcfg->public_key = NULL;
+  newcfg->encryption_public_key = NULL;
+  newcfg->local_encryption_public_key = NULL;
+  newcfg->local_encryption_public_key_base64 = NULL;
+  newcfg->local_encryption_secret_key = NULL;
   newcfg->session_key = NULL;
   newcfg->session_key_len = 0;
   newcfg->groups = NULL;
@@ -122,10 +136,16 @@ static void *merge_modsso_config(apr_pool_t *p, void *base, void *add) {
       cadd->login_server ? cadd->login_server : cbase->login_server;
   newcfg->service = cadd->service ? cadd->service : cbase->service;
   newcfg->domain = cadd->domain ? cadd->domain : cbase->domain;
-  newcfg->public_key = cbase->public_key;
-  if (cadd->public_key) {
-    newcfg->public_key = cadd->public_key;
+  newcfg->public_key = cadd->public_key ? cadd->public_key : cbase->public_key;
+  newcfg->encryption_public_key = cadd->encryption_public_key ? cadd->encryption_public_key : cbase->encryption_public_key;
+  newcfg->local_encryption_secret_key = cadd->local_encryption_secret_key ? cadd->local_encryption_secret_key : cbase->local_encryption_secret_key;
+  newcfg->local_encryption_public_key = cbase->local_encryption_public_key;
+  newcfg->local_encryption_public_key_base64 = cbase->local_encryption_public_key_base64;
+  if (cadd->local_encryption_public_key) {
+    newcfg->local_encryption_public_key = cadd->local_encryption_public_key;
+    newcfg->local_encryption_public_key_base64 = cadd->local_encryption_public_key_base64;
   }
+
   newcfg->session_key = cbase->session_key;
   newcfg->session_key_len = cbase->session_key_len;
   if (cadd->session_key) {
@@ -163,46 +183,59 @@ static const char *set_modsso_domain(cmd_parms *parms, void *mconfig,
 static const char *set_modsso_public_key_file(cmd_parms *parms, void *mconfig,
                                               const char *arg) {
   modsso_config *s_cfg = (modsso_config *)mconfig;
-  char buf[128];
-  apr_size_t n = sizeof(buf);
-  apr_file_t *file;
-  int status;
 
-  if (apr_file_open(&file, arg, APR_FOPEN_READ, 0, parms->pool) !=
-      APR_SUCCESS) {
-    return "Could not open SSOPublicKeyFile";
+  if (modsso_read_fixed_size_file(parms->pool, arg, SSO_PUBLIC_KEY_SIZE, &s_cfg->public_key) < 0) {
+    return "Could not read SSOPublicKeyFile";
   }
-  status = apr_file_read(file, (void *)buf, &n);
-  apr_file_close(file);
-  if (status != APR_SUCCESS) {
-    return "Could not read contents of SSOPublicKeyFile";
+  return NULL;
+}
+
+static const char *set_modsso_session_key_file(cmd_parms *parms, void *mconfig,
+                                               const char *arg) {
+  modsso_config *s_cfg = (modsso_config *)mconfig;
+
+  if (modsso_read_fixed_size_file(parms->pool, arg, MODSSO_SESSION_KEY_SIZE, &s_cfg->session_key) < 0) {
+    return "Could not read SSOSessionKeyFile";
   }
+  s_cfg->session_key_len = MODSSO_SESSION_KEY_SIZE;
+  return NULL;
+}
 
-  unsigned char *key = (unsigned char *)apr_palloc(parms->pool, n);
-  memcpy(key, buf, n);
-  s_cfg->public_key = key;
+static const char *set_modsso_encryption_public_key_file(cmd_parms *parms, void *mconfig, const char *arg) {
+  modsso_config *s_cfg = (modsso_config *)mconfig;
 
+  if (modsso_read_fixed_size_file(parms->pool, arg, SSO_TICKET_ENCRYPTION_PUBLIC_KEY_SIZE, &s_cfg->encryption_public_key) < 0) {
+    return "Could not read SSOEncryptionPublicKeyFile";
+  }
   return NULL;
 }
 
-static const char *set_modsso_session_key_file(cmd_parms *parms, void *mconfig,
-                                               const char *arg) {
+static const char *set_modsso_local_encryption_public_key_file(cmd_parms *parms, void *mconfig, const char *arg) {
   modsso_config *s_cfg = (modsso_config *)mconfig;
-  unsigned char *session_key = NULL;
-  size_t session_key_len = MODSSO_SESSION_KEY_SIZE;
+  size_t sz;
 
-  session_key = (unsigned char *)apr_palloc(parms->pool, session_key_len);
-  if (modsso_session_read_key_from_file(parms->pool, arg, session_key,
-                                        &session_key_len) < 0) {
-    return "Could not open SSOSessionKeyFile";
+  if (modsso_read_fixed_size_file(parms->pool, arg, SSO_TICKET_ENCRYPTION_PUBLIC_KEY_SIZE, &s_cfg->local_encryption_public_key) < 0) {
+    return "Could not read SSOLocalEncryptionPublicKeyFile";
   }
 
-  s_cfg->session_key = session_key;
-  s_cfg->session_key_len = session_key_len;
+  sz = SSO_TICKET_ENCRYPTION_PUBLIC_KEY_SIZE * 2;
+  s_cfg->local_encryption_public_key_base64 = apr_palloc(parms->pool, sz);
+  if (sso_serialize_ticket_encryption_public_key(s_cfg->local_encryption_public_key, s_cfg->local_encryption_public_key_base64, sz) < 0) {
+    return "Failure to base64-encode the SSOLocalEncryptionPublicKeyFile";
+  }
 
   return NULL;
 }
 
+static const char *set_modsso_local_encryption_secret_key_file(cmd_parms *parms, void *mconfig, const char *arg) {
+  modsso_config *s_cfg = (modsso_config *)mconfig;
+
+  if (modsso_read_fixed_size_file(parms->pool, arg, SSO_TICKET_ENCRYPTION_SECRET_KEY_SIZE, &s_cfg->local_encryption_secret_key) < 0) {
+    return "Could not read SSOEncryptionSecretKeyFile";
+  }
+  return NULL;
+}
+
 static const command_rec mod_sso_cmds[] = {
     AP_INIT_TAKE1("SSOLoginServer", (CMD_HAND_TYPE)set_modsso_login_server,
                   NULL, OR_ALL,
@@ -215,6 +248,18 @@ static const command_rec mod_sso_cmds[] = {
         "SSOPublicKeyFile", (CMD_HAND_TYPE)set_modsso_public_key_file, NULL,
         RSRC_CONF,
         "SSOPublicKeyFile (string) Location of the login server public key"),
+    AP_INIT_TAKE1(
+        "SSOEncryptionPublicKeyFile", (CMD_HAND_TYPE)set_modsso_encryption_public_key_file, NULL,
+        RSRC_CONF,
+        "SSOEncryptionPublicKeyFile (string) Location of the login server SSO ticket encryption public key"),
+    AP_INIT_TAKE1(
+        "SSOLocalEncryptionPublicKeyFile", (CMD_HAND_TYPE)set_modsso_local_encryption_public_key_file, NULL,
+        RSRC_CONF,
+        "SSOLocalEncryptionPublicKeyFile (string) Location of the local SSO ticket encryption public key"),
+    AP_INIT_TAKE1(
+        "SSOLocalEncryptionSecretKeyFile", (CMD_HAND_TYPE)set_modsso_local_encryption_secret_key_file, NULL,
+        RSRC_CONF,
+        "SSOLocalEncryptionSecretKeyFile (string) Location of the local SSO ticket encryption secret key"),
     AP_INIT_TAKE1(
         "SSOSessionKeyFile", (CMD_HAND_TYPE)set_modsso_session_key_file, NULL,
         RSRC_CONF,
@@ -385,9 +430,49 @@ static int check_config(request_rec *r, modsso_config *s_cfg) {
                  "sso: SSOPublicKeyFile is not defined!");
     return 0;
   }
+  if ((s_cfg->encryption_public_key || s_cfg->local_encryption_public_key || s_cfg->local_encryption_secret_key)
+      && !(s_cfg->encryption_public_key && s_cfg->local_encryption_public_key && s_cfg->local_encryption_secret_key)) {
+    ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
+                 "sso: all of SSOEncryptionPublicKey, SSOLocalEncryptionPublicKey and SSOLocalEncryptionSecretKey must be defined to enable ticket encryption!");
+  }
   return 1;
 }
 
+static int modsso_get_params_from_request(modsso_config *s_cfg, request_rec *r, struct modsso_params *params) {
+  if (modsso_parse_query_string(r->pool, r->args, params) < 0) {
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+                 "sso: get_params_from_request: invalid query parameters");
+    return -1;
+  }
+
+  // If we have a local encryption key
+  if (modsso_config_ticket_encryption_enabled(s_cfg)) {
+    char ticket_buf[512];
+
+    if (!params->et || !params->et[0]) {
+      ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+                   "sso: request is missing the 'et' query parameter");
+      return -1;
+    }
+
+    if (sso_ticket_decrypt(params->et, s_cfg->encryption_public_key, s_cfg->local_encryption_secret_key, ticket_buf, sizeof(ticket_buf)) < 0) {
+      ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+                   "sso: could not decrypt ticket from request");
+      return -1;
+    }
+
+    params->t = apr_pstrdup(r->pool, ticket_buf);
+  } else {
+    if (!params->t || !params->t[0]) {
+      ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+                   "sso: request is missing the 't' query parameter");
+      return -1;
+    }
+  }
+  
+  return 0;
+}
+
 /**
  * Apache method handler for mod_sso.
  *
@@ -452,8 +537,8 @@ static int mod_sso_method_handler(request_rec *r) {
       return HTTP_BAD_REQUEST;
     }
 
-    // Parse query params
-    if (modsso_parse_query_string(r->pool, r->args, &params) < 0) {
+    // Parse query params, including decrypting the ticket if necessary.
+    if (modsso_get_params_from_request(s_cfg, r, &params) < 0) {
       ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
                    "sso: invalid parameters for sso_login: %s", r->args);
       return HTTP_BAD_REQUEST;
@@ -530,6 +615,9 @@ static int redirect_to_login_server(request_rec *r, modsso_config *s_cfg,
                           unique_id, sso_login_path);
     }
   }
+  if (modsso_config_ticket_encryption_enabled(s_cfg)) {
+    login_url = apr_pstrcat(r->pool, login_url, "&k=", s_cfg->local_encryption_public_key_base64, NULL);
+  }
   if (groups) {
     login_url =
         apr_pstrcat(r->pool, login_url,
diff --git a/src/mod_sso/mod_sso.h b/src/mod_sso/mod_sso.h
index 5bad1038d85b551d0425efb7ebc4056babd8fb12..b3f6dd67c4b907fffb1c6a60de37d6ca6205e764 100644
--- a/src/mod_sso/mod_sso.h
+++ b/src/mod_sso/mod_sso.h
@@ -25,6 +25,7 @@
 #include "ap_config.h"
 #include "apr_strings.h"
 #include <sso/sso.h>
+#include <sso/sso_encrypt.h>
 
 #ifdef APLOG_USE_MODULE
 APLOG_USE_MODULE(sso);
@@ -44,6 +45,7 @@ struct request_rec;
 
 struct modsso_params {
   char *t;
+  char *et;
   char *d;
 };
 
@@ -63,6 +65,9 @@ void modsso_set_cookie(request_rec *r, const char *cookie_name,
 
 void modsso_del_cookie(request_rec *r, const char *cookie_name);
 
+int modsso_read_fixed_size_file(apr_pool_t *pool, const char *path,
+                                size_t size, const unsigned char **out);
+
 // session.c
 int modsso_session_read_key_from_file(apr_pool_t *pool, const char *path,
                                       unsigned char *out, size_t *outsz);
diff --git a/src/mod_sso/sso_utils.c b/src/mod_sso/sso_utils.c
index 0977d459f02d7031ef50fd161b7734445d9f6f1d..5f9f49caec03ed426ba8824254221914a688b0ce 100644
--- a/src/mod_sso/sso_utils.c
+++ b/src/mod_sso/sso_utils.c
@@ -22,16 +22,18 @@
  * OTHER DEALINGS IN THE SOFTWARE.
  */
 
+#include <ctype.h>
+
+#include "ap_config.h"
+#include "apr_strings.h"
 #include "httpd.h"
+
 #include "http_config.h"
 #include "http_core.h"
 #include "http_log.h"
-#include "apr_strings.h"
-#include "http_protocol.h"
 #include "http_main.h"
-#include "ap_config.h"
+#include "http_protocol.h"
 #include "mod_sso.h"
-#include <ctype.h>
 
 // Ugly implementation of url decoding. Returns a newly allocated
 // string (using malloc).
@@ -40,48 +42,48 @@ char *modsso_url_decode(apr_pool_t *p, const char *i) {
   char state = 'N';
   char buf[3], obuf[2];
   for (o = output; *i; i++) {
-    switch(state) {
+    switch (state) {
     case 'N':
-      if (*i == '%') state = 1;
-      else *o++ = *i;
+      if (*i == '%')
+        state = 1;
+      else
+        *o++ = *i;
+      break;
+    case 1: {
+      char c = tolower(*i);
+      buf[0] = *i;
+      if ((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9')) {
+        state = 2;
+      } else {
+        *o++ = '%';
+        *o++ = *i;
+        state = 'N';
+      }
+      break;
+    }
+    case 2: {
+      char c = tolower(*i);
+      if ((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9')) {
+        buf[1] = *i;
+        buf[2] = 0;
+        obuf[0] = strtol(buf, 0, 16);
+        obuf[1] = 0;
+        if (obuf[0])
+          *o++ = obuf[0];
+      } else {
+        *o++ = '%';
+        *o++ = buf[0];
+        *o++ = *i;
+      }
+      state = 'N';
       break;
-    case 1:
-       {
-         char c = tolower(*i);
-         buf[0] = *i;
-         if ((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9')) {
-           state = 2;
-         } else {
-           *o++ = '%';
-           *o++ = *i;
-           state = 'N';
-         }
-         break;
-       }
-    case 2:
-       {
-         char c = tolower(*i);
-         if ((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9')) {
-           buf[1] = *i;
-           buf[2] = 0;
-           obuf[0] = strtol(buf, 0, 16);
-           obuf[1] = 0;
-           if (obuf[0])
-              *o++ = obuf[0];
-         } else {
-           *o++ = '%';
-           *o++ = buf[0];
-           *o++ = *i;
-         }
-         state = 'N';
-         break;
-       }
+    }
     }
   }
   if (state != 'N') {
     *o++ = buf[0];
     if (state > 1)
-       *o++ = buf[1];
+      *o++ = buf[1];
   }
   *o = '\0';
   return output;
@@ -105,11 +107,13 @@ char *modsso_url_encode(apr_pool_t *p, const char *s) {
   return out;
 }
 
-int modsso_parse_query_string(apr_pool_t *p, const char *str, modsso_params_t params) {
+int modsso_parse_query_string(apr_pool_t *p, const char *str,
+                              modsso_params_t params) {
   char *tmp = apr_pstrdup(p, str), *strptr = NULL;
 
   params->d = NULL;
   params->t = NULL;
+  params->et = NULL;
 
   if (str == NULL) {
     return 0;
@@ -136,31 +140,30 @@ int modsso_parse_query_string(apr_pool_t *p, const char *str, modsso_params_t pa
       params->d = value;
     } else if (!strcmp(token, "t")) {
       params->t = value;
+    } else if (!strcmp(token, "et")) {
+      params->et = value;
     }
   }
 
-  if (!params->d || !params->d[0]
-      || !params->t || !params->t[0]) {
+  // Both 'd' and one of 't' or 'et' must be set.
+  if (!params->d || !params->d[0] ||
+      ((!params->t || !params->t[0]) && (!params->et || !params->et[0]))) {
     return -1;
   }
 
   return 0;
 }
 
-static char *make_cookie_value(request_rec *r,
-                               const char *name,
-                               const char *session_id,
-                               const char *path, 
-                               int cookie_lifespan)
-{
+static char *make_cookie_value(request_rec *r, const char *name,
+                               const char *session_id, const char *path,
+                               int cookie_lifespan) {
   size_t sz = 1024;
   char *cookie_value = (char *)apr_palloc(r->pool, sz);
 
   if (cookie_lifespan == 0) {
-    
-    snprintf(cookie_value, sz,
-             "%s=%s; path=%s; secure; httpOnly;",
-             name, session_id, path);
+
+    snprintf(cookie_value, sz, "%s=%s; path=%s; secure; httpOnly;", name,
+             session_id, path);
   } else {
     char expires[200];
     time_t t;
@@ -168,8 +171,7 @@ static char *make_cookie_value(request_rec *r,
     t = time(NULL) + cookie_lifespan;
     tmp = gmtime(&t);
     strftime(expires, sizeof(expires), "%a, %d-%b-%Y %H:%M:%S GMT", tmp);
-    snprintf(cookie_value, sz,
-             "%s=%s; expires=%s; path=%s; secure; httpOnly;",
+    snprintf(cookie_value, sz, "%s=%s; expires=%s; path=%s; secure; httpOnly;",
              name, session_id, expires, path);
   }
   return cookie_value;
@@ -185,7 +187,7 @@ static char *make_cookie_value(request_rec *r,
 char *modsso_get_cookie(request_rec *r, const char *cookie_name) {
   char *rv = NULL, *cookies, *cookie, *tokenizerCtx = NULL;
   int cookie_name_len = strlen(cookie_name);
-  
+
   const char *cookies_c = apr_table_get(r->headers_in, "Cookie");
   if (cookies_c == NULL) {
     return NULL;
@@ -198,7 +200,8 @@ char *modsso_get_cookie(request_rec *r, const char *cookie_name) {
   while (cookie) {
     while (*cookie == ' ')
       cookie++;
-    if (strncmp(cookie, cookie_name, cookie_name_len) == 0 && cookie[cookie_name_len] == '=') {
+    if (strncmp(cookie, cookie_name, cookie_name_len) == 0 &&
+        cookie[cookie_name_len] == '=') {
       cookie += (cookie_name_len + 1);
       rv = apr_pstrdup(r->pool, cookie);
       break;
@@ -208,20 +211,43 @@ char *modsso_get_cookie(request_rec *r, const char *cookie_name) {
   return rv;
 }
 
-void modsso_set_cookie(request_rec *r, const char *cookie_name, 
-                       const char *value, const char *path)
-{
+void modsso_set_cookie(request_rec *r, const char *cookie_name,
+                       const char *value, const char *path) {
   char *cookie_value;
   cookie_value = make_cookie_value(r, cookie_name, value, path, 0);
-  apr_table_setn(r->err_headers_out, "Set-Cookie", 
+  apr_table_setn(r->err_headers_out, "Set-Cookie",
                  apr_pstrdup(r->pool, cookie_value));
 }
 
-void modsso_del_cookie(request_rec *r, const char *cookie_name)
-{
+void modsso_del_cookie(request_rec *r, const char *cookie_name) {
   char cookie_value[512];
   sprintf(cookie_value, "%s=; expires=Mar, 01-01-1971 00:00:00 GMT",
           cookie_name);
   apr_table_setn(r->err_headers_out, "Set-Cookie",
                  apr_pstrdup(r->pool, cookie_value));
 }
+
+int modsso_read_fixed_size_file(apr_pool_t *pool, const char *path, size_t size,
+                                const unsigned char **out) {
+  char *m = NULL;
+  int status;
+  apr_file_t *file;
+  apr_size_t n;
+
+  if (apr_file_open(&file, path, APR_FOPEN_READ, 0, pool) != APR_SUCCESS)
+    goto fail;
+
+  n = size;
+  m = apr_palloc(pool, n);
+  status = apr_file_read(file, m, &n);
+  apr_file_close(file);
+  if (status != APR_SUCCESS || n != size)
+    goto fail;
+
+  *out = (unsigned char *)m;
+  return 0;
+
+fail:
+  // apr_pfree(pool, m);
+  return -1;
+}
diff --git a/src/sso/Makefile.am b/src/sso/Makefile.am
index deb9bcf47d1be17e9b4b5a0ce24f68dbbb88da61..56b1646e21f39fd4a0ba1858f0ac39ef59155d52 100644
--- a/src/sso/Makefile.am
+++ b/src/sso/Makefile.am
@@ -8,6 +8,7 @@ ssotool_LDADD = libsso.la
 
 libsso_la_SOURCES = \
 	sso.c sso.h \
+	sso_encrypt.c sso_encrypt.h \
 	base64.c \
 	randombytes.c \
 	tweetnacl.c tweetnacl.h
diff --git a/src/sso/sso_encrypt.c b/src/sso/sso_encrypt.c
new file mode 100644
index 0000000000000000000000000000000000000000..df1e8c865b40835d74eec6ae0fc63f3b4bd81a16
--- /dev/null
+++ b/src/sso/sso_encrypt.c
@@ -0,0 +1,124 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sso.h"
+#include "tweetnacl.h"
+
+extern void randombytes(unsigned char *, unsigned long long);
+
+int sso_generate_encryption_keys(unsigned char *publicp,
+                                 unsigned char *secretp) {
+  return crypto_box_keypair(publicp, secretp);
+}
+
+int sso_serialize_ticket_encryption_public_key(const unsigned char *public_key,
+                                               char *out, size_t out_sz) {
+  size_t sz = out_sz;
+  return sso_base64_encode((unsigned char *)out, &sz, public_key,
+                           crypto_box_PUBLICKEYBYTES);
+}
+
+int sso_ticket_encrypt(const char *serialized_ticket,
+                       const char *serialized_pub_key,
+                       const unsigned char *secret_key, char *out,
+                       size_t out_sz) {
+  unsigned char pub_key[crypto_box_PUBLICKEYBYTES];
+  unsigned char n[crypto_box_NONCEBYTES];
+  unsigned char *m = NULL, *c = NULL;
+  int serialized_ticket_len;
+  int mlen;
+  int ret = -1;
+  size_t sz, enc_sz, nonce_sz, pub_sz;
+
+  // Decode the public key (base64-encoded).
+  pub_sz = crypto_box_PUBLICKEYBYTES;
+  ret = sso_base64_decode(pub_key, &pub_sz, (unsigned char *)serialized_pub_key,
+                          strlen(serialized_pub_key));
+  if (ret < 0)
+    return ret;
+
+  // Encrypt the serialized ticket string.
+  serialized_ticket_len = strlen(serialized_ticket);
+  mlen = serialized_ticket_len + crypto_box_ZEROBYTES;
+  c = malloc(mlen);
+  m = malloc(mlen);
+  memset(m, 0, crypto_box_ZEROBYTES);
+  memcpy(m + crypto_box_ZEROBYTES, serialized_ticket, serialized_ticket_len);
+  randombytes(n, crypto_box_NONCEBYTES);
+
+  crypto_box(c, m, mlen, n, pub_key, secret_key);
+
+  // Build the output string by concatenating the nonce and the
+  // encrypted message, each base64-encoded, separated by a dot.
+  nonce_sz = out_sz;
+  ret = sso_base64_encode((unsigned char *)out, &nonce_sz, n,
+                          crypto_box_NONCEBYTES);
+  if (ret < 0)
+    goto fail;
+
+  out[nonce_sz++] = '.';
+  sz = out_sz - nonce_sz;
+  enc_sz = mlen - crypto_box_BOXZEROBYTES;
+  ret = sso_base64_encode((unsigned char *)(out + nonce_sz), &sz,
+                          c + crypto_box_BOXZEROBYTES, enc_sz);
+
+fail:
+  free(m);
+  free(c);
+  return ret;
+}
+
+int sso_ticket_decrypt(const char *encrypted, const unsigned char *public_key,
+                       const unsigned char *secret_key, char *out,
+                       size_t out_sz) {
+  const char *nonce, *enc_msg;
+  unsigned char n[crypto_box_NONCEBYTES];
+  unsigned char *c = NULL, *m = NULL;
+  int nonce_b64len, enc_msg_b64len, clen;
+  size_t nonce_sz, enc_msg_sz;
+  int ret;
+
+  nonce = encrypted;
+  enc_msg = strchr(encrypted, '.');
+  if (!enc_msg)
+    return -1;
+  nonce_b64len = enc_msg - nonce;
+  enc_msg++;
+  enc_msg_b64len = strlen(enc_msg);
+
+  nonce_sz = crypto_box_NONCEBYTES;
+  ret = sso_base64_decode(n, &nonce_sz, (unsigned char *)nonce, nonce_b64len);
+  if (ret < 0)
+    goto fail;
+  if (nonce_sz != crypto_box_NONCEBYTES) {
+    ret = -1;
+    goto fail;
+  }
+
+  c = malloc(crypto_box_BOXZEROBYTES + enc_msg_b64len);
+  memset(c, 0, crypto_box_BOXZEROBYTES);
+  enc_msg_sz = enc_msg_b64len;
+  ret = sso_base64_decode(c + crypto_box_BOXZEROBYTES, &enc_msg_sz,
+                          (unsigned char *)enc_msg, enc_msg_b64len);
+  if (ret < 0)
+    goto fail;
+
+  clen = crypto_box_BOXZEROBYTES + enc_msg_sz;
+  m = malloc(clen);
+
+  ret = crypto_box_open(m, c, clen, n, public_key, secret_key);
+  if (ret < 0)
+    goto fail;
+
+  memcpy(out, m + crypto_box_ZEROBYTES, clen - crypto_box_ZEROBYTES);
+  out[clen - crypto_box_ZEROBYTES] = '\0';
+
+fail:
+  if (c)
+    free(c);
+  if (m)
+    free(m);
+  return ret;
+}
diff --git a/src/sso/sso_encrypt.h b/src/sso/sso_encrypt.h
new file mode 100644
index 0000000000000000000000000000000000000000..d5c6e2c1d497c51f03ecb78b9a18a31a4b14def1
--- /dev/null
+++ b/src/sso/sso_encrypt.h
@@ -0,0 +1,30 @@
+#ifndef __sso_encrypt_H
+#define __sso_encrypt_H 1
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Must match tweetnacl.h */
+#define SSO_TICKET_ENCRYPTION_PUBLIC_KEY_SIZE 32
+#define SSO_TICKET_ENCRYPTION_SECRET_KEY_SIZE 32
+
+int sso_generate_encryption_keys(unsigned char *publicp,
+                                 unsigned char *secretp);
+int sso_serialize_ticket_encryption_public_key(const unsigned char *public_key,
+                                               char *out, size_t out_sz);
+int sso_ticket_encrypt(const char *serialized_ticket,
+                       const char *serialized_pub_key,
+                       const unsigned char *secret_key, char *out,
+                       size_t out_sz);
+int sso_ticket_decrypt(const char *encrypted, const unsigned char *public_key,
+                       const unsigned char *secret_key, char *out,
+                       size_t out_sz);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/sso/test/Makefile.am b/src/sso/test/Makefile.am
index 56096ad0c5699abb1efb3f60372ef066c3cbb761..65f7cf6fcc561fef073e6fd82b9bed862c907b7a 100644
--- a/src/sso/test/Makefile.am
+++ b/src/sso/test/Makefile.am
@@ -2,6 +2,7 @@ include $(top_srcdir)/Makefile.defs
 
 check_PROGRAMS = \
 	sso_unittest \
+	sso_encrypt_unittest \
 	tweetnacl_unittest
 
 TESTS = $(check_PROGRAMS)
@@ -11,4 +12,6 @@ LDADD = $(GTEST_LIBS) ../libsso.la
 
 sso_unittest_SOURCES = sso_unittest.cc
 
+sso_encrypt_unittest_SOURCES = sso_encrypt_unittest.cc
+
 tweetnacl_unittest_SOURCES = tweetnacl_unittest.cc
diff --git a/src/sso/test/sso_encrypt_unittest.cc b/src/sso/test/sso_encrypt_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..14101db3e58537c911ce753141daa0e85328873e
--- /dev/null
+++ b/src/sso/test/sso_encrypt_unittest.cc
@@ -0,0 +1,63 @@
+#include <gtest/gtest.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sso.h"
+#include "sso_encrypt.h"
+
+extern "C" {
+#include "tweetnacl.h"
+}
+
+using std::string;
+
+namespace {
+
+class Encryption : public testing::Test {
+protected:
+  virtual void SetUp() { sso_generate_encryption_keys(public_key, secret_key); }
+
+  unsigned char public_key[crypto_box_PUBLICKEYBYTES];
+  unsigned char secret_key[crypto_box_SECRETKEYBYTES];
+};
+
+TEST_F(Encryption, EncryptAndDecrypt) {
+  const char *serialized_ticket = "hello world I am a ticket";
+  char serialized_pubkey[64];
+  char buf[512];
+  char out[512];
+
+  ASSERT_EQ(0, sso_serialize_ticket_encryption_public_key(
+                   public_key, serialized_pubkey, sizeof(serialized_pubkey)));
+  printf("serialized public key: %s (%lu bytes)\n", serialized_pubkey,
+         strlen(serialized_pubkey));
+  ASSERT_EQ(0, sso_ticket_encrypt(serialized_ticket, serialized_pubkey,
+                                  secret_key, buf, sizeof(buf)));
+  printf("encrypted ticket: %s\n", buf);
+  ASSERT_EQ(0,
+            sso_ticket_decrypt(buf, public_key, secret_key, out, sizeof(out)));
+
+  ASSERT_EQ(string(out), string(serialized_ticket));
+}
+
+TEST_F(Encryption, DecryptFailsWithBadData) {
+  const char *not_valid[] = {
+      "abcdefgh", "", "something.else", "dGVzdDE=.dGVzdDI=", NULL,
+  };
+  char out[1024];
+  int i;
+
+  for (i = 0; not_valid[i]; i++) {
+    ASSERT_LT(sso_ticket_decrypt(not_valid[i], public_key, secret_key, out,
+                                 sizeof(out)),
+              0)
+        << "no error decrypting: " << not_valid[i];
+  }
+}
+
+} // namespace
+
+int main(int argc, char **argv) {
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}