diff --git a/src/fileutils.cpp b/src/fileutils.cpp
index 9b7cecc81917b8c4fdd2dbdf3c915499ea3dce12..4fae5dc5290ef72f44ca17701dde01552c47c6d9 100644
--- a/src/fileutils.cpp
+++ b/src/fileutils.cpp
@@ -31,6 +31,9 @@
 #ifdef __ANDROID__
 #include "client/ring_signal.h"
 #endif
+#ifdef _WIN32
+#include "string_utils.h"
+#endif
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -204,6 +207,38 @@ bool isDirectoryWritable(const std::string &directory)
     return access(directory.c_str(), W_OK) == 0;
 }
 
+std::chrono::system_clock::time_point
+writeTime(const std::string& path)
+{
+#ifndef _WIN32
+    struct stat s;
+    auto ret = stat(path.c_str(), &s);
+    if (ret)
+        throw std::runtime_error("Can't check write time for: " + path);
+    return std::chrono::system_clock::from_time_t(s.st_mtime);
+#else
+    HANDLE h = CreateFile(ring::to_wstring(path).c_str(), GENERIC_READ, FILE_SHARE_READ,  nullptr,  OPEN_EXISTING,  FILE_ATTRIBUTE_NORMAL, nullptr);
+    if (h == INVALID_HANDLE_VALUE)
+        throw std::runtime_error("Can't open: " + path);
+    FILETIME lastWriteTime;
+    if (!GetFileTime(h, nullptr, nullptr, &lastWriteTime))
+        throw std::runtime_error("Can't check write time for: " + path);
+    CloseHandle(h);
+    SYSTEMTIME sTime;
+    if (!FileTimeToSystemTime(&lastWriteTime, &sTime))
+        throw std::runtime_error("Can't check write time for: " + path);
+    struct tm tm {};
+    tm.tm_year = sTime.wYear - 1900;
+    tm.tm_mon = sTime.wMonth - 1;
+    tm.tm_mday = sTime.wDay;
+    tm.tm_hour = sTime.wHour;
+    tm.tm_min = sTime.wMinute;
+    tm.tm_sec = sTime.wSecond;
+    tm.tm_isdst = -1;
+    return std::chrono::system_clock::from_time_t(mktime(&tm));
+#endif
+}
+
 std::vector<uint8_t>
 loadFile(const std::string& path)
 {
diff --git a/src/fileutils.h b/src/fileutils.h
index ff0d79bc533c3832dd5f7349a32270176f4d665b..ef7ea8bbf94dcf1c937f28a5b42e6431596faa26 100644
--- a/src/fileutils.h
+++ b/src/fileutils.h
@@ -23,6 +23,7 @@
 
 #include <string>
 #include <vector>
+#include <chrono>
 
 #define PROTECTED_GETENV(str) ({char *envvar_ = getenv((str)); \
                                                    envvar_ ? envvar_ : "";})
