diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt
index c97a2279b2700354c0e8a94e36dda3887c7769df..c0abb99ca994d4d9ff91947fe00f5dbf0dbeaeee 100644
--- a/cpp/src/CMakeLists.txt
+++ b/cpp/src/CMakeLists.txt
@@ -3,7 +3,7 @@ if (fmt_FOUND)
 endif()
 
 if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
-  add_definitions(-DNOGDI)
+  add_definitions(-DNOGDI -DNOMINMAX)
 endif()
 
 add_definitions(-DBOOST_FILESYSTEM_NO_DEPRECATED)
@@ -50,6 +50,7 @@ set(VEREIGNLIB_SRC
   vereign/core/rand.cc
   vereign/core/temp.cc
   vereign/core/fs.cc
+  vereign/core/string.cc
 
   vereign/restapi/detail/http_reader.cc
   vereign/restapi/client.cc
@@ -95,6 +96,9 @@ if (LINUX)
   )
 elseif (WIN32)
   list(APPEND VEREIGNLIB_SRC
+    vereign/ncrypt/errors.cc
+    vereign/ncrypt/unique_ptr.cc
+    vereign/ncrypt/rsa.cc
     vereign/kvstore/detail/linux_crypto_storage.cc
   )
 endif()
@@ -115,6 +119,8 @@ target_link_libraries(vereignlib PUBLIC
   Boost::filesystem
   $<$<CXX_COMPILER_ID:MSVC>:Boost::date_time>
   SQLite::SQLite3
+  $<$<CXX_COMPILER_ID:MSVC>:ncrypt.lib>
+  $<$<CXX_COMPILER_ID:MSVC>:cryptui.lib>
 )
 
 add_library(vereign SHARED
diff --git a/cpp/src/csandbox.cc b/cpp/src/csandbox.cc
index 0f3226d364e1b95441b80b2706f5f0222b347e8a..e8288a68446d589959df01778774b5344b9429fa 100644
--- a/cpp/src/csandbox.cc
+++ b/cpp/src/csandbox.cc
@@ -1,6 +1,10 @@
 #include <boost/core/ignore_unused.hpp>
 #include <iostream>
 #include <boost/filesystem.hpp>
+#include <vereign/core/string.hh>
+
+#include <vereign/bytes/view.hh>
+#include <vereign/bytes/view_dump.hh>
 
 namespace fs = boost::filesystem;
 
@@ -10,11 +14,37 @@ auto main(int argc, char** argv) -> int {
 
   // fs::path ;
 
-  std::cout << "Temp directory is " << fs::temp_directory_path() << '\n';
+  // std::wstring s = L"c:\\проба.txt";
+  std::string path = "c:\\ztrash\\проба.txt";
+  std::wstring ws = vereign::string::widen(path);
+
+  std::string p2 = vereign::string::narrow(ws);
+  std::cout << vereign::bytes::dump(vereign::bytes::View(path)) << std::endl;
+  std::cout << vereign::bytes::dump(vereign::bytes::View(p2)) << std::endl;
+  if (p2 == path) {
+    std::cout << "yes" << std::endl;
+  }
+
+  std::cout << vereign::bytes::dump(vereign::bytes::View(ws)) << std::endl;
+
+  // std::ofstream f{ws};
+  // std::wcout << ws;
+  std::cout << path << std::endl;
+  std::wcout << ws << std::endl;
+  // std::cout << "проба" << std::endl;
+  // wchar_t ru[] = L"Привет"; //Russian language
+  // std::wcout << ru;
+  // std::cout << std::endl;
+  // std::cout << "Temp directory is " << fs::temp_directory_path() << '\n';
   // std::cout << "Home directory is " << fs::user << '\n';
 
   // p.wstring();
   // std::cout << p.string() << std::endl;;
 
+  // wchar_t* in = L"ทดสอบ"; // thai language
+  // char* out=(char *)malloc(15);
+  // WideCharToMultiByte(874, 0, in, 15, out, 15, NULL, NULL);
+  // printf(out); // result is correctly in Thai although not neat
+
   return 1;
 }
diff --git a/cpp/src/vereign/bytes/view.hh b/cpp/src/vereign/bytes/view.hh
index ddf1c7514b8df61731d6d96c6e38ad3937c18309..c64f59a14003857e8d7b5c3f7efbf0b619ad8f97 100644
--- a/cpp/src/vereign/bytes/view.hh
+++ b/cpp/src/vereign/bytes/view.hh
@@ -11,7 +11,7 @@ namespace vereign::bytes {
 
 class View {
 public:
-  View() noexcept : size_{0}, data_{nullptr} {}
+  View() = default;
 
   View(const uint8_t* data, std::size_t size) noexcept
     : size_{size},
@@ -25,6 +25,12 @@ public:
   {
   }
 
+  View(std::wstring_view str) noexcept
+    : size_{str.length() * sizeof(wchar_t)},
+      data_{size_ > 0 ? reinterpret_cast<const uint8_t*>(str.data()): nullptr}
+  {
+  }
+
   View(const void* ptr, std::size_t size) noexcept
     : size_{size},
       data_{static_cast<const uint8_t*>(ptr)}
diff --git a/cpp/src/vereign/core/lock_guard.hh b/cpp/src/vereign/core/lock_guard.hh
new file mode 100644
index 0000000000000000000000000000000000000000..9e310b21ca53ce5c53fea581a483ecaa568004f4
--- /dev/null
+++ b/cpp/src/vereign/core/lock_guard.hh
@@ -0,0 +1,28 @@
+#ifndef __VEREIGN_LOCK_GUARD_HH
+#define __VEREIGN_LOCK_GUARD_HH
+
+namespace vereign::core {
+
+template <class Lockable>
+class LockGuard {
+public:
+  explicit LockGuard(Lockable& lock)
+    : lock_{lock}
+  {
+    lock.Lock();
+  }
+
+  ~LockGuard() noexcept {
+    lock_.Unlock();
+  }
+
+  LockGuard(const LockGuard&) = delete;
+  auto operator=(const LockGuard&) -> LockGuard&  = delete;
+
+private:
+  Lockable& lock_;
+};
+
+} // namespace vereign::kvstore
+
+#endif // __VEREIGN_LOCK_GUARD_HH
diff --git a/cpp/src/vereign/core/string.cc b/cpp/src/vereign/core/string.cc
new file mode 100644
index 0000000000000000000000000000000000000000..933e2e2fb8412ff2a0e0bf244c90bf7873924594
--- /dev/null
+++ b/cpp/src/vereign/core/string.cc
@@ -0,0 +1,84 @@
+#include <vereign/core/string.hh>
+
+#include <iostream>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <stringapiset.h>
+#endif
+
+namespace vereign::string {
+
+#ifdef _WIN32
+
+auto widen(const std::string& utf8_str) -> std::wstring {
+  if (utf8_str.empty()) {
+    return L"";
+  }
+
+  int num_chars = MultiByteToWideChar(CP_UTF8 , 0, utf8_str.data(), utf8_str.length(), nullptr, 0);
+  if (num_chars == 0) {
+    return std::wstring{};
+  }
+
+  std::wstring result;
+  result.resize(num_chars);
+  num_chars = MultiByteToWideChar(
+    CP_UTF8,
+    0,
+    utf8_str.data(),
+    utf8_str.length(),
+    result.data(),
+    num_chars
+  );
+
+  if (num_chars == 0) {
+    return L"";
+  }
+
+  return result;
+}
+
+auto narrow(const std::wstring& utf16_str) -> std::string {
+  if (utf16_str.empty()) {
+    return "";
+  }
+
+  int num_chars = WideCharToMultiByte(
+    CP_UTF8,
+    0,
+    utf16_str.data(),
+    utf16_str.length(),
+    nullptr,
+    0,
+    nullptr,
+    nullptr
+  );
+
+  if (num_chars == 0) {
+    return "";
+  }
+
+  std::string result;
+  result.resize(num_chars);
+  num_chars = WideCharToMultiByte(
+    CP_UTF8,
+    0,
+    utf16_str.data(),
+    utf16_str.length(),
+    result.data(),
+    num_chars,
+    nullptr,
+    nullptr
+  );
+
+  if (num_chars == 0) {
+    return "";
+  }
+
+  return result;
+}
+
+#endif
+
+} // vereign::string
diff --git a/cpp/src/vereign/core/string.hh b/cpp/src/vereign/core/string.hh
new file mode 100644
index 0000000000000000000000000000000000000000..1db221266e4aca68d3d86764dbbbf59008adccd3
--- /dev/null
+++ b/cpp/src/vereign/core/string.hh
@@ -0,0 +1,18 @@
+#ifndef __VEREIGN_CORE_STRING_HH
+#define __VEREIGN_CORE_STRING_HH
+
+#include <string>
+#include <codecvt>
+
+namespace vereign::string {
+
+#ifdef _WIN32
+
+auto widen(const std::string& utf8_str) -> std::wstring;
+auto narrow(const std::wstring& utf16_str) -> std::string;
+
+#endif
+
+} // vereign::string
+
+#endif // __VEREIGN_CORE_STRING_HH
diff --git a/cpp/src/vereign/encoding/hex.cc b/cpp/src/vereign/encoding/hex.cc
index 6c8dc18cf76bc8d93ae78e82c739ea70744e77a1..e513512bfddecebab9f9fdd51423bbf5ad811f36 100644
--- a/cpp/src/vereign/encoding/hex.cc
+++ b/cpp/src/vereign/encoding/hex.cc
@@ -51,4 +51,21 @@ void Decode(std::string_view src, bytes::Buffer& decoded) {
   decoded.IncSize(src.size() / 2);
 }
 
+void EncodeReverse(bytes::View src, bytes::Buffer& encoded) {
+  if (src.Size() == 0) {
+    return;
+  }
+
+  static const char* nibbles = { "0123456789abcdef" };
+
+  encoded.Reserve(src.Size() * 2);
+
+  for (std::size_t i = 0; i < src.Size(); ++i) {
+    encoded[i*2] = nibbles[src[src.Size() - 1 - i] >> 4];
+    encoded[i*2 + 1] = nibbles[src[src.Size() - 1 - i] & 0x0F];
+  }
+
+  encoded.IncSize(src.Size() * 2);
+}
+
 } // vereign::encoding::base64
diff --git a/cpp/src/vereign/encoding/hex.hh b/cpp/src/vereign/encoding/hex.hh
index 495f09027acb88f9c7f4066fc33d29a05f476b49..f2e406453bda12dbead8086bcf70099b72d2b176 100644
--- a/cpp/src/vereign/encoding/hex.hh
+++ b/cpp/src/vereign/encoding/hex.hh
@@ -9,6 +9,8 @@ namespace vereign::encoding::hex {
 void Encode(bytes::View src, bytes::Buffer& encoded);
 void Decode(bytes::View src, bytes::Buffer& decoded);
 
+void EncodeReverse(bytes::View src, bytes::Buffer& encoded);
+
 } // vereign::encoding::hex
 
 #endif // __VEREIGN_ENCODING_HEX_HH
diff --git a/cpp/src/vereign/kvstore/sqlite_storage.cc b/cpp/src/vereign/kvstore/sqlite_storage.cc
index 390c2cb4fd462068457288aefe93b9d200393bf0..de5b48d28b5f2e68628f4c1b3b155de3f29f4db9 100644
--- a/cpp/src/vereign/kvstore/sqlite_storage.cc
+++ b/cpp/src/vereign/kvstore/sqlite_storage.cc
@@ -1,12 +1,59 @@
 #include <vereign/kvstore/sqlite_storage.hh>
+
 #include <vereign/encoding/binary.hh>
+#include <vereign/kvstore/errors.hh>
+#include <boost/optional.hpp>
+#include <vereign/core/lock_guard.hh>
 
+#include <mutex>
 #include <array>
 
 namespace vereign::kvstore {
 
+namespace detail {
+
+class SqliteLock {
+public:
+  SqliteLock(sqlite::Connection& db)
+    : db_{db}
+  {
+  }
+
+  ~SqliteLock() {
+    Unlock();
+  }
+
+  void Lock() {
+    if (!tr_) {
+      tr_ = db_.BeginExplicitTransaction();
+    }
+
+    count_++;
+  }
+
+  void Unlock() {
+    if (count_ == 0 || !tr_) {
+      throw Error{"SqliteStorage unexpected call Unlock with non existent lock"};
+    }
+
+    count_--;
+    if (count_ == 0) {
+      tr_->Commit();
+      tr_.reset();
+    }
+  }
+
+private:
+  int count_ = 0;
+  boost::optional<sqlite::Transaction> tr_;
+  sqlite::Connection& db_;
+};
+
+}
+
 SqliteStorage::SqliteStorage(const std::string& db_path)
-  : db_{db_path}
+  : db_{db_path},
+    lock_{std::make_unique<detail::SqliteLock>(db_)}
 {
   db_.Execute(R"(
 CREATE TABLE IF NOT EXISTS storage (
@@ -16,19 +63,27 @@ CREATE TABLE IF NOT EXISTS storage (
   )");
 }
 
+SqliteStorage::~SqliteStorage() = default;
+
+void SqliteStorage::Lock() {
+  lock_->Lock();
+}
+
+void SqliteStorage::Unlock() {
+  lock_->Unlock();
+}
+
 void SqliteStorage::PutBytes(const std::string& key, bytes::View value) {
-  auto tr = db_.BeginExplicitTransaction();
+  core::LockGuard<detail::SqliteLock> l{*lock_};
 
   auto stmt = db_.Prepare("REPLACE INTO storage(key, value) VALUES(?, ?);");
   stmt.BindText(1, key);
   stmt.BindBlob(2, value);
   stmt.Step();
-
-  tr.Commit();
 }
 
 void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) {
-  auto tr = db_.BeginExplicitTransaction();
+  core::LockGuard<detail::SqliteLock> l{*lock_};
 
   auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?");
   stmt.BindText(1, key);
@@ -37,12 +92,11 @@ void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) {
   if (!end) {
     value.Write(stmt.GetColumnBlob(0));
   }
-
-  tr.Commit();
 }
 
 void SqliteStorage::PutInt64(const std::string& key, int64_t value) {
-  auto tr = db_.BeginExplicitTransaction();
+  core::LockGuard<detail::SqliteLock> l{*lock_};
+
   std::array<uint8_t, 8> encoded;
   encoding::binary::EncodeUint64(encoded.data(), value);
 
@@ -50,12 +104,10 @@ void SqliteStorage::PutInt64(const std::string& key, int64_t value) {
   stmt.BindText(1, key);
   stmt.BindBlob(2, bytes::View(encoded.data(), 8));
   stmt.Step();
-
-  tr.Commit();
 }
 
 auto SqliteStorage::GetInt64(const std::string& key) -> int64_t {
-  auto tr = db_.BeginExplicitTransaction();
+  core::LockGuard<detail::SqliteLock> l{*lock_};
 
   auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?");
   stmt.BindText(1, key);
@@ -67,14 +119,11 @@ auto SqliteStorage::GetInt64(const std::string& key) -> int64_t {
 
   auto buf = stmt.GetColumnBlob(0);
   if (buf.Size() != 8) {
-    // FIXME: probably throw an exception here
-    return 0;
+    throw Error("cannot decode bytes size");
   }
 
   int64_t result = encoding::binary::DecodeUint64(buf);
 
-  tr.Commit();
-
   return result;
 }
 
diff --git a/cpp/src/vereign/kvstore/sqlite_storage.hh b/cpp/src/vereign/kvstore/sqlite_storage.hh
index ef91c50e0a40dec5b07c16355a13de1bf01b7d4a..012c35f494faae64d4bfc04b9e23cfb19b6d8555 100644
--- a/cpp/src/vereign/kvstore/sqlite_storage.hh
+++ b/cpp/src/vereign/kvstore/sqlite_storage.hh
@@ -4,11 +4,24 @@
 #include <vereign/kvstore/storage.hh>
 #include <vereign/sqlite/connection.hh>
 
+#include <memory>
+
 namespace vereign::kvstore {
 
+namespace detail {
+class SqliteLock;
+}
+
 class SqliteStorage : public Storage {
 public:
   SqliteStorage(const std::string& db_path);
+  ~SqliteStorage() override;
+
+  SqliteStorage(const SqliteStorage&) = delete;
+  auto operator=(const SqliteStorage&) -> SqliteStorage& = delete;
+
+  void Lock() override;
+  void Unlock() override;
 
   void PutBytes(const std::string& key, bytes::View value) override;
   void GetBytes(const std::string& key, bytes::Buffer& value) override;
@@ -18,6 +31,7 @@ public:
 
 private:
   sqlite::Connection db_;
+  std::unique_ptr<detail::SqliteLock> lock_;
 };
 
 } // namespace vereign::kvstore
diff --git a/cpp/src/vereign/kvstore/storage.hh b/cpp/src/vereign/kvstore/storage.hh
index a8adf3d756d070b949f1ac9c443ec284fb357b07..2bdb268d6395ceb981bed9adf6b442e7b34c0fe1 100644
--- a/cpp/src/vereign/kvstore/storage.hh
+++ b/cpp/src/vereign/kvstore/storage.hh
@@ -7,6 +7,9 @@ namespace vereign::kvstore {
 
 class Storage {
 public:
+  virtual void Lock() = 0;
+  virtual void Unlock() = 0;
+
   virtual void PutBytes(const std::string& key, bytes::View value) = 0;
   virtual void GetBytes(const std::string& key, bytes::Buffer& value) = 0;
 
diff --git a/cpp/src/vereign/ncrypt/errors.cc b/cpp/src/vereign/ncrypt/errors.cc
new file mode 100644
index 0000000000000000000000000000000000000000..fedb516591b3709bad2cbc244caabc0f4f9a3e79
--- /dev/null
+++ b/cpp/src/vereign/ncrypt/errors.cc
@@ -0,0 +1,45 @@
+#include <vereign/ncrypt/errors.hh>
+#include <vereign/bytes/buffer.hh>
+#include <vereign/bytes/view_dump.hh>
+#include <vereign/encoding/hex.hh>
+#include <vereign/encoding/binary.hh>
+
+#include <string>
+
+namespace vereign::ncrypt {
+
+auto SecurityStatusToString(SECURITY_STATUS status) -> std::string {
+  switch (status) {
+  case NTE_FAIL:
+    return "NTE_FAIL";
+  case NTE_INVALID_PARAMETER:
+    return "NTE_INVALID_PARAMETER";
+  case NTE_BUFFER_TOO_SMALL:
+    return "NTE_BUFFER_TOO_SMALL";
+  case NTE_EXISTS:
+    return "NTE_EXISTS";
+  case NTE_BAD_KEYSET:
+    return "NTE_BAD_KEYSET";
+  case NTE_INVALID_HANDLE:
+    return "NTE_INVALID_HANDLE";
+  case NTE_NOT_SUPPORTED:
+    return "NTE_NOT_SUPPORTED";
+  case NTE_NULL_REFERENCE_POINTER:
+    return "NTE_NULL_REFERENCE_POINTER";
+  default:
+    bytes::Buffer encoded;
+    encoding::hex::EncodeReverse(bytes::View(&status, sizeof(status)), encoded);
+    return std::string(encoded.View().String());
+  }
+}
+
+Error::Error(SECURITY_STATUS status, const std::string& msg)
+  : std::runtime_error{msg + ": " + SecurityStatusToString(status)},
+    status_{status}
+{}
+
+auto Error::SecurityStatus() const -> SECURITY_STATUS {
+  return status_;
+}
+
+} // vereign::ncrypt
diff --git a/cpp/src/vereign/ncrypt/errors.hh b/cpp/src/vereign/ncrypt/errors.hh
new file mode 100644
index 0000000000000000000000000000000000000000..a37056c02947ccdcd0aa49841aaeb4593dd6c06e
--- /dev/null
+++ b/cpp/src/vereign/ncrypt/errors.hh
@@ -0,0 +1,26 @@
+#ifndef __VEREIGN_NCRYPT_ERRORS_HH
+#define __VEREIGN_NCRYPT_ERRORS_HH
+
+#include <windows.h>
+#include <ncrypt.h>
+#include <stdexcept>
+
+namespace vereign::ncrypt {
+
+constexpr SECURITY_STATUS NTE_NULL_REFERENCE_POINTER = 0x800706f4;
+
+auto SecurityStatusToString(SECURITY_STATUS status) -> std::string;
+
+class Error : public std::runtime_error {
+public:
+  Error(SECURITY_STATUS status, const std::string& msg);
+
+  auto SecurityStatus() const -> SECURITY_STATUS;
+
+private:
+  SECURITY_STATUS status_;
+};
+
+} // vereign::ncrypt
+
+#endif // __VEREIGN_NCRYPT_ERRORS_HH
diff --git a/cpp/src/vereign/ncrypt/rsa.cc b/cpp/src/vereign/ncrypt/rsa.cc
new file mode 100644
index 0000000000000000000000000000000000000000..d745434813e1c82f256267047f48e4ef0b716a21
--- /dev/null
+++ b/cpp/src/vereign/ncrypt/rsa.cc
@@ -0,0 +1,173 @@
+#include <vereign/ncrypt/rsa.hh>
+
+#include <vereign/ncrypt/errors.hh>
+#include <vereign/core/string.hh>
+
+namespace vereign::ncrypt::rsa {
+
+auto OpenStorageProvider() -> UniquePtr {
+  UniquePtr provider{};
+  auto status = NCryptOpenStorageProvider(provider.Ref(), MS_KEY_STORAGE_PROVIDER, 0);
+  if (status != ERROR_SUCCESS) {
+    throw Error{status, "open crypto store failed"};
+  }
+
+  return provider;
+}
+
+auto LoadKey(NCRYPT_PROV_HANDLE provider, const std::string& key_name) -> UniquePtr {
+  UniquePtr key{};
+  auto wkey_name = string::widen(key_name);
+  auto status = NCryptOpenKey(provider, key.Ref(), wkey_name.data(), 0, 0);
+  if (status != ERROR_SUCCESS && status != NTE_BAD_KEYSET) {
+    throw Error{status, "open key failed"};
+  }
+
+  return key;
+}
+
+auto CreateKey(NCRYPT_PROV_HANDLE provider, int bits, const std::string& key_name) -> UniquePtr {
+  UniquePtr key{};
+  auto wkey_name = string::widen(key_name);
+  auto status = NCryptCreatePersistedKey(
+    provider,
+    key.Ref(),
+    BCRYPT_RSA_ALGORITHM,
+    wkey_name.data(),
+    0,
+    0
+  );
+  if (status != ERROR_SUCCESS) {
+    throw Error{status, "creating rsa key failed"};
+  }
+
+  auto key_len = DWORD(bits);
+  status = NCryptSetProperty(
+    key.Get(),
+    NCRYPT_LENGTH_PROPERTY,
+    (PBYTE)&key_len,
+    sizeof(key_len),
+    NCRYPT_PERSIST_FLAG
+  );
+  if (status != ERROR_SUCCESS) {
+    throw Error{status, "setup rsa key length failed"};
+  }
+
+  // FIXME: add the ui policy
+  // NCRYPT_UI_POLICY ui_policy{};
+  // ui_policy.dwVersion = 1;
+  // ui_policy.dwFlags = NCRYPT_UI_PROTECT_KEY_FLAG;
+  // ui_policy.pszCreationTitle = L"Vereign Client";
+  // ui_policy.pszDescription =
+    // L"Vereign Client will use this key to authenticate with the Vereign Services";
+  // ui_policy.pszFriendlyName = L"Vereign Client Identity Key";
+
+  // status = NCryptSetProperty(
+    // key.Get(),
+    // NCRYPT_UI_POLICY_PROPERTY,
+    // (PBYTE)&ui_policy,
+    // sizeof(ui_policy),
+    // NCRYPT_PERSIST_FLAG
+  // );
+  // if (status != ERROR_SUCCESS) {
+    // throw Error{status, "configure key ui policy failed"};
+  // }
+
+  status = NCryptFinalizeKey(key.Get(), 0);
+  if (status != ERROR_SUCCESS) {
+    throw Error{status, "finalizing rsa key failed"};
+  }
+
+  return key;
+}
+
+void DeleteKey(NCRYPT_KEY_HANDLE key) {
+  auto status = NCryptDeleteKey(key, 0);
+  if (status != ERROR_SUCCESS) {
+    throw Error{status, "deleting key failed"};
+  }
+}
+
+void PublicKeyEncrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& encrypted) {
+  BCRYPT_OAEP_PADDING_INFO pad;
+  int flags = NCRYPT_PAD_OAEP_FLAG;
+  pad.pszAlgId = BCRYPT_SHA1_ALGORITHM;
+  pad.pbLabel = nullptr;
+  pad.cbLabel = 0;
+  DWORD size;
+
+  auto status = NCryptEncrypt(
+    key,
+    (PBYTE)src.Data(),
+    (DWORD)src.Size(),
+    &pad,
+    nullptr,
+    0,
+    &size,
+    flags
+  );
+  if (status != ERROR_SUCCESS) {
+    throw Error{status, "encryption failed"};
+  }
+
+  encrypted.Reserve(size);
+
+  status = NCryptEncrypt(
+    key,
+    (PBYTE)src.Data(),
+    (DWORD)src.Size(),
+    &pad,
+    (PBYTE)encrypted.end(),
+    size,
+    &size,
+    flags
+  );
+  if (status != ERROR_SUCCESS) {
+    throw Error{status, "encryption failed"};
+  }
+
+  encrypted.IncSize(size);
+}
+
+void PrivateKeyDecrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& decrypted) {
+  BCRYPT_OAEP_PADDING_INFO pad;
+  int flags = NCRYPT_PAD_OAEP_FLAG;
+  pad.pszAlgId = BCRYPT_SHA1_ALGORITHM;
+  pad.pbLabel = nullptr;
+  pad.cbLabel = 0;
+  DWORD size;
+
+  auto status = NCryptDecrypt(
+    key,
+    (PBYTE)src.Data(),
+    (DWORD)src.Size(),
+    &pad,
+    nullptr,
+    0,
+    &size,
+    flags
+  );
+  if (status != ERROR_SUCCESS) {
+    throw Error{status, "decryption failed"};
+  }
+
+  decrypted.Reserve(size);
+
+  status = NCryptDecrypt(
+    key,
+    (PBYTE)src.Data(),
+    (DWORD)src.Size(),
+    &pad,
+    (PBYTE)decrypted.end(),
+    size,
+    &size,
+    flags
+  );
+  if (status != ERROR_SUCCESS) {
+    throw Error{status, "decryption failed"};
+  }
+
+  decrypted.IncSize(size);
+}
+
+} // vereign::ncrypt::rsa
diff --git a/cpp/src/vereign/ncrypt/rsa.hh b/cpp/src/vereign/ncrypt/rsa.hh
new file mode 100644
index 0000000000000000000000000000000000000000..749eb512eca488b08b33df38eb0dbd24f8f2fd57
--- /dev/null
+++ b/cpp/src/vereign/ncrypt/rsa.hh
@@ -0,0 +1,23 @@
+#ifndef __VEREIGN_NCRYPT_RSA_HH
+#define __VEREIGN_NCRYPT_RSA_HH
+
+#include <vereign/ncrypt/unique_ptr.hh>
+#include <vereign/bytes/buffer.hh>
+
+#include <windows.h>
+#include <ncrypt.h>
+#include <string>
+
+namespace vereign::ncrypt::rsa {
+
+auto OpenStorageProvider() -> UniquePtr;
+auto LoadKey(NCRYPT_PROV_HANDLE provider, const std::string& key_name) -> UniquePtr;
+auto CreateKey(NCRYPT_PROV_HANDLE provider, int bits, const std::string& key_name) -> UniquePtr;
+void DeleteKey(NCRYPT_KEY_HANDLE key);
+
+void PublicKeyEncrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& encrypted);
+void PrivateKeyDecrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& decrypted);
+
+} // vereign::ncrypt::rsa
+
+#endif // __VEREIGN_NCRYPT_RSA_HH
diff --git a/cpp/src/vereign/ncrypt/unique_ptr.cc b/cpp/src/vereign/ncrypt/unique_ptr.cc
new file mode 100644
index 0000000000000000000000000000000000000000..06e7d2c9239e9b68b4877b7b78ff8621ef897837
--- /dev/null
+++ b/cpp/src/vereign/ncrypt/unique_ptr.cc
@@ -0,0 +1,82 @@
+#include <vereign/ncrypt/unique_ptr.hh>
+
+#include <vereign/ncrypt/errors.hh>
+#include <utility>
+#include <iostream>
+
+namespace vereign::ncrypt {
+
+UniquePtr::UniquePtr() noexcept
+  : ptr_{0}
+{
+}
+
+UniquePtr::UniquePtr(ULONG_PTR ptr) noexcept
+  : ptr_{ptr}
+{
+}
+
+UniquePtr::UniquePtr(UniquePtr&& other) noexcept
+  : ptr_{other.ptr_}
+{
+  other.ptr_ = 0;
+}
+
+auto UniquePtr::operator=(UniquePtr&& other) noexcept -> UniquePtr& {
+  std::swap(ptr_, other.ptr_);
+
+  return *this;
+}
+
+UniquePtr::~UniquePtr() noexcept {
+  if (ptr_ == 0) {
+    return;
+  }
+
+  NCryptFreeObject(ptr_);
+}
+
+auto UniquePtr::Get() const noexcept -> ULONG_PTR {
+  return ptr_;
+}
+
+auto UniquePtr::Ref() noexcept -> ULONG_PTR* {
+  return &ptr_;
+}
+
+void UniquePtr::Reset() {
+  if (ptr_ == 0) {
+    return;
+  }
+
+  auto status = NCryptFreeObject(ptr_);
+  if (status == NTE_INVALID_HANDLE) {
+    throw Error(status, "free object failed");
+  }
+
+  ptr_ = 0;
+}
+
+auto UniquePtr::Release() -> ULONG_PTR {
+  if (ptr_ == 0) {
+    return 0;
+  }
+
+  auto result = ptr_;
+
+  auto status = NCryptFreeObject(ptr_);
+  if (status == NTE_INVALID_HANDLE) {
+    throw Error(status, "free object failed");
+  }
+
+  ptr_ = 0;
+
+  return result;
+}
+
+
+UniquePtr::operator bool() const noexcept {
+  return ptr_ != 0;
+}
+
+} // vereign::ncrypt
diff --git a/cpp/src/vereign/ncrypt/unique_ptr.hh b/cpp/src/vereign/ncrypt/unique_ptr.hh
new file mode 100644
index 0000000000000000000000000000000000000000..8341e3b08bbd08848a06c0931067ac23848e03e1
--- /dev/null
+++ b/cpp/src/vereign/ncrypt/unique_ptr.hh
@@ -0,0 +1,33 @@
+#ifndef __VEREIGN_NCRYPT_UNIQUE_PTR_HH
+#define __VEREIGN_NCRYPT_UNIQUE_PTR_HH
+
+#include <windows.h>
+
+namespace vereign::ncrypt {
+
+class UniquePtr {
+public:
+  UniquePtr() noexcept;
+  explicit UniquePtr(ULONG_PTR ptr) noexcept;
+
+  UniquePtr(UniquePtr&& other) noexcept;
+  auto operator=(UniquePtr&& other) noexcept -> UniquePtr&;
+
+  UniquePtr(const UniquePtr&) = delete;
+  auto operator=(const UniquePtr&) -> UniquePtr&  = delete;
+
+  ~UniquePtr() noexcept ;
+
+  auto Get() const noexcept -> ULONG_PTR;
+  auto Ref() noexcept -> ULONG_PTR*;
+  void Reset();
+  auto Release() -> ULONG_PTR;
+  operator bool() const noexcept;
+
+private:
+  ULONG_PTR ptr_;
+};
+
+} // vereign::ncrypt
+
+#endif // __VEREIGN_NCRYPT_UNIQUE_PTR_HH
diff --git a/cpp/src/vereign/sqlite/connection.cc b/cpp/src/vereign/sqlite/connection.cc
index d5af4f388aff59a068d51de0879176379dead23f..e12fb7c4a85e97df396c12c2d8649290e05b1499 100644
--- a/cpp/src/vereign/sqlite/connection.cc
+++ b/cpp/src/vereign/sqlite/connection.cc
@@ -1,3 +1,4 @@
+#include <stdexcept>
 #include <vereign/sqlite/connection.hh>
 #include <vereign/sqlite/errors.hh>
 
@@ -21,6 +22,18 @@ Transaction::Transaction(Transaction&& other)
   other.finished_ = true;
 }
 
+auto Transaction::operator=(Transaction&& other) -> Transaction& {
+  if (!finished_) {
+    throw std::runtime_error{"cannot move assign to non finished transaction"};
+  }
+
+  db_ = other.db_;
+  finished_ = other.finished_;
+  other.finished_ = true;
+
+  return *this;
+}
+
 void Transaction::Commit() {
   char* errMsg = nullptr;
   auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
diff --git a/cpp/src/vereign/sqlite/connection.hh b/cpp/src/vereign/sqlite/connection.hh
index ffa4ef85b885675e8bd662c9bd436ec492a1a675..86bfd6f839847984f26d0eae3d8ba05ef1f1fbb7 100644
--- a/cpp/src/vereign/sqlite/connection.hh
+++ b/cpp/src/vereign/sqlite/connection.hh
@@ -14,14 +14,14 @@ namespace vereign::sqlite {
 class Transaction {
 private:
   friend class Connection;
-
   explicit Transaction(sqlite3* db);
-  Transaction(Transaction&&);
 
 public:
   void Commit();
   void Rollback();
 
+  Transaction(Transaction&&);
+  auto operator=(Transaction&&) -> Transaction&;
   ~Transaction();
 
   Transaction(const Transaction&) = delete;
diff --git a/cpp/tests/vereign/CMakeLists.txt b/cpp/tests/vereign/CMakeLists.txt
index 31ba5f294ee8062cf252096588ebfa17d9f289f9..172c4032825569568b6a6cba042d4cde96a91360 100644
--- a/cpp/tests/vereign/CMakeLists.txt
+++ b/cpp/tests/vereign/CMakeLists.txt
@@ -1,6 +1,6 @@
 
 if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
-  add_definitions(-DNOGDI)
+  add_definitions(-DNOGDI -DNOMINMAX)
 endif()
 
 include_directories(
@@ -10,7 +10,7 @@ include_directories(
   ${Boost_INCLUDE_DIRS}
 )
 
-list(APPEND tests_src
+list(APPEND TESTS_SRC
   init_tests.cc
   ../util/protobuf.cc
   ../experiment/array.cc
@@ -38,7 +38,14 @@ list(APPEND tests_src
   grpc/server_test.cc
   grpc/json/encoder_test.cc
 )
-add_executable(tests ${tests_src})
+
+if (WIN32)
+  list(APPEND TESTS_SRC
+    ncrypt/rsa_test.cc
+  )
+endif()
+
+add_executable(tests ${TESTS_SRC})
 
 target_proto_generate(
   TARGET tests
diff --git a/cpp/tests/vereign/crypto/rsa_test.cc b/cpp/tests/vereign/crypto/rsa_test.cc
index c9356e1f456547b6137ca8423709e7c75ba9de55..b484d6eca07021b0838f95ad6ef56f3ffc815aed 100644
--- a/cpp/tests/vereign/crypto/rsa_test.cc
+++ b/cpp/tests/vereign/crypto/rsa_test.cc
@@ -11,7 +11,7 @@ using namespace vereign;
 
 TEST_CASE("crypto::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/crypto/rsa][vereign/crypto]") {
   SECTION("small input") {
-    auto key = crypto::rsa::GenerateKey(4096);
+    auto key = crypto::rsa::GenerateKey(2048);
 
     const std::string input{"foo bar"};
     bytes::Buffer encrypted;
@@ -25,7 +25,7 @@ TEST_CASE("crypto::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/crypto/rsa
   }
 
   SECTION("zero input") {
-    auto key = crypto::rsa::GenerateKey(4096);
+    auto key = crypto::rsa::GenerateKey(2048);
 
     const std::string input;
     bytes::Buffer encrypted;
@@ -39,9 +39,9 @@ TEST_CASE("crypto::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/crypto/rsa
   }
 
   SECTION("max size input") {
-    auto key = crypto::rsa::GenerateKey(4096);
+    auto key = crypto::rsa::GenerateKey(2048);
 
-    bytes::Buffer input{470};
+    bytes::Buffer input{214};
     crypto::Rand(input);
     bytes::Buffer encrypted;
 
@@ -54,9 +54,9 @@ TEST_CASE("crypto::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/crypto/rsa
   }
 
   SECTION("invalid big input") {
-    auto key = crypto::rsa::GenerateKey(4096);
+    auto key = crypto::rsa::GenerateKey(2048);
 
-    bytes::Buffer input{471};
+    bytes::Buffer input{215};
     crypto::Rand(input);
     bytes::Buffer encrypted;
 
diff --git a/cpp/tests/vereign/ncrypt/rsa_test.cc b/cpp/tests/vereign/ncrypt/rsa_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0c3c4aae1143630b32bb4540560d2374b5f0b63b
--- /dev/null
+++ b/cpp/tests/vereign/ncrypt/rsa_test.cc
@@ -0,0 +1,211 @@
+#include <vereign/crypto/rand.hh>
+#include <vereign/ncrypt/rsa.hh>
+
+#include <vereign/core/scope_guard.hh>
+#include <vereign/ncrypt/unique_ptr.hh>
+#include <vereign/bytes/view_dump.hh>
+
+#include <catch2/catch.hpp>
+#include <iostream>
+
+using namespace vereign;
+
+TEST_CASE("ncrypt::rsa::OpenStorageProvider", "[vereign/ncrypt/rsa][vereign/ncrypt]") {
+  auto provider = ncrypt::rsa::OpenStorageProvider();
+  CHECK(provider.Get() != 0);
+}
+
+TEST_CASE("ncrypt::CreateKey", "[vereign/ncrypt/rsa][vereign/ncrypt]") {
+  const auto test_key = std::string{"vereign_test_key"};
+
+  SECTION("when the key does not exists, it must create a new key") {
+    auto provider = ncrypt::rsa::OpenStorageProvider();
+
+    // cleanup
+    auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
+    if (key) {
+      ncrypt::rsa::DeleteKey(key.Get());
+    }
+
+    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key);
+    CHECK(new_key.Get() != 0);
+
+    key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
+    CHECK(key.Get() != 0);
+
+    // cleanup
+    ncrypt::rsa::DeleteKey(key.Get());
+  }
+
+  SECTION("when the key already exists, it must fail with NTE_EXISTS") {
+    auto provider = ncrypt::rsa::OpenStorageProvider();
+
+    // cleanup
+    auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
+    if (key) {
+      ncrypt::rsa::DeleteKey(key.Get());
+    }
+
+    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key);
+    CHECK(new_key.Get() != 0);
+
+    CHECK_THROWS_WITH(
+      ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key),
+      "creating rsa key failed: NTE_EXISTS"
+    );
+
+    key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
+    CHECK(key.Get() != 0);
+
+    // cleanup
+    ncrypt::rsa::DeleteKey(key.Get());
+  }
+}
+
+TEST_CASE("ncrypt::rsa::LoadKey", "[vereign/ncrypt/rsa][vereign/ncrypt]") {
+  const auto test_key = std::string{"vereign_test_key"};
+
+  SECTION("when the key exists, it must load the key") {
+    auto provider = ncrypt::rsa::OpenStorageProvider();
+
+    // cleanup
+    auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
+    if (key) {
+      ncrypt::rsa::DeleteKey(key.Get());
+    }
+
+    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key);
+    CHECK(new_key.Get() != 0);
+
+    key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
+    CHECK(key.Get() != 0);
+
+    // cleanup
+    ncrypt::rsa::DeleteKey(key.Get());
+  }
+
+  SECTION("when the key does not exists, it must fail") {
+    auto provider = ncrypt::rsa::OpenStorageProvider();
+
+    // cleanup
+    auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
+    if (key) {
+      ncrypt::rsa::DeleteKey(key.Get());
+    }
+
+    key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
+    CHECK(key.Get() == 0);
+  }
+}
+
+TEST_CASE("ncrypt::rsa::DeleteKey", "[vereign/ncrypt/rsa][vereign/ncrypt]") {
+  const auto test_key = std::string{"vereign_test_key"};
+
+  SECTION("when the key exists, it must delete the key") {
+    auto provider = ncrypt::rsa::OpenStorageProvider();
+
+    // cleanup
+    auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
+    if (key) {
+      ncrypt::rsa::DeleteKey(key.Get());
+    }
+
+    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key);
+    CHECK(new_key.Get() != 0);
+
+    ncrypt::rsa::DeleteKey(new_key.Get());
+
+    key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
+    CHECK(key.Get() == 0);
+  }
+
+  SECTION("when the key does not exists, it must fail") {
+    auto provider = ncrypt::rsa::OpenStorageProvider();
+
+    // cleanup
+    auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
+    if (key) {
+      ncrypt::rsa::DeleteKey(key.Get());
+    }
+
+    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key);
+    CHECK(new_key.Get() != 0);
+
+    key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
+    CHECK(key.Get() != 0);
+
+    ncrypt::rsa::DeleteKey(new_key.Get());
+
+    CHECK_THROWS_WITH(
+      ncrypt::rsa::DeleteKey(key.Get()),
+      "deleting key failed: NTE_BAD_KEYSET"
+    );
+  }
+}
+
+TEST_CASE("ncrypt::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/ncrypt/rsa][vereign/ncrypt]") {
+  const auto test_key = std::string{"vereign_test_key"};
+  auto provider = ncrypt::rsa::OpenStorageProvider();
+  auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
+  if (key) {
+    ncrypt::rsa::DeleteKey(key.Get());
+  }
+
+  key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key);
+  REQUIRE(key.Get() != 0);
+  auto delete_key = core::ScopeGuard([&key] { ncrypt::rsa::DeleteKey(key.Get()); });
+
+  SECTION("small input") {
+    const std::string input{"foo bar"};
+    bytes::Buffer encrypted;
+
+    ncrypt::rsa::PublicKeyEncrypt(key.Get(), bytes::View(input), encrypted);
+
+    bytes::Buffer decrypted;
+    ncrypt::rsa::PrivateKeyDecrypt(key.Get(), encrypted.View(), decrypted);
+
+    CHECK(decrypted.View() == bytes::View(input));
+  }
+
+  SECTION("zero input") {
+    const std::string input;
+    bytes::Buffer encrypted;
+
+    CHECK_THROWS_WITH(
+      ncrypt::rsa::PublicKeyEncrypt(key.Get(), bytes::View(input), encrypted),
+      "encryption failed: NTE_NULL_REFERENCE_POINTER"
+    );
+
+    bytes::Buffer decrypted;
+    CHECK_THROWS_WITH(
+      ncrypt::rsa::PrivateKeyDecrypt(key.Get(), encrypted.View(), decrypted),
+      "decryption failed: NTE_NULL_REFERENCE_POINTER"
+    );
+  }
+
+  SECTION("max size input") {
+    bytes::Buffer input{214};
+    crypto::Rand(input);
+    bytes::Buffer encrypted;
+
+    ncrypt::rsa::PublicKeyEncrypt(key.Get(), input.View(), encrypted);
+
+    bytes::Buffer decrypted;
+    ncrypt::rsa::PrivateKeyDecrypt(key.Get(), encrypted.View(), decrypted);
+
+    CHECK(decrypted.View() == input.View());
+  }
+
+  SECTION("invalid big input") {
+    bytes::Buffer input{215};
+    crypto::Rand(input);
+    bytes::Buffer encrypted;
+
+    CHECK_THROWS_WITH(
+      ncrypt::rsa::PublicKeyEncrypt(key.Get(), input.View(), encrypted),
+      "encryption failed: NTE_INVALID_PARAMETER"
+    );
+
+    CHECK(encrypted.Size() == 0);
+  }
+}