diff --git a/.travis.yml b/.travis.yml
index a89bd8e3fca927741f3f77424ea12a7fd1a39eb8..06a95396f81ca367f6f5fef2e907dd8e66f7e281 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -46,9 +46,12 @@ matrix:
             - "g++-7"
 
 install:
+  - if [ $TRAVIS_OS_NAME == osx ]; then brew update && brew install boost libsodium; fi
+
   - if [ $TRAVIS_OS_NAME == linux ]; then sudo add-apt-repository -y ppa:george-edison55/cmake-3.x; fi
+  - if [ $TRAVIS_OS_NAME == linux ]; then sudo add-apt-repository -y ppa:chris-lea/libsodium; fi
   - if [ $TRAVIS_OS_NAME == linux ]; then sudo apt-get update -qq; fi
-  - if [ $TRAVIS_OS_NAME == linux ]; then sudo apt-get install -qq -y cmake; fi
+  - if [ $TRAVIS_OS_NAME == linux ]; then sudo apt-get install -qq -y cmake libsodium-dev; fi
 
 script:
   - if [[ "${CXX_VERSION}" != "" ]]; then export CXX=${CXX_VERSION}; fi
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d40308465d656205e5d4b0fe5a4c5b833115a457..692124e59d8259afeaf45e3c8dd3b86c69fbee46 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -44,6 +44,13 @@ set(MTXCLIENT_DEPS "")
 find_package(OpenSSL REQUIRED)
 include_directories(${OPENSSL_INCLUDE_DIR})
 
+#
+# libsodium
+#
+include(Findsodium)
+set(MTXCLIENT_LIBS ${MTXCLIENT_LIBS} ${sodium_LIBRARY_RELEASE})
+include_directories(SYSTEM sodium_INCLUDE_DIR)
+
 #
 # Boost 1.66
 #
@@ -84,7 +91,8 @@ endif()
 
 include_directories(SYSTEM ${MATRIX_STRUCTS_INCLUDE_DIR})
 
