diff --git a/CMakeLists.txt b/CMakeLists.txt
index 71058f033724ffa06175ef8723c507dc7087046d..945b4478621f159441a1eaa1b52977c53edffa0a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -106,10 +106,11 @@ set_package_properties(Boost PROPERTIES
     TYPE REQUIRED
 )
 
-add_library(matrix_client
+add_library(matrix_client SHARED
             lib/http/client.cpp
             lib/http/session.cpp
             lib/crypto/client.cpp
+            lib/crypto/utils.cpp
             lib/utils.cpp
             lib/structs/common.cpp
             lib/structs/errors.cpp
diff --git a/include/mtxclient/crypto/types.hpp b/include/mtxclient/crypto/types.hpp
index 675e2b1acbad65a40b24c2fc204a949956bab769..52f52cb2131d536eda591134b22f4699263fb005 100644
--- a/include/mtxclient/crypto/types.hpp
+++ b/include/mtxclient/crypto/types.hpp
@@ -80,13 +80,18 @@ from_json(const nlohmann::json &obj, ExportedSession &s)
 inline void
 to_json(nlohmann::json &obj, const ExportedSessionKeys &keys)
 {
-        obj["sessions"] = keys.sessions;
+        obj = keys.sessions;
 }
 
 inline void
 from_json(const nlohmann::json &obj, ExportedSessionKeys &keys)
 {
-        keys.sessions = obj.at("sessions").get<std::vector<ExportedSession>>();
+        try {
+                keys.sessions = obj.get<std::vector<ExportedSession>>();
+        // might be the old format.
+        } catch (const nlohmann::json::exception&) {
+                keys.sessions = obj.at("sessions").get<std::vector<ExportedSession>>();
+        }
 }
 
 inline void
diff --git a/include/mtxclient/crypto/utils.hpp b/include/mtxclient/crypto/utils.hpp
index fb9c374485585cfa6a8ded316e25336418deb4a5..257e97b180772790ed758dc0791ba2e7d5fc9a8d 100644
--- a/include/mtxclient/crypto/utils.hpp
+++ b/include/mtxclient/crypto/utils.hpp
@@ -3,12 +3,14 @@
 
 #include <string>
 #include <vector>
+#include <algorithm>
 
 #include <openssl/aes.h>
 #include <openssl/evp.h>
 #include <openssl/hmac.h>
 #include <openssl/sha.h>
 
+#include <boost/algorithm/string.hpp>
 
 namespace mtx {
 namespace crypto {
@@ -16,13 +18,28 @@ namespace crypto {
 //! Data representation used to interact with libolm.
 using BinaryBuf = std::vector<uint8_t>;
 
+const std::string HEADER_LINE ( "-----BEGIN MEGOLM SESSION DATA-----");
+const std::string TRAILER_LINE ( "-----END MEGOLM SESSION DATA-----");
+
 //! Simple wrapper around the OpenSSL PKCS5_PBKDF2_HMAC function
 BinaryBuf PBKDF2_HMAC_SHA_512(const std::string pass, const BinaryBuf salt, uint32_t iterations);
 
-BinaryBuf AES_CTR_256(const std::string plaintext, const BinaryBuf aes256Key, BinaryBuf iv);
+BinaryBuf AES_CTR_256_Encrypt(const std::string plaintext, const BinaryBuf aes256Key, BinaryBuf iv);
+
+BinaryBuf AES_CTR_256_Decrypt(const std::string ciphertext, const BinaryBuf aes256Key, BinaryBuf iv);
 
 BinaryBuf HMAC_SHA256(const BinaryBuf hmacKey, const BinaryBuf data);
 
+//! Translates the data back into the binary buffer, taking care
+//! to remove the header and footer elements.
+std::string unpack_key_file(const std::string &data);
+
+template<typename T>
+void remove_substrs(std::basic_string<T>& s,
+                   const std::basic_string<T>& p);
+
+void uint8_to_uint32(uint8_t b[4], uint32_t &u32);
+
 void uint32_to_uint8 (uint8_t b[4], uint32_t u32);
 
 void print_binary_buf(const BinaryBuf buf) ;
diff --git a/lib/crypto/client.cpp b/lib/crypto/client.cpp
index fe3bd50d1378b439314d13a268e1e16a3427a804..70a58c621c5b15993e9c7d2446c31fac855ad7db 100644
--- a/lib/crypto/client.cpp
+++ b/lib/crypto/client.cpp
@@ -561,7 +561,7 @@ mtx::crypto::encrypt_exported_sessions(const mtx::crypto::ExportedSessionKeys &k
 
         BinaryBuf hmac256 = BinaryBuf(buf.begin() + 32, buf.begin() + (2 * 32));
 
-        auto ciphertext = mtx::crypto::AES_CTR_256(plaintext,
+        auto ciphertext = mtx::crypto::AES_CTR_256_Encrypt(plaintext,
                                 aes256,
                                 nonce);
 
@@ -594,31 +594,87 @@ mtx::crypto::encrypt_exported_sessions(const mtx::crypto::ExportedSessionKeys &k
 mtx::crypto::ExportedSessionKeys
 mtx::crypto::decrypt_exported_sessions(const std::string &data, std::string pass)
 {
-        std::cout << "decrypt_exported_sessions data: " << data << std::endl;
-        if (data.size() <
-            crypto_secretbox_MACBYTES + crypto_secretbox_NONCEBYTES + crypto_pwhash_SALTBYTES)
-                throw sodium_exception{"decrypt_exported_sessions", "ciphertext too small"};
+        // Parse the data into a base64 string without the header and footer
+        std::string unpacked = mtx::crypto::unpack_key_file(data);
 
-        const auto nonce_start = data.begin();
-        const auto nonce_end   = nonce_start + crypto_secretbox_NONCEBYTES;
-        auto nonce             = BinaryBuf(nonce_start, nonce_end);
+        std::string binary_str = base642bin(unpacked);
 
-        const auto salt_end = nonce_end + crypto_pwhash_SALTBYTES;
-        auto salt           = BinaryBuf(nonce_end, salt_end);
+        const auto binary_start = binary_str.begin();
+        const auto binary_end = binary_str.end();
 
-        auto ciphertext = BinaryBuf(salt_end, data.end());
-        auto decrypted  = create_buffer(ciphertext.size() - crypto_secretbox_MACBYTES);
+        // Format version 0x01, 1 byte
+        const auto format_end = binary_start + 1;
+        auto format           = BinaryBuf(binary_start, format_end);
 
-        auto key = derive_key(pass, salt);
+        // Salt, 16 bytes
+        const auto salt_end   = format_end + crypto_pwhash_SALTBYTES;
+        auto salt             = BinaryBuf(format_end, salt_end);
 
-        if (crypto_secretbox_open_easy(decrypted.data(),
-                                       reinterpret_cast<const unsigned char *>(ciphertext.data()),
-                                       ciphertext.size(),
-                                       nonce.data(),
-                                       reinterpret_cast<const unsigned char *>(key.data())) != 0)
-                throw sodium_exception{"crypto_secretbox_open_easy", "failed to decrypt"};
+        // IV, 16 bytes
+        const auto iv_end     = salt_end + AES_BLOCK_SIZE;
+        auto iv               = BinaryBuf(salt_end, iv_end);
 
-        return json::parse(std::string(decrypted.begin(), decrypted.end()));
+        // Number of rounds, 4 bytes
+        const auto rounds_end = iv_end + sizeof(uint32_t);
+        auto rounds_buff      = BinaryBuf(iv_end, rounds_end);
+        uint8_t rounds_arr[4];
+        std::copy(rounds_buff.begin(), rounds_buff.end(), rounds_arr);
+        uint32_t rounds;
+        mtx::crypto::uint8_to_uint32(rounds_arr, rounds);
+
+        // Variable-length JSON object...
+        const auto json_end = binary_end - SHA256_DIGEST_LENGTH;
+        auto json           = BinaryBuf(rounds_end, json_end);
+
+        // HMAC of the above, 32 bytes
+        auto hmac           = BinaryBuf(json_end, binary_end);
+
+        // derive the keys
+        auto buf = mtx::crypto::PBKDF2_HMAC_SHA_512(pass,
+                                   salt,
+                                   rounds);
+
+        BinaryBuf aes256 = BinaryBuf(buf.begin(), buf.begin() + 32);
+
+        BinaryBuf hmac256 = BinaryBuf(buf.begin() + 32, buf.begin() + (2 * 32));
+
+        // get hmac and verify they match
+        auto hmacSha256 = mtx::crypto::HMAC_SHA256(hmac256, BinaryBuf(binary_start, json_end));
+
+        if (hmacSha256 != hmac) {
+                throw sodium_exception{"decrypt_exported_sessions", "HMAC doesn't match"};
+        }
+
+        const std::string ciphertext(json.begin(), json.end());
+        auto decrypted = mtx::crypto::AES_CTR_256_Decrypt(ciphertext, aes256, iv);
+
+        // TODO: Move this to a helper function for the 'old' format that
+        // nheko enabled pre-e2e key spec.
+        // std::cout << "decrypt_exported_sessions data: " << data << std::endl;
+        // if (data.size() <
+        //     crypto_secretbox_MACBYTES + crypto_secretbox_NONCEBYTES + crypto_pwhash_SALTBYTES)
+        //         throw sodium_exception{"decrypt_exported_sessions", "ciphertext too small"};
+
+        // const auto nonce_start = data.begin();
+        // const auto nonce_end   = nonce_start + crypto_secretbox_NONCEBYTES;
+        // auto nonce             = BinaryBuf(nonce_start, nonce_end);
+
+        // const auto salt_end = nonce_end + crypto_pwhash_SALTBYTES;
+        // auto salt           = BinaryBuf(nonce_end, salt_end);
+
+        // auto ciphertext = BinaryBuf(salt_end, data.end());
+        // auto decrypted  = create_buffer(ciphertext.size() - crypto_secretbox_MACBYTES);
+
+        // auto key = derive_key(pass, salt);
+
+        // if (crypto_secretbox_open_easy(decrypted.data(),
+        //                                reinterpret_cast<const unsigned char *>(ciphertext.data()),
+        //                                ciphertext.size(),
+        //                                nonce.data(),
+        //                                reinterpret_cast<const unsigned char *>(key.data())) != 0)
+        //         throw sodium_exception{"crypto_secretbox_open_easy", "failed to decrypt"};
+        std::string plaintext(decrypted.begin(), decrypted.end());
+        return json::parse(plaintext);
 }
 
 std::string
diff --git a/lib/crypto/utils.cpp b/lib/crypto/utils.cpp
index 8800a0fe8b287ec0f77aee04fefa707798f1adcf..254c07d2b789d130c5cb133ee18b0eb2bdb08979 100644
--- a/lib/crypto/utils.cpp
+++ b/lib/crypto/utils.cpp
@@ -16,14 +16,14 @@ PBKDF2_HMAC_SHA_512(const std::string pass, const BinaryBuf salt, uint32_t itera
 }
 
 BinaryBuf
-AES_CTR_256(const std::string plaintext, const BinaryBuf aes256Key, BinaryBuf iv) {
+AES_CTR_256_Encrypt(const std::string plaintext, const BinaryBuf aes256Key, BinaryBuf iv) {
     EVP_CIPHER_CTX *ctx;
     
     int len;
 
     int ciphertext_len;
 
-    unsigned char *cipher;
+    unsigned char *cipher = new unsigned char[plaintext.size()];
 
     uint8_t *iv_data = iv.data();
     // need to set bit 63 to 0
@@ -58,10 +58,85 @@ AES_CTR_256(const std::string plaintext, const BinaryBuf aes256Key, BinaryBuf iv
     /* Clean up */
     EVP_CIPHER_CTX_free(ctx);
 
-    return BinaryBuf(reinterpret_cast<uint8_t *>(cipher), cipher + ciphertext_len);
+    BinaryBuf encrypted(reinterpret_cast<uint8_t *>(cipher), cipher + ciphertext_len);
+    delete [] cipher;
+    return encrypted;
 
 }
 
+BinaryBuf
+AES_CTR_256_Decrypt(const std::string ciphertext, const BinaryBuf aes256Key, BinaryBuf iv)
+{
+    EVP_CIPHER_CTX *ctx;
+
+    int len;
+
+    int plaintext_len;
+
+    unsigned char *plaintext = new unsigned char[ciphertext.size()];
+
+
+    /* Create and initialise the context */
+    if (!(ctx = EVP_CIPHER_CTX_new())) {
+            // handleErrors();
+    }
+
+    /* Initialise the decryption operation. IMPORTANT - ensure you use a key
+        * and IV size appropriate for your cipher
+        * In this example we are using 256 bit AES (i.e. a 256 bit key). The
+        * IV size for *most* modes is the same as the block size. For AES this
+        * is 128 bits */
+    if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, aes256Key.data(), iv.data())) {
+            // handleErrors();
+    }
+
+    /* Provide the message to be decrypted, and obtain the plaintext output.
+        * EVP_DecryptUpdate can be called multiple times if necessary
+        */
+    if (1 != EVP_DecryptUpdate(ctx, plaintext, &len, reinterpret_cast<const unsigned char*>(&ciphertext.data()[0]), ciphertext.size())) {
+            // handleErrors();
+    }
+    plaintext_len = len;
+
+    /* Finalise the decryption. Further plaintext bytes may be written at
+        * this stage.
+        */
+    if (1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) {
+            //  handleErrors();
+    }
+    plaintext_len += len;
+
+    /* Clean up */
+    EVP_CIPHER_CTX_free(ctx);
+
+    BinaryBuf decrypted(reinterpret_cast<uint8_t *>(plaintext), plaintext + plaintext_len);
+    delete[] plaintext;
+    return decrypted;
+}
+
+template<typename T>
+void remove_substrs(std::basic_string<T>& s,
+                   const std::basic_string<T>& p) {
+   auto n = p.length();
+
+   for (auto i = s.find(p);
+        i != std::basic_string<T>::npos;
+        i = s.find(p))
+      s.erase(i, n);
+}
+
+std::string
+unpack_key_file(const std::string &data) {
+    std::string unpacked(data);
+    remove_substrs(unpacked, HEADER_LINE);
+
+    remove_substrs(unpacked, TRAILER_LINE);
+
+    remove_substrs(unpacked, std::string("\n"));
+
+    return unpacked;
+}
+
 BinaryBuf
 HMAC_SHA256 (const BinaryBuf hmacKey, const BinaryBuf data) {
     unsigned int len = SHA256_DIGEST_LENGTH;
@@ -79,6 +154,10 @@ print_binary_buf(const BinaryBuf buf) {
     std::cout << std::endl;
 }
 
+void uint8_to_uint32(uint8_t b[4], uint32_t &u32) {
+    u32 = b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3];
+}
+
 void uint32_to_uint8 (uint8_t b[4], uint32_t u32) {
     b[3] = (uint8_t)u32;
     b[2] = (uint8_t)(u32>>=8);