@@ -66,6 +67,8 @@ namespace ring { namespace fileutils {
 
     bool isDirectory(const std::string& path);
 
+    std::chrono::system_clock::time_point writeTime(const std::string& path);
+
     /**
      * Read content of the directory.
      * The result is a list of full paths of files in the directory,
diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp
index 34a8cd6a9c8f8840b4e3645c53c0c65f0fa3f6a3..acda9c942d35ebfb5770f0accc27929832dee6ff 100644
--- a/src/ringdht/ringaccount.cpp
+++ b/src/ringdht/ringaccount.cpp
@@ -1220,12 +1220,35 @@ RingAccount::loadValues() const
     return values;
 }
 
+tls::DhParams
+RingAccount::loadDhParams(const std::string path)
+{
+    try {
+        auto modified = fileutils::writeTime(path);
+        RING_WARN("modification date: %ld", std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - modified).count());
+        if (modified > std::chrono::system_clock::now() - std::chrono::hours(24 * 3))
+            return {fileutils::loadFile(path)};
+        else {
+            RING_WARN("file is too old");
+            throw std::runtime_error("file is too old");
+        }
+    } catch (...) {
+        auto params = tls::DhParams::generate();
+        try {
+            fileutils::saveFile(path, params.serialize(), 0600);
+        } catch (...) {
+            RING_WARN("error saving dh params");
+        }
+        return params;
+    }
+}
+
 void
 RingAccount::generateDhParams()
 {
-    std::packaged_task<decltype(tls::newDhParams)> task(tls::newDhParams);
+    std::packaged_task<decltype(loadDhParams)> task(loadDhParams);
     dhParams_ = task.get_future();
-    std::thread task_td(std::move(task));
+    std::thread task_td(std::move(task), cachePath_ + DIR_SEPARATOR_STR "dhParams");
     task_td.detach();
 }
 
diff --git a/src/ringdht/ringaccount.h b/src/ringdht/ringaccount.h
index 3be94634375e043ff43787cf92c7fcb860231aea..0d899cf15386628df5e1d625fce64f2412a9b63b 100644
--- a/src/ringdht/ringaccount.h
+++ b/src/ringdht/ringaccount.h
@@ -346,6 +346,8 @@ class RingAccount : public SIPAccountBase {
         void loadTreatedMessages();
         void saveTreatedMessages() const;
 
+        static tls::DhParams loadDhParams(const std::string path);
+
         /**
          * If privkeyPath_ is a valid private key file (PEM or DER),
          * and certPath_ a valid certificate file, load and returns them.
@@ -375,7 +377,7 @@ class RingAccount : public SIPAccountBase {
          */
         void generateDhParams();
 
-        std::shared_future<tls::TlsParams::DhParams> dhParams_;
+        std::shared_future<tls::DhParams> dhParams_;
         std::mutex dhParamsMtx_;
         std::condition_variable dhParamsCv_;
         bool allowPeersFromHistory_;
diff --git a/src/security/tls_session.cpp b/src/security/tls_session.cpp
index 0c53b45cbcce7ccd571c35c3bed8063601880e2f..c5818096fcccdf5dcd29a43580166ffdea5f6c24 100644
--- a/src/security/tls_session.cpp
+++ b/src/security/tls_session.cpp
@@ -54,6 +54,30 @@ duration2ms(std::chrono::duration<Rep, Period> d)
     return std::chrono::duration_cast<std::chrono::milliseconds>(d).count();
 }
 
+DhParams::DhParams(const std::vector<uint8_t>& data)
+{
+    gnutls_dh_params_t new_params_;
+    int ret = gnutls_dh_params_init(&new_params_);
+    if (ret)
+        throw std::runtime_error(std::string("Error initializing DH params: ") + gnutls_strerror(ret));
+    params_.reset(new_params_);
+    const gnutls_datum_t dat {(uint8_t*)data.data(), (unsigned)data.size()};
+    if (int ret_pem = gnutls_dh_params_import_pkcs3(params_.get(), &dat, GNUTLS_X509_FMT_PEM))
+        if (int ret_der = gnutls_dh_params_import_pkcs3(params_.get(), &dat, GNUTLS_X509_FMT_DER))
+            throw std::runtime_error(std::string("Error importing DH params: ") + gnutls_strerror(ret_pem) + " " + gnutls_strerror(ret_der));
+}
+
+std::vector<uint8_t>
+DhParams::serialize() const
+{
+    gnutls_datum_t out;
+    if (gnutls_dh_params_export2_pkcs3(params_.get(), GNUTLS_X509_FMT_PEM, &out))
+        return {};
+    std::vector<uint8_t> ret {out.data, out.data+out.size};
+    gnutls_free(out.data);
+    return ret;
+}
+
 class TlsSession::TlsCertificateCredendials
 {
     using T = gnutls_certificate_credentials_t;
@@ -202,8 +226,8 @@ TlsSession::initCredentials()
 
     // Setup DH-params (server only, may block on dh_params.get())
     if (isServer_) {
-        if (auto& dh_params = params_.dh_params.get())
-            gnutls_certificate_set_dh_params(*xcred_, dh_params.get());
+        if (const auto& dh_params = params_.dh_params.get().get())
+            gnutls_certificate_set_dh_params(*xcred_, dh_params);
         else
             RING_WARN("[TLS] DH params unavailable"); // YOMGUI: need to stop?
     }
@@ -654,12 +678,13 @@ TlsSession::process()
         callbacks_.onStateChange(new_state);
 }
 
-TlsParams::DhParams
-newDhParams()
+
+DhParams
+DhParams::generate()
 {
     using clock = std::chrono::high_resolution_clock;
 
-    auto bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, /* GNUTLS_SEC_PARAM_HIGH */ GNUTLS_SEC_PARAM_NORMAL);
+    auto bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, /* GNUTLS_SEC_PARAM_HIGH */ GNUTLS_SEC_PARAM_HIGH);
     RING_DBG("Generating DH params with %u bits", bits);
     auto start = clock::now();
 