-set(MTXCLIENT_LIBS matrix_structs
+set(MTXCLIENT_LIBS ${MTXCLIENT_LIBS}
+    matrix_structs
     ${Boost_LIBRARIES}
     ${OPENSSL_LIBRARIES}
     ${ZLIB_LIBRARIES})
diff --git a/cmake/Findsodium.cmake b/cmake/Findsodium.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..30e6f5c1d166e392db5e1cdf67848d61598b49d0
--- /dev/null
+++ b/cmake/Findsodium.cmake
@@ -0,0 +1,288 @@
+# Written in 2016 by Henrik Steffen Gaßmann <henrik@gassmann.onl>
+#
+# To the extent possible under law, the author(s) have dedicated all
+# copyright and related and neighboring rights to this software to the
+# public domain worldwide. This software is distributed without any warranty.
+#
+# You should have received a copy of the CC0 Public Domain Dedication
+# along with this software. If not, see
+#
+#     http://creativecommons.org/publicdomain/zero/1.0/
+#
+########################################################################
+# Tries to find the local libsodium installation.
+#
+# On Windows the sodium_DIR environment variable is used as a default
+# hint which can be overridden by setting the corresponding cmake variable.
+#
+# Once done the following variables will be defined:
+#
+#   sodium_FOUND
+#   sodium_INCLUDE_DIR
+#   sodium_LIBRARY_DEBUG
+#   sodium_LIBRARY_RELEASE
+#
+#
+# Furthermore an imported "sodium" target is created.
+#
+
+if (CMAKE_C_COMPILER_ID STREQUAL "GNU"
+    OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
+    set(_GCC_COMPATIBLE 1)
+endif()
+
+# static library option
+if (NOT DEFINED sodium_USE_STATIC_LIBS)
+    option(sodium_USE_STATIC_LIBS "enable to statically link against sodium" OFF)
+endif()
+if(NOT (sodium_USE_STATIC_LIBS EQUAL sodium_USE_STATIC_LIBS_LAST))
+    unset(sodium_LIBRARY CACHE)
+    unset(sodium_LIBRARY_DEBUG CACHE)
+    unset(sodium_LIBRARY_RELEASE CACHE)
+    unset(sodium_DLL_DEBUG CACHE)
+    unset(sodium_DLL_RELEASE CACHE)
+    set(sodium_USE_STATIC_LIBS_LAST ${sodium_USE_STATIC_LIBS} CACHE INTERNAL "internal change tracking variable")
+endif()
+
+
+########################################################################
+# UNIX
+if (UNIX)
+    # import pkg-config
+    find_package(PkgConfig QUIET)
+    if (PKG_CONFIG_FOUND)
+        pkg_check_modules(sodium_PKG QUIET libsodium)
+    endif()
+
+    if(sodium_USE_STATIC_LIBS)
+        foreach(_libname ${sodium_PKG_STATIC_LIBRARIES})
+            if (NOT _libname MATCHES "^lib.*\\.a$") # ignore strings already ending with .a
+                list(INSERT sodium_PKG_STATIC_LIBRARIES 0 "lib${_libname}.a")
+            endif()
+        endforeach()
+        list(REMOVE_DUPLICATES sodium_PKG_STATIC_LIBRARIES)
+
+        # if pkgconfig for libsodium doesn't provide
+        # static lib info, then override PKG_STATIC here..
+        if (sodium_PKG_STATIC_LIBRARIES STREQUAL "")
+            set(sodium_PKG_STATIC_LIBRARIES libsodium.a)
+        endif()
+
+        set(XPREFIX sodium_PKG_STATIC)
+    else()
+        if (sodium_PKG_LIBRARIES STREQUAL "")
+            set(sodium_PKG_LIBRARIES sodium)
+        endif()
+
+        set(XPREFIX sodium_PKG)
+    endif()
+
+    find_path(sodium_INCLUDE_DIR sodium.h
+        HINTS ${${XPREFIX}_INCLUDE_DIRS}
+    )
+    find_library(sodium_LIBRARY_DEBUG NAMES ${${XPREFIX}_LIBRARIES}
+        HINTS ${${XPREFIX}_LIBRARY_DIRS}
+    )
+    find_library(sodium_LIBRARY_RELEASE NAMES ${${XPREFIX}_LIBRARIES}
+        HINTS ${${XPREFIX}_LIBRARY_DIRS}
+    )
+
+
+########################################################################
+# Windows
+elseif (WIN32)
+    set(sodium_DIR "$ENV{sodium_DIR}" CACHE FILEPATH "sodium install directory")
+    mark_as_advanced(sodium_DIR)
+
+    find_path(sodium_INCLUDE_DIR sodium.h
+        HINTS ${sodium_DIR}
+        PATH_SUFFIXES include
+    )
+
+    if (MSVC)
+        # detect target architecture
+        file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/arch.c" [=[
+            #if defined _M_IX86
+            #error ARCH_VALUE x86_32
+            #elif defined _M_X64
+            #error ARCH_VALUE x86_64
+            #endif
+            #error ARCH_VALUE unknown
+        ]=])
+        try_compile(_UNUSED_VAR "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/arch.c"
+            OUTPUT_VARIABLE _COMPILATION_LOG
+        )
+        string(REGEX REPLACE ".*ARCH_VALUE ([a-zA-Z0-9_]+).*" "\\1" _TARGET_ARCH "${_COMPILATION_LOG}")
+
+        # construct library path
+        if (_TARGET_ARCH STREQUAL "x86_32")
+            string(APPEND _PLATFORM_PATH "Win32")
+        elseif(_TARGET_ARCH STREQUAL "x86_64")
+            string(APPEND _PLATFORM_PATH "x64")
+        else()
+            message(FATAL_ERROR "the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake.")
+        endif()
+        string(APPEND _PLATFORM_PATH "/$$CONFIG$$")
+
+        if (MSVC_VERSION LESS 1900)
+            math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 60")
+        else()
+            math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 50")
+        endif()
+        string(APPEND _PLATFORM_PATH "/v${_VS_VERSION}")
+
+        if (sodium_USE_STATIC_LIBS)
+            string(APPEND _PLATFORM_PATH "/static")
+        else()
+            string(APPEND _PLATFORM_PATH "/dynamic")
+        endif()
+
+        string(REPLACE "$$CONFIG$$" "Debug" _DEBUG_PATH_SUFFIX "${_PLATFORM_PATH}")
+        string(REPLACE "$$CONFIG$$" "Release" _RELEASE_PATH_SUFFIX "${_PLATFORM_PATH}")
+
+        find_library(sodium_LIBRARY_DEBUG libsodium.lib
+            HINTS ${sodium_DIR}
+            PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}
+        )
+        find_library(sodium_LIBRARY_RELEASE libsodium.lib
+            HINTS ${sodium_DIR}
+            PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}
+        )
+        if (NOT sodium_USE_STATIC_LIBS)
+            set(CMAKE_FIND_LIBRARY_SUFFIXES_BCK ${CMAKE_FIND_LIBRARY_SUFFIXES})
+            set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
+            find_library(sodium_DLL_DEBUG libsodium
+                HINTS ${sodium_DIR}
+                PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}
+            )
+            find_library(sodium_DLL_RELEASE libsodium
+                HINTS ${sodium_DIR}
+                PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}
+            )
+            set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_BCK})
+        endif()
+
+    elseif(_GCC_COMPATIBLE)
+        if (sodium_USE_STATIC_LIBS)
+            find_library(sodium_LIBRARY_DEBUG libsodium.a
+                HINTS ${sodium_DIR}
+                PATH_SUFFIXES lib
+            )
+            find_library(sodium_LIBRARY_RELEASE libsodium.a
+                HINTS ${sodium_DIR}
+                PATH_SUFFIXES lib
+            )
+        else()
+            find_library(sodium_LIBRARY_DEBUG libsodium.dll.a
+                HINTS ${sodium_DIR}
+                PATH_SUFFIXES lib
+            )
+            find_library(sodium_LIBRARY_RELEASE libsodium.dll.a
+                HINTS ${sodium_DIR}
+                PATH_SUFFIXES lib
+            )
+
+            file(GLOB _DLL
+                LIST_DIRECTORIES false
+                RELATIVE "${sodium_DIR}/bin"
+                "${sodium_DIR}/bin/libsodium*.dll"
+            )
+            find_library(sodium_DLL_DEBUG ${_DLL} libsodium
+                HINTS ${sodium_DIR}
+                PATH_SUFFIXES bin
+            )
+            find_library(sodium_DLL_RELEASE ${_DLL} libsodium
+                HINTS ${sodium_DIR}
+                PATH_SUFFIXES bin
+            )
+        endif()
+    else()
+        message(FATAL_ERROR "this platform is not supported by FindSodium.cmake")
+    endif()
+
+
+########################################################################
+# unsupported
+else()
+    message(FATAL_ERROR "this platform is not supported by FindSodium.cmake")
+endif()
+
+
+########################################################################
+# common stuff
+
+# extract sodium version
+if (sodium_INCLUDE_DIR)
+    set(_VERSION_HEADER "${_INCLUDE_DIR}/sodium/version.h")
+    if (EXISTS _VERSION_HEADER)
+        file(READ "${_VERSION_HEADER}" _VERSION_HEADER_CONTENT)
+        string(REGEX REPLACE ".*#[ \t]*define[ \t]*SODIUM_VERSION_STRING[ \t]*\"([^\n]*)\".*" "\\1"
+            sodium_VERSION "${_VERSION_HEADER_CONTENT}")
+        set(sodium_VERSION "${sodium_VERSION}" PARENT_SCOPE)
+    endif()
+endif()
+
+# communicate results
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(sodium
+    REQUIRED_VARS
+        sodium_LIBRARY_RELEASE
+        sodium_LIBRARY_DEBUG
+        sodium_INCLUDE_DIR
+    VERSION_VAR
+        sodium_VERSION
+)
+
+# mark file paths as advanced
+mark_as_advanced(sodium_INCLUDE_DIR)
+mark_as_advanced(sodium_LIBRARY_DEBUG)
+mark_as_advanced(sodium_LIBRARY_RELEASE)
+if (WIN32)
+    mark_as_advanced(sodium_DLL_DEBUG)
+    mark_as_advanced(sodium_DLL_RELEASE)
+endif()
+
+# create imported target
+if(sodium_USE_STATIC_LIBS)
+    set(_LIB_TYPE STATIC)
+else()
+    set(_LIB_TYPE SHARED)
+endif()
+add_library(sodium ${_LIB_TYPE} IMPORTED)
+
+set_target_properties(sodium PROPERTIES
+    INTERFACE_INCLUDE_DIRECTORIES "${sodium_INCLUDE_DIR}"
+    IMPORTED_LINK_INTERFACE_LANGUAGES "C"
+)
+
+if (sodium_USE_STATIC_LIBS)
+    set_target_properties(sodium PROPERTIES
+        INTERFACE_COMPILE_DEFINITIONS "SODIUM_STATIC"
+        IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}"
+        IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}"
+    )
+else()
+    if (UNIX)
+        set_target_properties(sodium PROPERTIES
+            IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}"
+            IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}"
+        )
+    elseif (WIN32)
+        set_target_properties(sodium PROPERTIES
+            IMPORTED_IMPLIB "${sodium_LIBRARY_RELEASE}"
+            IMPORTED_IMPLIB_DEBUG "${sodium_LIBRARY_DEBUG}"
+        )
+        if (NOT (sodium_DLL_DEBUG MATCHES ".*-NOTFOUND"))
+            set_target_properties(sodium PROPERTIES
+                IMPORTED_LOCATION_DEBUG "${sodium_DLL_DEBUG}"
+            )
+        endif()
+        if (NOT (sodium_DLL_RELEASE MATCHES ".*-NOTFOUND"))
+            set_target_properties(sodium PROPERTIES
+                IMPORTED_LOCATION_RELWITHDEBINFO "${sodium_DLL_RELEASE}"
+                IMPORTED_LOCATION_MINSIZEREL "${sodium_DLL_RELEASE}"
+                IMPORTED_LOCATION_RELEASE "${sodium_DLL_RELEASE}"
+            )
+        endif()
+    endif()
+endif()
diff --git a/src/client.cpp b/src/client.cpp
index ce0baeae5cd97165b59b0c155bd3a7fe1da31ce1..c0e69263de897600ddeca9246fd6a97749928712 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -567,3 +567,20 @@ Client::upload_identity_keys(
         post<mtx::requests::UploadKeys, mtx::responses::UploadKeys>(
           "/client/r0/keys/upload", req, callback);
 }
+
+void
+Client::upload_one_time_keys(
+  const nlohmann::json &identity_keys,
+  std::function<void(const mtx::responses::UploadKeys &res, RequestErr err)> callback)
+{
+        mtx::requests::UploadKeys req;
+        req.device_keys.user_id   = user_id().to_string();
+        req.device_keys.device_id = device_id();
+
+        auto obj = identity_keys.at("curve25519");
+        for (auto it = obj.begin(); it != obj.end(); ++it)
+                req.one_time_keys.emplace("curve25519:" + it.key(), it.value());
+
+        post<mtx::requests::UploadKeys, mtx::responses::UploadKeys>(
+          "/client/r0/keys/upload", req, callback);
+}
diff --git a/src/client.hpp b/src/client.hpp
index afe2769d9c03d65b2d2584d2367a585260fab093..39188a89fe8a44baac6bab7b6aed5c8259447aeb 100644
--- a/src/client.hpp
+++ b/src/client.hpp
@@ -200,12 +200,18 @@ public:
         // Encryption related endpoints.
         //
 
-        //! Upload identity & one time keys.
+        //! Upload identity keys.
         // TODO: Replace json with a proper type. API methods shouldn't throw.
         void upload_identity_keys(
           const nlohmann::json &identity_keys,
           std::function<void(const mtx::responses::UploadKeys &res, RequestErr err)> cb);
 
+        //! Upload one time keys.
+        // TODO: Replace json with a proper type. API methods shouldn't throw.
+        void upload_one_time_keys(
+          const nlohmann::json &one_time_keys,
+          std::function<void(const mtx::responses::UploadKeys &res, RequestErr err)> cb);
+
 private:
         template<class Request, class Response>
         void post(const std::string &endpoint,
diff --git a/src/crypto.cpp b/src/crypto.cpp
index b8c39ab811700ecad545cf0e37e2ed1574703bd6..17c8a003c0434de88dd66b24aff975a974f7bcc0 100644
--- a/src/crypto.cpp
+++ b/src/crypto.cpp
@@ -1,15 +1,28 @@
+#include <sodium.h>
+
 #include "crypto.hpp"
 
 using json = nlohmann::json;
+using namespace mtx::client::crypto;
+
+std::unique_ptr<uint8_t[]>
+mtx::client::crypto::create_buffer(std::size_t nbytes)
+{
+        auto buf = std::make_unique<uint8_t[]>(nbytes);
+        randombytes_buf(buf.get(), nbytes);
+
+        return buf;
+}
 
 std::shared_ptr<olm::Account>
 mtx::client::crypto::olm_new_account()
 {
-        auto olm_account    = std::make_shared<olm::Account>();
-        const auto buf_size = olm_account->new_account_random_length();
-        auto account_buf    = std::make_unique<uint8_t[]>(buf_size);
+        auto olm_account = std::make_shared<olm::Account>();
 
-        int result = olm_account->new_account(account_buf.get(), buf_size);
+        const auto nbytes = olm_account->new_account_random_length();
+        auto buf          = create_buffer(nbytes);
+
+        int result = olm_account->new_account(buf.get(), nbytes);
 
         if (result == -1)
                 throw olm_exception("olm_new_account", olm_account->last_error);
@@ -20,31 +33,41 @@ mtx::client::crypto::olm_new_account()
 json
 mtx::client::crypto::identity_keys(std::shared_ptr<olm::Account> account)
 {
-        const auto buf_size = account->get_identity_json_length();
-        auto json_buf       = std::make_unique<uint8_t[]>(buf_size);
+        const auto nbytes = account->get_identity_json_length();
+        auto buf          = create_buffer(nbytes);
 
-        int result = account->get_identity_json(json_buf.get(), buf_size);
+        int result = account->get_identity_json(buf.get(), nbytes);
 
         if (result == -1)
                 throw olm_exception("identity_keys", account->last_error);
 
-        std::string data(json_buf.get(), json_buf.get() + buf_size);
+        std::string data(buf.get(), buf.get() + nbytes);
 
         return json::parse(data);
 }
 
+std::size_t
+mtx::client::crypto::generate_one_time_keys(std::shared_ptr<olm::Account> account,
+                                            std::size_t number_of_keys)
+{
+        const auto nbytes = account->generate_one_time_keys_random_length(number_of_keys);
+
+        auto buf = create_buffer(nbytes);
+        return account->generate_one_time_keys(number_of_keys, buf.get(), nbytes);
+}
+
 json
 mtx::client::crypto::one_time_keys(std::shared_ptr<olm::Account> account)
 {
-        const auto buf_size = account->get_one_time_keys_json_length();
-        auto json_buf       = std::make_unique<uint8_t[]>(buf_size);
+        const auto nbytes = account->get_one_time_keys_json_length();
+        auto buf          = create_buffer(nbytes);
 
-        int result = account->get_one_time_keys_json(json_buf.get(), buf_size);
+        int result = account->get_one_time_keys_json(buf.get(), nbytes);
 
         if (result == -1)
                 throw olm_exception("one_time_keys", account->last_error);
 
-        std::string data(json_buf.get(), json_buf.get() + buf_size);
+        std::string data(buf.get(), buf.get() + nbytes);
 
         return json::parse(data);
 }
diff --git a/src/crypto.hpp b/src/crypto.hpp
index bd6240a95ee3c22d40ebbc835abba65066e64763..be8b8f91be6e32206011780324eae4e490db0b6f 100644
--- a/src/crypto.hpp
+++ b/src/crypto.hpp
@@ -37,10 +37,17 @@ olm_new_account();
 nlohmann::json
 identity_keys(std::shared_ptr<olm::Account> user);
 
+//! Generate a number of one time keys.
+std::size_t
+generate_one_time_keys(std::shared_ptr<olm::Account> account, std::size_t number_of_keys);
+
 //! Retrieve the json representation of the one time keys for the given account.
 nlohmann::json
 one_time_keys(std::shared_ptr<olm::Account> user);
 
+std::unique_ptr<uint8_t[]>
+create_buffer(std::size_t nbytes);
+
 } // namespace crypto
 } // namespace client
 } // namespace mtx
diff --git a/tests/e2ee.cpp b/tests/e2ee.cpp
index 16167fad0edc1ed6bfe89d156bf7f1d9c1b936ab..9af76e64ae61c0916cf07d3ffd5d3e1394b9b89c 100644
--- a/tests/e2ee.cpp
+++ b/tests/e2ee.cpp
@@ -68,3 +68,29 @@ TEST(Encryption, UploadIdentityKeys)
 
         alice->close();
 }
+
+TEST(Encryption, UploadOneTimeKeys)
+{
+        auto alice       = std::make_shared<Client>("localhost");
+        auto olm_account = mtx::client::crypto::olm_new_account();
+
+        alice->login(
+          "alice", "secret", [](const mtx::responses::Login &, ErrType err) { check_error(err); });
+
+        while (alice->access_token().empty())
+                std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+        auto number_of_keys = mtx::client::crypto::generate_one_time_keys(olm_account, 5);
+        EXPECT_EQ(number_of_keys, 5);
+
+        auto one_time_keys = mtx::client::crypto::one_time_keys(olm_account);
+
+        alice->upload_one_time_keys(one_time_keys,
+                                    [](const mtx::responses::UploadKeys &res, ErrType err) {
+                                            check_error(err);
+                                            EXPECT_EQ(res.one_time_key_counts.size(), 1);
+                                            EXPECT_EQ(res.one_time_key_counts.at("curve25519"), 5);
+                                    });
+
+        alice->close();
+}