@@ -667,18 +692,19 @@ newDhParams()
     int ret = gnutls_dh_params_init(&new_params_);
     if (ret != GNUTLS_E_SUCCESS) {
         RING_ERR("Error initializing DH params: %s", gnutls_strerror(ret));
-        return {nullptr, gnutls_dh_params_deinit};
+        return {};
     }
+    DhParams params {new_params_};
 
-    ret = gnutls_dh_params_generate2(new_params_, bits);
+    ret = gnutls_dh_params_generate2(params.get(), bits);
     if (ret != GNUTLS_E_SUCCESS) {
         RING_ERR("Error generating DH params: %s", gnutls_strerror(ret));
-        return {nullptr, gnutls_dh_params_deinit};
+        return {};
     }
 
     std::chrono::duration<double> time_span = clock::now() - start;
     RING_DBG("Generated DH params with %u bits in %lfs", bits, time_span.count());
-    return {new_params_, gnutls_dh_params_deinit};
+    return params;
 }
 
 }} // namespace ring::tls
diff --git a/src/security/tls_session.h b/src/security/tls_session.h
index 5bd2b5b7171bef4e785434455f7f102b71755ea5..450634ea6ab2271475cd01250feaca07ce034959 100644
--- a/src/security/tls_session.h
+++ b/src/security/tls_session.h
@@ -59,9 +59,34 @@ enum class TlsSessionState {
     SHUTDOWN
 };
 
-struct TlsParams {
-    using DhParams = std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&>;
+class DhParams {
+public:
+    DhParams() = default;
+    DhParams(DhParams&&) = default;
+
+    /** Take ownership of gnutls_dh_params */
+    explicit DhParams(gnutls_dh_params_t p) : params_(p, gnutls_dh_params_deinit) {};
+
+    /** Deserialize DER or PEM encoded DH-params */
+    DhParams(const std::vector<uint8_t>& data);
+
+    gnutls_dh_params_t get() {
+        return params_.get();
+    }
+    const gnutls_dh_params_t get() const {
+        return params_.get();
+    }
+
+    /** Serialize data in PEM format */
+    std::vector<uint8_t> serialize() const;
 
+    static DhParams generate();
+
+private:
+    std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&> params_ {nullptr, gnutls_dh_params_deinit};
+};
+
+struct TlsParams {
     // User CA list for session credentials
     std::string ca_list;
 
@@ -81,10 +106,6 @@ struct TlsParams {
                       unsigned cert_list_size)> cert_check;
 };
 
-// Compute Diffie-Hellman parameters suitable for TlsParams::dh_params.
-// Synchonous call: turn it asynchronous with std::async(std::launch::async, newDhParams)
-TlsParams::DhParams newDhParams();
-
 /**
  * TlsSession
  *
diff --git a/src/string_utils.cpp b/src/string_utils.cpp
index fbe8058fcd6609fb2d953c61324a6619f500e9b5..181e88818c327b040efcb2c56cf73e5111a97090 100644
--- a/src/string_utils.cpp
+++ b/src/string_utils.cpp
@@ -25,9 +25,28 @@
 #include <algorithm>
 #include <ostream>
 #include <stdexcept>
+#ifdef _WIN32
+#include <windows.h>
+#endif
 
 namespace ring {
 
+#ifdef _WIN32
+
+std::wstring to_wstring(const std::string& s)
+{
+    int slength = (int)s.length() + 1;
+    int len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, nullptr, 0);
+    if (not len)
+        throw std::runtime_error("Can't convert string to wchar");
+    std::wstring r((size_t)len, 0);
+    if (!MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, &(*r.begin()), len))
+        throw std::runtime_error("Can't convert string to wchar");
+    return r;
+}
+
+#endif
+
 std::string
 to_string(double value)
 {
diff --git a/src/string_utils.h b/src/string_utils.h
index 6ba88f2bb2fe15307fe3a554c9ba6ca17c43beac..707c88f33a196e856ee152a30bfeafc91c01b399 100644
--- a/src/string_utils.h
+++ b/src/string_utils.h
@@ -42,6 +42,12 @@ bool_to_str(bool b) noexcept
 
 std::string to_string(double value);
 
+#ifdef _WIN32
+
+std::wstring to_wstring(const std::string& s);
+
+#endif
+
 #ifdef __ANDROID__
 
 // Rationale: