From e7f688327135e7261d6f9a43304de842533dc80d Mon Sep 17 00:00:00 2001
From: Holger Nahrstaedt <holgernahrstaedt@gmx.de>
Date: Thu, 18 Jun 2020 13:47:26 +0200
Subject: [PATCH] Preparing release 0.24.0

* new beemstorage module
* Config is handled by SqliteConfigurationStore or InRamConfigurationStore
* Keys are handled by SqliteEncryptedKeyStore or InRamPlainKeyStore
* Move aes to beemgraphenebase
* Wallet.keys, Wallet.keyStorage, Wallet.token and Wallet.keyMap has been removed
* Wallet.store has now the Key Interface that handles key management
* Token handling has been removed from Wallet
* Token storage has been move from wallet to SteemConnect/HiveSigner
---
 CHANGELOG.rst                            |  11 +
 beem/__init__.py                         |   1 -
 beem/blockchaininstance.py               |   5 +-
 beem/cli.py                              |  64 +-
 beem/exceptions.py                       |   6 -
 beem/hive.py                             |  15 -
 beem/hivesigner.py                       | 135 ++++-
 beem/instance.py                         |   4 +-
 beem/message.py                          |   1 -
 beem/steem.py                            |  16 -
 beem/steemconnect.py                     | 135 ++++-
 beem/storage.py                          | 718 ++---------------------
 beem/transactionbuilder.py               |   2 +-
 beem/version.py                          |   2 +-
 beem/wallet.py                           | 478 +++++----------
 beemapi/version.py                       |   2 +-
 beembase/version.py                      |   2 +-
 beemgraphenebase/__init__.py             |   1 +
 {beem => beemgraphenebase}/aes.py        |   0
 beemgraphenebase/bip38.py                |   5 +
 beemgraphenebase/version.py              |   2 +-
 beemstorage/__init__.py                  |  38 ++
 beemstorage/base.py                      | 302 ++++++++++
 beemstorage/exceptions.py                |  18 +
 beemstorage/interfaces.py                | 257 ++++++++
 beemstorage/masterpassword.py            | 243 ++++++++
 beemstorage/ram.py                       |  32 +
 beemstorage/sqlite.py                    | 352 +++++++++++
 docs/beem.aes.rst                        |   7 -
 docs/beemgraphenebase.aes.rst            |   7 +
 docs/beemstorage.base.rst                |   7 +
 docs/beemstorage.exceptions.rst          |   7 +
 docs/beemstorage.interfaces.rst          |   7 +
 docs/beemstorage.masterpassword.rst      |   7 +
 docs/beemstorage.ram.rst                 |   7 +
 docs/beemstorage.sqlite.rst              |   7 +
 docs/modules.rst                         |  17 +-
 setup.py                                 |   2 +-
 tests/beem/test_aes.py                   |   2 +-
 tests/beem/test_connection.py            |  14 +-
 tests/beem/test_hive.py                  |  16 +-
 tests/beem/test_steem.py                 |  17 +-
 tests/beem/test_storage.py               |   2 +-
 tests/beem/test_testnet.py               |   4 +-
 tests/beem/test_txbuffers.py             |  10 +-
 tests/beem/test_wallet.py                |  27 -
 tests/beemapi/test_noderpc.py            |   4 +-
 tests/beemstorage/__init__.py            |   0
 tests/beemstorage/test_keystorage.py     | 111 ++++
 tests/beemstorage/test_masterpassword.py |  74 +++
 tests/beemstorage/test_sqlite.py         |  41 ++
 51 files changed, 2082 insertions(+), 1162 deletions(-)
 rename {beem => beemgraphenebase}/aes.py (100%)
 create mode 100644 beemstorage/__init__.py
 create mode 100644 beemstorage/base.py
 create mode 100644 beemstorage/exceptions.py
 create mode 100644 beemstorage/interfaces.py
 create mode 100644 beemstorage/masterpassword.py
 create mode 100644 beemstorage/ram.py
 create mode 100644 beemstorage/sqlite.py
 delete mode 100644 docs/beem.aes.rst
 create mode 100644 docs/beemgraphenebase.aes.rst
 create mode 100644 docs/beemstorage.base.rst
 create mode 100644 docs/beemstorage.exceptions.rst
 create mode 100644 docs/beemstorage.interfaces.rst
 create mode 100644 docs/beemstorage.masterpassword.rst
 create mode 100644 docs/beemstorage.ram.rst
 create mode 100644 docs/beemstorage.sqlite.rst
 create mode 100644 tests/beemstorage/__init__.py
 create mode 100644 tests/beemstorage/test_keystorage.py
 create mode 100644 tests/beemstorage/test_masterpassword.py
 create mode 100644 tests/beemstorage/test_sqlite.py

diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index ed08ff4c..c2699566 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,5 +1,16 @@
 Changelog
 =========
+0.24.0
+------
+* new beemstorage module
+* Config is handled by SqliteConfigurationStore or InRamConfigurationStore
+* Keys are handled by SqliteEncryptedKeyStore or InRamPlainKeyStore
+* Move aes to beemgraphenebase
+* Wallet.keys, Wallet.keyStorage, Wallet.token and Wallet.keyMap has been removed
+* Wallet.store has now the Key Interface that handles key management
+* Token handling has been removed from Wallet
+* Token storage has been move from wallet to SteemConnect/HiveSigner
+
 0.23.13
 -------
 * receiver parameter removed from beempy decrypt 
diff --git a/beem/__init__.py b/beem/__init__.py
index 2a406665..5ce33201 100644
--- a/beem/__init__.py
+++ b/beem/__init__.py
@@ -4,7 +4,6 @@ from .hive import Hive
 from .version import version as __version__
 __all__ = [
     "steem",
-    "aes",
     "account",
     "amount",
     "asset",
diff --git a/beem/blockchaininstance.py b/beem/blockchaininstance.py
index f1bff90d..4eaf9fe0 100644
--- a/beem/blockchaininstance.py
+++ b/beem/blockchaininstance.py
@@ -15,14 +15,13 @@ import time
 from beemgraphenebase.py23 import bytes_types, integer_types, string_types, text_type
 from datetime import datetime, timedelta, date
 from beemapi.noderpc import NodeRPC
-from beemapi.exceptions import NoAccessApi, NoApiWithName
 from beemgraphenebase.account import PrivateKey, PublicKey
 from beembase import transactions, operations
 from beemgraphenebase.chains import known_chains
+from .storage import get_default_config_store
 from .account import Account
 from .amount import Amount
 from .price import Price
-from .storage import get_default_config_storage
 from .version import version as beem_version
 from .exceptions import (
     AccountExistsException,
@@ -193,7 +192,7 @@ class BlockChainInstance(object):
         self.path = kwargs.get("path", None)
 
         # Store config for access through other Classes
-        self.config = get_default_config_storage()
+        self.config = kwargs.get("config_store", get_default_config_store(**kwargs))
         if self.path is None:
             self.path = self.config["default_path"]
 
diff --git a/beem/cli.py b/beem/cli.py
index 44ffcc14..ed2c9d2c 100644
--- a/beem/cli.py
+++ b/beem/cli.py
@@ -108,7 +108,7 @@ def unlock_wallet(stm, password=None, allow_wif=True):
         return True
     if not stm.wallet.locked():
         return True
-    if len(stm.wallet.keys) > 0:
+    if not stm.wallet.store.is_encrypted():
         return True
     password_storage = stm.config["password_storage"]
     if not password and KEYRING_AVAILABLE and password_storage == "keyring":
@@ -151,6 +151,45 @@ def unlock_wallet(stm, password=None, allow_wif=True):
         return True
 
 
+def unlock_token_wallet(stm, sc2, password=None):
+    if stm.unsigned and stm.nobroadcast:
+        return True
+    if stm.use_ledger:
+        return True
+    if not sc2.locked():
+        return True
+    if not sc2.store.is_encrypted():
+        return True
+    password_storage = stm.config["password_storage"]
+    if not password and KEYRING_AVAILABLE and password_storage == "keyring":
+        password = keyring.get_password("beem", "wallet")
+    if not password and password_storage == "environment" and "UNLOCK" in os.environ:
+        password = os.environ.get("UNLOCK")
+    if bool(password):
+        sc2.unlock(password)
+    else:
+        password = click.prompt("Password to unlock wallet", confirmation_prompt=False, hide_input=True)
+        try:
+            sc2.unlock(password)
+        except:
+            raise exceptions.WrongMasterPasswordException("entered password is not a valid password")
+
+    if sc2.locked():
+        if password_storage == "keyring" or password_storage == "environment":
+            print("Wallet could not be unlocked with %s!" % password_storage)
+            password = click.prompt("Password to unlock wallet", confirmation_prompt=False, hide_input=True)
+            if bool(password):
+                unlock_token_wallet(stm, password=password)
+                if not sc2.locked():
+                    return True
+        else:
+            print("Wallet could not be unlocked!")
+        return False
+    else:
+        print("Wallet Unlocked!")
+        return True
+
+
 @shell(prompt='beempy> ', intro='Starting beempy... (use help to list all commands)', chain=True)
 # @click.group(chain=True)
 @click.option(
@@ -561,6 +600,7 @@ def createwallet(wipe):
         password = keyring.set_password("beem", "wallet", password)
     elif password_storage == "environment":
         print("The new wallet password can be stored in the UNLOCK environment variable to skip password prompt!")
+    stm.wallet.wipe(True)
     stm.wallet.create(password)
     set_shared_blockchain_instance(stm)
 
@@ -584,7 +624,7 @@ def walletinfo(unlock, lock):
     t.add_row(["created", stm.wallet.created()])
     t.add_row(["locked", stm.wallet.locked()])
     t.add_row(["Number of stored keys", len(stm.wallet.getPublicKeys())])
-    t.add_row(["sql-file", stm.wallet.keyStorage.sqlDataBaseFile])
+    t.add_row(["sql-file", stm.wallet.store.sqlite_file])
     password_storage = stm.config["password_storage"]
     t.add_row(["password_storage", password_storage])
     password = os.environ.get("UNLOCK")
@@ -883,11 +923,12 @@ def addtoken(name, unsafe_import_token):
     stm = shared_blockchain_instance()
     if stm.rpc is not None:
         stm.rpc.rpcconnect()
-    if not unlock_wallet(stm):
-        return
+    sc2 = SteemConnect(blockchain_instance=stm)
+    if not unlock_token_wallet(stm, sc2):
+        return    
     if not unsafe_import_token:
         unsafe_import_token = click.prompt("Enter private token", confirmation_prompt=False, hide_input=True)
-    stm.wallet.addToken(name, unsafe_import_token)
+    sc2.addToken(name, unsafe_import_token)
     set_shared_blockchain_instance(stm)
 
 
@@ -906,9 +947,10 @@ def deltoken(confirm, name):
     stm = shared_blockchain_instance()
     if stm.rpc is not None:
         stm.rpc.rpcconnect()
-    if not unlock_wallet(stm):
-        return
-    stm.wallet.removeTokenFromPublicName(name)
+    sc2 = SteemConnect(blockchain_instance=stm)
+    if not unlock_token_wallet(stm, sc2):
+        return    
+    sc2.removeTokenFromPublicName(name)
     set_shared_blockchain_instance(stm)
 
 
@@ -950,10 +992,10 @@ def listtoken():
     stm = shared_blockchain_instance()
     t = PrettyTable(["name", "scope", "status"])
     t.align = "l"
-    if not unlock_wallet(stm):
-        return
     sc2 = SteemConnect(blockchain_instance=stm)
-    for name in stm.wallet.getPublicNames():
+    if not unlock_token_wallet(stm, sc2):
+        return    
+    for name in sc2.getPublicNames():
         ret = sc2.me(username=name)
         if "error" in ret:
             t.add_row([name, "-", ret["error"]])
diff --git a/beem/exceptions.py b/beem/exceptions.py
index 0586ee6e..baee5db5 100644
--- a/beem/exceptions.py
+++ b/beem/exceptions.py
@@ -12,12 +12,6 @@ class WalletExists(Exception):
     pass
 
 
-class WalletLocked(Exception):
-    """ Wallet is locked
-    """
-    pass
-
-
 class RPCConnectionRequired(Exception):
     """ An RPC connection is required
     """
diff --git a/beem/hive.py b/beem/hive.py
index d2f411fb..ab383744 100644
--- a/beem/hive.py
+++ b/beem/hive.py
@@ -14,24 +14,9 @@ import ast
 import time
 from beemgraphenebase.py23 import bytes_types, integer_types, string_types, text_type
 from datetime import datetime, timedelta, date
-from beemapi.noderpc import NodeRPC
-from beemapi.exceptions import NoAccessApi, NoApiWithName
-from beemgraphenebase.account import PrivateKey, PublicKey
 from beembase import transactions, operations
 from beemgraphenebase.chains import known_chains
-from .account import Account
 from .amount import Amount
-from .price import Price
-from .storage import get_default_config_storage
-from .version import version as beem_version
-from .exceptions import (
-    AccountExistsException,
-    AccountDoesNotExistsException
-)
-from .wallet import Wallet
-from .steemconnect import SteemConnect
-from .hivesigner import HiveSigner
-from .transactionbuilder import TransactionBuilder
 from beem.blockchaininstance import BlockChainInstance
 from .utils import formatTime, resolve_authorperm, derive_permlink, sanitize_permlink, remove_from_dict, addTzInfo, formatToTimeStamp
 from beem.constants import STEEM_VOTE_REGENERATION_SECONDS, STEEM_100_PERCENT, STEEM_1_PERCENT, STEEM_RC_REGEN_TIME
diff --git a/beem/hivesigner.py b/beem/hivesigner.py
index 3a210501..80223920 100644
--- a/beem/hivesigner.py
+++ b/beem/hivesigner.py
@@ -11,10 +11,17 @@ except ImportError:
     from urlparse import urlparse, urljoin
     from urllib import urlencode
 import requests
-from .storage import get_default_config_storage
+import logging
 from six import PY2
 from beem.instance import shared_blockchain_instance
 from beem.amount import Amount
+from beem.exceptions import (
+    MissingKeyError,
+    WalletExists
+)
+from beemstorage.exceptions import KeyAlreadyInStoreException, WalletLocked
+
+log = logging.getLogger(__name__)
 
 
 class HiveSigner(object):
@@ -94,10 +101,130 @@ class HiveSigner(object):
         self.hs_oauth_base_url = kwargs.get("hs_oauth_base_url", config["hs_oauth_base_url"])
         self.hs_api_url = kwargs.get("hs_api_url", config["hs_api_url"])
 
+        if "token" in kwargs and len(kwargs["token"]) > 0:
+            from beemstorage import InRamPlainTokenStore
+            self.store = InRamPlainTokenStore()
+            self.setToken(kwargs["keys"])
+        else:
+            """ If no keys are provided manually we load the SQLite
+                keyStorage
+            """
+            from beemstorage import SqliteEncryptedTokenStore
+            self.store = kwargs.get(
+                "token_store",
+                SqliteEncryptedTokenStore(config=config, **kwargs),
+            )
+
     @property
     def headers(self):
         return {'Authorization': self.access_token}
 
+    def setToken(self, loadtoken):
+        """ This method is strictly only for in memory token that are
+            passed to Wallet/Steem with the ``token`` argument
+        """
+        log.debug(
+            "Force setting of private token. Not using the wallet database!")
+        if not isinstance(loadtoken, (set)):
+            raise ValueError("token must be a dict variable!")
+        for name in loadtoken:
+            self.store.add(loadtoken[name], name)
+
+    def is_encrypted(self):
+        """ Is the key store encrypted?
+        """
+        return self.store.is_encrypted()
+
+    def unlock(self, pwd):
+        """ Unlock the wallet database
+        """
+        unlock_ok = None
+        if self.store.is_encrypted():
+            unlock_ok = self.store.unlock(pwd)
+        return unlock_ok
+
+    def lock(self):
+        """ Lock the wallet database
+        """
+        lock_ok = False
+        if self.store.is_encrypted():
+            lock_ok =  self.store.lock()       
+        return lock_ok
+
+    def unlocked(self):
+        """ Is the wallet database unlocked?
+        """
+        unlocked = True
+        if self.store.is_encrypted():
+            unlocked = not self.store.locked()   
+        return unlocked
+
+    def locked(self):
+        """ Is the wallet database locked?
+        """
+        if self.store.is_encrypted():
+            return self.store.locked()
+        else:
+            return False
+
+    def changePassphrase(self, new_pwd):
+        """ Change the passphrase for the wallet database
+        """
+        self.store.change_password(new_pwd)
+
+    def created(self):
+        """ Do we have a wallet database already?
+        """
+        if len(self.store.getPublicKeys()):
+            # Already keys installed
+            return True
+        else:
+            return False
+
+    def create(self, pwd):
+        """ Alias for :func:`newWallet`
+
+            :param str pwd: Passphrase for the created wallet
+        """
+        self.newWallet(pwd)
+
+    def newWallet(self, pwd):
+        """ Create a new wallet database
+
+            :param str pwd: Passphrase for the created wallet
+        """
+        if self.created():
+            raise WalletExists("You already have created a wallet!")
+        self.store.unlock(pwd)
+
+    def addToken(self, name, token):
+        if str(name) in self.store:
+            raise KeyAlreadyInStoreException("Token already in the store")
+        self.store.add(str(token), str(name))
+
+    def getTokenForAccountName(self, name):
+        """ Obtain the private token for a given public name
+
+            :param str name: Public name
+        """      
+        if str(name) not in self.store:
+            raise MissingKeyError
+        return self.store.getPrivateKeyForPublicKey(str(name))
+
+    def removeTokenFromPublicName(self, name):
+        """ Remove a token from the wallet database
+
+            :param str name: token to be removed
+        """
+        self.store.delete(str(name))
+
+    def getPublicNames(self):
+        """ Return all installed public token
+        """
+        if self.store is None:
+            return
+        return self.store.getPublicNames()
+
     def get_login_url(self, redirect_uri, **kwargs):
         """ Returns a login url for receiving token from HiveSigner
         """
@@ -127,7 +254,7 @@ class HiveSigner(object):
             "grant_type": "authorization_code",
             "code": code,
             "client_id": self.client_id,
-            "client_secret": self.blockchain.wallet.getTokenForAccountName(self.client_id),
+            "client_secret": self.getTokenForAccountName(self.client_id),
         }
 
         r = requests.post(
@@ -166,7 +293,7 @@ class HiveSigner(object):
         if permission != "posting":
             self.access_token = None
             return
-        self.access_token = self.blockchain.wallet.getTokenForAccountName(username)
+        self.access_token = self.getTokenForAccountName(username)
 
     def broadcast(self, operations, username=None):
         """ Broadcast an operation
@@ -210,7 +337,7 @@ class HiveSigner(object):
             "grant_type": "refresh_token",
             "refresh_token": code,
             "client_id": self.client_id,
-            "client_secret": self.blockchain.wallet.getTokenForAccountName(self.client_id),
+            "client_secret": self.getTokenForAccountName(self.client_id),
             "scope": scope,
         }
 
diff --git a/beem/instance.py b/beem/instance.py
index fb9e6ee4..bccf638f 100644
--- a/beem/instance.py
+++ b/beem/instance.py
@@ -30,8 +30,8 @@ def shared_blockchain_instance():
     """
     if not SharedInstance.instance:
         clear_cache()
-        from beem.storage import get_default_config_storage
-        default_chain = get_default_config_storage()["default_chain"]
+        from beem.storage import get_default_config_store
+        default_chain = get_default_config_store()["default_chain"]
         if default_chain == "steem":
             SharedInstance.instance = beem.Steem(**SharedInstance.config)
         else:
diff --git a/beem/message.py b/beem/message.py
index c54ae9ab..597ef9b4 100644
--- a/beem/message.py
+++ b/beem/message.py
@@ -14,7 +14,6 @@ from beemgraphenebase.account import PublicKey
 from beem.instance import shared_blockchain_instance
 from beem.account import Account
 from .exceptions import InvalidMessageSignature, WrongMemoKey, AccountDoesNotExistsException, InvalidMemoKeyException
-from .storage import get_default_config_storage
 
 
 log = logging.getLogger(__name__)
diff --git a/beem/steem.py b/beem/steem.py
index 5fea22ed..541a977e 100644
--- a/beem/steem.py
+++ b/beem/steem.py
@@ -14,24 +14,8 @@ import ast
 import time
 from beemgraphenebase.py23 import bytes_types, integer_types, string_types, text_type
 from datetime import datetime, timedelta, date
-from beemapi.noderpc import NodeRPC
-from beemapi.exceptions import NoAccessApi, NoApiWithName
-from beemgraphenebase.account import PrivateKey, PublicKey
-from beembase import transactions, operations
 from beemgraphenebase.chains import known_chains
-from .account import Account
 from .amount import Amount
-from .price import Price
-from .storage import get_default_config_storage
-from .version import version as beem_version
-from .exceptions import (
-    AccountExistsException,
-    AccountDoesNotExistsException
-)
-from .wallet import Wallet
-from .steemconnect import SteemConnect
-from .hivesigner import HiveSigner
-from .transactionbuilder import TransactionBuilder
 from .utils import formatTime, resolve_authorperm, derive_permlink, sanitize_permlink, remove_from_dict, addTzInfo, formatToTimeStamp
 from beem.constants import STEEM_VOTE_REGENERATION_SECONDS, STEEM_100_PERCENT, STEEM_1_PERCENT, STEEM_RC_REGEN_TIME
 from beem.blockchaininstance import BlockChainInstance
diff --git a/beem/steemconnect.py b/beem/steemconnect.py
index afc02a40..45c6ecd3 100644
--- a/beem/steemconnect.py
+++ b/beem/steemconnect.py
@@ -11,10 +11,17 @@ except ImportError:
     from urlparse import urlparse, urljoin
     from urllib import urlencode
 import requests
-from .storage import get_default_config_storage
+import logging
 from six import PY2
 from beem.instance import shared_blockchain_instance
 from beem.amount import Amount
+from beem.exceptions import (
+    MissingKeyError,
+    WalletExists
+)
+from beemstorage.exceptions import KeyAlreadyInStoreException, WalletLocked
+
+log = logging.getLogger(__name__)
 
 
 class SteemConnect(object):
@@ -93,11 +100,131 @@ class SteemConnect(object):
         self.scope = kwargs.get("scope", "login")
         self.oauth_base_url = kwargs.get("oauth_base_url", config["oauth_base_url"])
         self.sc2_api_url = kwargs.get("sc2_api_url", config["sc2_api_url"])
+        
+        if "token" in kwargs and len(kwargs["token"]) > 0:
+            from beemstorage import InRamPlainTokenStore
+            self.store = InRamPlainTokenStore()
+            self.setToken(kwargs["keys"])
+        else:
+            """ If no keys are provided manually we load the SQLite
+                keyStorage
+            """
+            from beemstorage import SqliteEncryptedTokenStore
+            self.store = kwargs.get(
+                "token_store",
+                SqliteEncryptedTokenStore(config=config, **kwargs),
+            )        
 
     @property
     def headers(self):
         return {'Authorization': self.access_token}
 
+    def setToken(self, loadtoken):
+        """ This method is strictly only for in memory token that are
+            passed to Wallet/Steem with the ``token`` argument
+        """
+        log.debug(
+            "Force setting of private token. Not using the wallet database!")
+        if not isinstance(loadtoken, (set)):
+            raise ValueError("token must be a dict variable!")
+        for name in loadtoken:
+            self.store.add(loadtoken[name], name)
+
+    def is_encrypted(self):
+        """ Is the key store encrypted?
+        """
+        return self.store.is_encrypted()
+
+    def unlock(self, pwd):
+        """ Unlock the wallet database
+        """
+        unlock_ok = None
+        if self.store.is_encrypted():
+            unlock_ok = self.store.unlock(pwd)
+        return unlock_ok
+
+    def lock(self):
+        """ Lock the wallet database
+        """
+        lock_ok = False
+        if self.store.is_encrypted():
+            lock_ok =  self.store.lock()       
+        return lock_ok
+
+    def unlocked(self):
+        """ Is the wallet database unlocked?
+        """
+        unlocked = True
+        if self.store.is_encrypted():
+            unlocked = not self.store.locked()   
+        return unlocked
+
+    def locked(self):
+        """ Is the wallet database locked?
+        """
+        if self.store.is_encrypted():
+            return self.store.locked()
+        else:
+            return False
+
+    def changePassphrase(self, new_pwd):
+        """ Change the passphrase for the wallet database
+        """
+        self.store.change_password(new_pwd)
+
+    def created(self):
+        """ Do we have a wallet database already?
+        """
+        if len(self.store.getPublicKeys()):
+            # Already keys installed
+            return True
+        else:
+            return False
+
+    def create(self, pwd):
+        """ Alias for :func:`newWallet`
+
+            :param str pwd: Passphrase for the created wallet
+        """
+        self.newWallet(pwd)
+
+    def newWallet(self, pwd):
+        """ Create a new wallet database
+
+            :param str pwd: Passphrase for the created wallet
+        """
+        if self.created():
+            raise WalletExists("You already have created a wallet!")
+        self.store.unlock(pwd)
+
+    def addToken(self, name, token):
+        if str(name) in self.store:
+            raise KeyAlreadyInStoreException("Token already in the store")
+        self.store.add(str(token), str(name))
+
+    def getTokenForAccountName(self, name):
+        """ Obtain the private token for a given public name
+
+            :param str name: Public name
+        """      
+        if str(name) not in self.store:
+            raise MissingKeyError
+        return self.store.getPrivateKeyForPublicKey(str(name))
+
+    def removeTokenFromPublicName(self, name):
+        """ Remove a token from the wallet database
+
+            :param str name: token to be removed
+        """
+        self.store.delete(str(name))
+
+    def getPublicNames(self):
+        """ Return all installed public token
+        """
+        if self.store is None:
+            return
+        return self.store.getPublicNames()
+
     def get_login_url(self, redirect_uri, **kwargs):
         """ Returns a login url for receiving token from steemconnect
         """
@@ -127,7 +254,7 @@ class SteemConnect(object):
             "grant_type": "authorization_code",
             "code": code,
             "client_id": self.client_id,
-            "client_secret": self.steem.wallet.getTokenForAccountName(self.client_id),
+            "client_secret": self.getTokenForAccountName(self.client_id),
         }
 
         r = requests.post(
@@ -166,7 +293,7 @@ class SteemConnect(object):
         if permission != "posting":
             self.access_token = None
             return
-        self.access_token = self.steem.wallet.getTokenForAccountName(username)
+        self.access_token = self.getTokenForAccountName(username)
 
     def broadcast(self, operations, username=None):
         """ Broadcast an operation
@@ -210,7 +337,7 @@ class SteemConnect(object):
             "grant_type": "refresh_token",
             "refresh_token": code,
             "client_id": self.client_id,
-            "client_secret": self.steem.wallet.getTokenForAccountName(self.client_id),
+            "client_secret": self.getTokenForAccountName(self.client_id),
             "scope": scope,
         }
 
diff --git a/beem/storage.py b/beem/storage.py
index 30bc72bc..2b4c3408 100644
--- a/beem/storage.py
+++ b/beem/storage.py
@@ -10,7 +10,7 @@ import shutil
 import time
 import os
 import sqlite3
-from .aes import AESCipher
+from beemgraphenebase.aes import AESCipher
 from appdirs import user_data_dir
 from datetime import datetime
 import logging
@@ -22,684 +22,44 @@ from .nodelist import NodeList
 log = logging.getLogger(__name__)
 log.setLevel(logging.DEBUG)
 log.addHandler(logging.StreamHandler())
+from beemstorage import (
+    SqliteConfigurationStore,
+    SqliteEncryptedKeyStore,
+)
 
 timeformat = "%Y%m%d-%H%M%S"
 
-
-class DataDir(object):
-    """ This class ensures that the user's data is stored in its OS
-        preotected user directory:
-
-        **OSX:**
-
-         * `~/Library/Application Support/<AppName>`
-
-        **Windows:**
-
-         * `C:\\Documents and Settings\\<User>\\Application Data\\Local Settings\\<AppAuthor>\\<AppName>`
-         * `C:\\Documents and Settings\\<User>\\Application Data\\<AppAuthor>\\<AppName>`
-
-        **Linux:**
-
-         * `~/.local/share/<AppName>`
-
-         Furthermore, it offers an interface to generated backups
-         in the `backups/` directory every now and then.
-    """
-    appname = "beem"
-    appauthor = "beem"
-    storageDatabase = "beem.sqlite"
-
-    data_dir = user_data_dir(appname, appauthor)
-    sqlDataBaseFile = os.path.join(data_dir, storageDatabase)
-
-    def __init__(self):
-        #: Storage
-        self.mkdir_p()
-
-    def mkdir_p(self):
-        """ Ensure that the directory in which the data is stored
-            exists
-        """
-        if os.path.isdir(self.data_dir):
-            return
-        else:
-            try:
-                os.makedirs(self.data_dir)
-            except FileExistsError:
-                self.sqlDataBaseFile = ":memory:"
-                return
-            except OSError:
-                self.sqlDataBaseFile = ":memory:"
-                return
-
-    def sqlite3_backup(self, backupdir):
-        """ Create timestamped database copy
-        """
-        if self.sqlDataBaseFile == ":memory:":
-            return
-        if not os.path.isdir(backupdir):
-            os.mkdir(backupdir)
-        backup_file = os.path.join(
-            backupdir,
-            os.path.basename(self.storageDatabase) +
-            datetime.utcnow().strftime("-" + timeformat))
-        self.sqlite3_copy(self.sqlDataBaseFile, backup_file)
-        config = get_default_config_storage()
-        config["lastBackup"] = datetime.utcnow().strftime(timeformat)
-        del config
-
-    def sqlite3_copy(self, src, dst):
-        """Copy sql file from src to dst"""
-        if self.sqlDataBaseFile == ":memory:":
-            return
-        if not os.path.isfile(src):
-            return
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        # Lock database before making a backup
-        cursor.execute('begin immediate')
-        # Make new backup file
-        shutil.copyfile(src, dst)
-        log.info("Creating {}...".format(dst))
-        # Unlock database
-        connection.rollback()
-
-    def recover_with_latest_backup(self, backupdir="backups"):
-        """ Replace database with latest backup"""
-        file_date = 0
-        if self.sqlDataBaseFile == ":memory:":
-            return
-        if not os.path.isdir(backupdir):
-            backupdir = os.path.join(self.data_dir, backupdir)
-        if not os.path.isdir(backupdir):
-            return
-        newest_backup_file = None
-        for filename in os.listdir(backupdir):
-            backup_file = os.path.join(backupdir, filename)
-            if os.stat(backup_file).st_ctime > file_date:
-                if os.path.isfile(backup_file):
-                    file_date = os.stat(backup_file).st_ctime
-                    newest_backup_file = backup_file
-        if newest_backup_file is not None:
-            self.sqlite3_copy(newest_backup_file, self.sqlDataBaseFile)
-
-    def clean_data(self, backupdir="backups"):
-        """ Delete files older than 70 days
-        """
-        if self.sqlDataBaseFile == ":memory:":
-            return
-        log.info("Cleaning up old backups")
-        backupdir = os.path.join(self.data_dir, backupdir)
-        for filename in os.listdir(backupdir):
-            backup_file = os.path.join(backupdir, filename)
-            if os.stat(backup_file).st_ctime < (time.time() - 70 * 86400):
-                if os.path.isfile(backup_file):
-                    os.remove(backup_file)
-                    log.info("Deleting {}...".format(backup_file))
-
-    def refreshBackup(self):
-        """ Make a new backup
-        """
-        backupdir = os.path.join(self.data_dir, "backups")
-        self.sqlite3_backup(backupdir)
-        self.clean_data(backupdir)
-
-
-class Key(DataDir):
-    """ This is the key storage that stores the public key and the
-        (possibly encrypted) private key in the `keys` table in the
-        SQLite3 database.
-    """
-    __tablename__ = 'keys'
-
-    def __init__(self):
-        super(Key, self).__init__()
-
-    def exists_table(self):
-        """ Check if the database table exists
-        """
-        query = ("SELECT name FROM sqlite_master "
-                 "WHERE type='table' AND name=?", (self.__tablename__, ))
-        try:
-            connection = sqlite3.connect(self.sqlDataBaseFile)
-            cursor = connection.cursor()
-            cursor.execute(*query)
-            return True if cursor.fetchone() else False
-        except sqlite3.OperationalError:
-            self.sqlDataBaseFile = ":memory:"
-            log.warning("Could not read(database: %s)" % (self.sqlDataBaseFile))
-            return True
-
-    def create_table(self):
-        """ Create the new table in the SQLite database
-        """
-        query = ("CREATE TABLE {0} ("
-                 "id INTEGER PRIMARY KEY AUTOINCREMENT,"
-                 "pub STRING(256),"
-                 "wif STRING(256))".format(self.__tablename__))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        cursor.execute(query)
-        connection.commit()
-
-    def getPublicKeys(self, prefix="STM"):
-        """ Returns the public keys stored in the database
-        """
-        query = ("SELECT pub from {0} ".format(self.__tablename__))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        try:
-            cursor.execute(query)
-            results = cursor.fetchall()
-            keys = []
-            for x in results:
-                if prefix == x[0][:len(prefix)]:
-                    keys.append(x[0])
-            return keys
-        except sqlite3.OperationalError:
-            return []
-
-    def getPrivateKeyForPublicKey(self, pub):
-        """Returns the (possibly encrypted) private key that
-           corresponds to a public key
-
-           :param str pub: Public key
-
-           The encryption scheme is BIP38
-        """
-        query = ("SELECT wif from {0} WHERE pub=?".format(self.__tablename__), (pub,))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        cursor.execute(*query)
-        key = cursor.fetchone()
-        if key:
-            return key[0]
-        else:
-            return None
-
-    def updateWif(self, pub, wif):
-        """ Change the wif to a pubkey
-
-           :param str pub: Public key
-           :param str wif: Private key
-        """
-        query = ("UPDATE {0} SET wif=? WHERE pub=?".format(self.__tablename__), (wif, pub))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        cursor.execute(*query)
-        connection.commit()
-
-    def add(self, wif, pub):
-        """Add a new public/private key pair (correspondence has to be
-           checked elsewhere!)
-
-           :param str pub: Public key
-           :param str wif: Private key
-        """
-        if self.getPrivateKeyForPublicKey(pub):
-            raise ValueError("Key already in storage")
-        query = ("INSERT INTO {0} (pub, wif) VALUES (?, ?)".format(self.__tablename__), (pub, wif))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        cursor.execute(*query)
-        connection.commit()
-
-    def delete(self, pub):
-        """ Delete the key identified as `pub`
-
-           :param str pub: Public key
-        """
-        query = ("DELETE FROM {0} WHERE pub=?".format(self.__tablename__), (pub,))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        cursor.execute(*query)
-        connection.commit()
-
-    def wipe(self, sure=False):
-        """Purge the entire wallet. No keys will survive this!"""
-        if not sure:
-            log.error(
-                "You need to confirm that you are sure "
-                "and understand the implications of "
-                "wiping your wallet!"
-            )
-            return
-        else:
-            query = ("DELETE FROM {0} ".format(self.__tablename__))
-            connection = sqlite3.connect(self.sqlDataBaseFile)
-            cursor = connection.cursor()
-            cursor.execute(query)
-            connection.commit()
-
-
-class Token(DataDir):
-    """ This is the token storage that stores the public username and the
-        (possibly encrypted) token in the `token` table in the
-        SQLite3 database.
-    """
-    __tablename__ = 'token'
-
-    def __init__(self):
-        super(Token, self).__init__()
-
-    def exists_table(self):
-        """ Check if the database table exists
-        """
-        query = ("SELECT name FROM sqlite_master "
-                 "WHERE type='table' AND name=?", (self.__tablename__, ))
-        try:
-            connection = sqlite3.connect(self.sqlDataBaseFile)
-            cursor = connection.cursor()
-            cursor.execute(*query)
-            return True if cursor.fetchone() else False
-        except sqlite3.OperationalError:
-            self.sqlDataBaseFile = ":memory:"
-            log.warning("Could not read(database: %s)" % (self.sqlDataBaseFile))
-            return True
-
-    def create_table(self):
-        """ Create the new table in the SQLite database
-        """
-        query = ("CREATE TABLE {0} ("
-                 "id INTEGER PRIMARY KEY AUTOINCREMENT,"
-                 "name STRING(256),"
-                 "token STRING(256))".format(self.__tablename__))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        cursor.execute(query)
-        connection.commit()
-
-    def getPublicNames(self):
-        """ Returns the public names stored in the database
-        """
-        query = ("SELECT name from {0} ".format(self.__tablename__))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        try:
-            cursor.execute(query)
-            results = cursor.fetchall()
-            return [x[0] for x in results]
-        except sqlite3.OperationalError:
-            return []
-
-    def getTokenForPublicName(self, name):
-        """Returns the (possibly encrypted) private token that
-           corresponds to a public name
-
-           :param str pub: Public name
-
-           The encryption scheme is BIP38
-        """
-        query = ("SELECT token from {0} WHERE name=?".format(self.__tablename__), (name,))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        cursor.execute(*query)
-        token = cursor.fetchone()
-        if token:
-            return token[0]
-        else:
-            return None
-
-    def updateToken(self, name, token):
-        """ Change the token to a name
-
-           :param str name: Public name
-           :param str token: Private token
-        """
-        query = ("UPDATE {0} SET token=? WHERE name=?".format(self.__tablename__), (token, name))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        cursor.execute(*query)
-        connection.commit()
-
-    def add(self, name, token):
-        """Add a new public/private token pair (correspondence has to be
-           checked elsewhere!)
-
-           :param str name: Public name
-           :param str token: Private token
-        """
-        if self.getTokenForPublicName(name):
-            raise ValueError("Key already in storage")
-        query = ("INSERT INTO {0} (name, token) VALUES (?, ?)".format(self.__tablename__), (name, token))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        cursor.execute(*query)
-        connection.commit()
-
-    def delete(self, name):
-        """ Delete the key identified as `name`
-
-           :param str name: Public name
-        """
-        query = ("DELETE FROM {0} WHERE name=?".format(self.__tablename__), (name,))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        cursor.execute(*query)
-        connection.commit()
-
-    def wipe(self, sure=False):
-        """Purge the entire wallet. No keys will survive this!"""
-        if not sure:
-            log.error(
-                "You need to confirm that you are sure "
-                "and understand the implications of "
-                "wiping your wallet!"
-            )
-            return
-        else:
-            query = ("DELETE FROM {0} ".format(self.__tablename__))
-            connection = sqlite3.connect(self.sqlDataBaseFile)
-            cursor = connection.cursor()
-            cursor.execute(query)
-            connection.commit()
-
-
-class Configuration(DataDir):
-    """ This is the configuration storage that stores key/value
-        pairs in the `config` table of the SQLite3 database.
-    """
-    __tablename__ = "config"
-
-    #: Default configuration
-    nodelist = NodeList()
-    blockchain = "hive"
-    if blockchain == "hive":
-        nodes = nodelist.get_hive_nodes(testnet=False)
-    elif blockchain == "steem":
-        nodes = nodelist.get_steem_nodes(testnet=False)
-    else:
-        nodes = []
-    config_defaults = {
-        "node": nodes,
-        "default_chain": blockchain,
-        "password_storage": "environment",
-        "rpcpassword": "",
-        "rpcuser": "",
-        "order-expiration": 7 * 24 * 60 * 60,
-        "client_id": "",
-        "sc2_client_id": None,
-        "hs_client_id": None,
-        "hot_sign_redirect_uri": None,
-        "sc2_api_url": "https://api.steemconnect.com/api/",
-        "oauth_base_url": "https://api.steemconnect.com/oauth2/",
-        "hs_api_url": "https://hivesigner.com/api/",
-        "hs_oauth_base_url": "https://hivesigner.com/oauth2/",
-        "default_canonical_url": "https://hive.blog",
-        "default_path": "48'/13'/0'/0'/0'"}
-
-    def __init__(self):
-        super(Configuration, self).__init__()
-
-    def exists_table(self):
-        """ Check if the database table exists
-        """
-        query = ("SELECT name FROM sqlite_master "
-                 "WHERE type='table' AND name=?", (self.__tablename__,))
-        try:
-            connection = sqlite3.connect(self.sqlDataBaseFile)
-            cursor = connection.cursor()
-            cursor.execute(*query)
-            return True if cursor.fetchone() else False
-        except sqlite3.OperationalError:
-            self.sqlDataBaseFile = ":memory:"
-            log.warning("Could not read(database: %s)" % (self.sqlDataBaseFile))
-            return True
-
-    def create_table(self):
-        """ Create the new table in the SQLite database
-        """
-        query = ("CREATE TABLE {0} ("
-                 "id INTEGER PRIMARY KEY AUTOINCREMENT,"
-                 "key STRING(256),"
-                 "value STRING(256))".format(self.__tablename__))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        try:
-            cursor.execute(query)
-            connection.commit()
-        except sqlite3.OperationalError:
-            log.error("Could not write to database: %s" % (self.__tablename__))
-            raise NoWriteAccess("Could not write to database: %s" % (self.__tablename__))
-
-    def checkBackup(self):
-        """ Backup the SQL database every 7 days
-        """
-        if ("lastBackup" not in self.config or
-                self.config["lastBackup"] == ""):
-            print("No backup has been created yet!")
-            self.refreshBackup()
-        try:
-            if (
-                datetime.utcnow() -
-                datetime.strptime(self.config["lastBackup"],
-                                  timeformat)
-            ).days > 7:
-                print("Backups older than 7 days!")
-                self.refreshBackup()
-        except:
-            self.refreshBackup()
-
-    def _haveKey(self, key):
-        """ Is the key `key` available int he configuration?
-        """
-        query = ("SELECT value FROM {0} WHERE key=?".format(self.__tablename__), (key,))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        try:
-            cursor.execute(*query)
-            return True if cursor.fetchone() else False
-        except sqlite3.OperationalError:
-            log.warning("Could not read %s (database: %s)" % (str(key), self.__tablename__))
-            return False
-
-    def __getitem__(self, key):
-        """ This method behaves differently from regular `dict` in that
-            it returns `None` if a key is not found!
-        """
-        query = ("SELECT value FROM {0} WHERE key=?".format(self.__tablename__), (key,))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        try:
-            cursor.execute(*query)
-            result = cursor.fetchone()
-            if result:
-                return result[0]
-            else:
-                if key in self.config_defaults:
-                    return self.config_defaults[key]
-                else:
-                    return None
-        except sqlite3.OperationalError:
-            log.warning("Could not read %s (database: %s)" % (str(key), self.__tablename__))
-            if key in self.config_defaults:
-                return self.config_defaults[key]
-            else:
-                return None
-
-    def get(self, key, default=None):
-        """ Return the key if exists or a default value
-        """
-        if key in self:
-            return self.__getitem__(key)
-        else:
-            return default
-
-    def __contains__(self, key):
-        if self._haveKey(key) or key in self.config_defaults:
-            return True
-        else:
-            return False
-
-    def __setitem__(self, key, value):
-        if self._haveKey(key):
-            query = ("UPDATE {0} SET value=? WHERE key=?".format(self.__tablename__), (value, key))
-        else:
-            query = ("INSERT INTO {0} (key, value) VALUES (?, ?)".format(self.__tablename__), (key, value))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        try:
-            cursor.execute(*query)
-            connection.commit()
-        except sqlite3.OperationalError:
-            log.error("Could not write to %s (database: %s)" % (str(key), self.__tablename__))
-            raise NoWriteAccess("Could not write to %s (database: %s)" % (str(key), self.__tablename__))
-
-    def delete(self, key):
-        """ Delete a key from the configuration store
-        """
-        query = ("DELETE FROM {0} WHERE key=?".format(self.__tablename__), (key,))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        try:
-            cursor.execute(*query)
-            connection.commit()
-        except sqlite3.OperationalError:
-            log.error("Could not write to %s (database: %s)" % (str(key), self.__tablename__))
-            raise NoWriteAccess("Could not write to %s (database: %s)" % (str(key), self.__tablename__))
-
-    def __iter__(self):
-        return iter(list(self.items()))
-
-    def items(self):
-        query = ("SELECT key, value from {0} ".format(self.__tablename__))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        cursor.execute(query)
-        r = {}
-        for key, value in cursor.fetchall():
-            r[key] = value
-        return r
-
-    def __len__(self):
-        query = ("SELECT id from {0} ".format(self.__tablename__))
-        connection = sqlite3.connect(self.sqlDataBaseFile)
-        cursor = connection.cursor()
-        cursor.execute(query)
-        return len(cursor.fetchall())
-
-
-class MasterPassword(object):
-    """ The keys are encrypted with a Masterpassword that is stored in
-        the configurationStore. It has a checksum to verify correctness
-        of the password
-    """
-
-    password = ""  # nosec
-    decrypted_master = ""
-
-    #: This key identifies the encrypted master password stored in the confiration
-    config_key = "encrypted_master_password"
-
-    def __init__(self, password):
-        """ The encrypted private keys in `keys` are encrypted with a
-            random encrypted masterpassword that is stored in the
-            configuration.
-
-            The password is used to encrypt this masterpassword. To
-            decrypt the keys stored in the keys database, one must use
-            BIP38, decrypt the masterpassword from the configuration
-            store with the user password, and use the decrypted
-            masterpassword to decrypt the BIP38 encrypted private keys
-            from the keys storage!
-
-            :param str password: Password to use for en-/de-cryption
-        """
-        self.password = password
-        self.config = get_default_config_storage()
-        if self.config_key not in self.config:
-            self.newMaster()
-            self.saveEncrytpedMaster()
-        else:
-            self.decryptEncryptedMaster()
-
-    def decryptEncryptedMaster(self):
-        """ Decrypt the encrypted masterpassword
-        """
-        aes = AESCipher(self.password)
-        checksum, encrypted_master = self.config[self.config_key].split("$")
-        try:
-            decrypted_master = aes.decrypt(encrypted_master)
-        except:
-            raise WrongMasterPasswordException
-        if checksum != self.deriveChecksum(decrypted_master):
-            raise WrongMasterPasswordException
-        self.decrypted_master = decrypted_master
-
-    def saveEncrytpedMaster(self):
-        """ Store the encrypted master password in the configuration
-            store
-        """
-        self.config[self.config_key] = self.getEncryptedMaster()
-
-    def newMaster(self):
-        """ Generate a new random masterpassword
-        """
-        # make sure to not overwrite an existing key
-        if (self.config_key in self.config and
-                self.config[self.config_key]):
-            return
-        self.decrypted_master = hexlify(os.urandom(32)).decode("ascii")
-
-    def deriveChecksum(self, s):
-        """ Derive the checksum
-        """
-        checksum = hashlib.sha256(py23_bytes(s, "ascii")).hexdigest()
-        return checksum[:4]
-
-    def getEncryptedMaster(self):
-        """ Obtain the encrypted masterkey
-        """
-        if not self.decrypted_master:
-            raise Exception("master not decrypted")
-        aes = AESCipher(self.password)
-        return "{}${}".format(self.deriveChecksum(self.decrypted_master),
-                              aes.encrypt(self.decrypted_master))
-
-    def changePassword(self, newpassword):
-        """ Change the password
-        """
-        self.password = newpassword
-        self.saveEncrytpedMaster()
-
-    @staticmethod
-    def wipe(sure=False):
-        """Remove all keys from configStorage"""
-        if not sure:
-            log.error(
-                "You need to confirm that you are sure "
-                "and understand the implications of "
-                "wiping your wallet!"
-            )
-            return
-        else:
-            config = get_default_config_storage()
-            config.delete(MasterPassword.config_key)
-
-
-def get_default_config_storage():
-    configStorage = Configuration()
-    # Create Tables if database is brand new
-    if not configStorage.exists_table():
-        configStorage.create_table()
-    return configStorage
-
-
-def get_default_key_storage():
-    keyStorage = Key()
-    
-    newKeyStorage = False
-    if not keyStorage.exists_table():
-        newKeyStorage = True
-        keyStorage.create_table()
-    return keyStorage
-
-
-def get_default_token_storage():
-    tokenStorage = Token()
-    newTokenStorage = False
-    if not tokenStorage.exists_table():
-        newTokenStorage = True
-        tokenStorage.create_table()
-    return tokenStorage
+#: Default configuration
+nodelist = NodeList()
+blockchain = "hive"
+if blockchain == "hive":
+    nodes = nodelist.get_hive_nodes(testnet=False)
+elif blockchain == "steem":
+    nodes = nodelist.get_steem_nodes(testnet=False)
+else:
+    nodes = []
+
+SqliteConfigurationStore.setdefault("node", nodes)
+SqliteConfigurationStore.setdefault("default_chain", blockchain)
+SqliteConfigurationStore.setdefault("password_storage", "environment")
+SqliteConfigurationStore.setdefault("rpcpassword", "")
+SqliteConfigurationStore.setdefault("rpcuser", "")
+SqliteConfigurationStore.setdefault("order-expiration", 7 * 24 * 60 * 60)
+SqliteConfigurationStore.setdefault("client_id", "")
+SqliteConfigurationStore.setdefault("sc2_client_id", None)
+SqliteConfigurationStore.setdefault("hs_client_id", None)
+SqliteConfigurationStore.setdefault("hot_sign_redirect_uri", None)
+SqliteConfigurationStore.setdefault("sc2_api_url", "https://api.steemconnect.com/api/")
+SqliteConfigurationStore.setdefault("oauth_base_url", "https://api.steemconnect.com/oauth2/")
+SqliteConfigurationStore.setdefault("hs_api_url", "https://hivesigner.com/api/")
+SqliteConfigurationStore.setdefault("hs_oauth_base_url", "https://hivesigner.com/oauth2/")
+SqliteConfigurationStore.setdefault("default_canonical_url", "https://hive.blog")
+SqliteConfigurationStore.setdefault("default_path", "48'/13'/0'/0'/0'")
+
+
+def get_default_config_store(*args, **kwargs):
+    return SqliteConfigurationStore(*args, **kwargs)
+
+
+def get_default_key_store(config, *args, **kwargs):
+    return SqliteEncryptedKeyStore(config=config, **kwargs)
diff --git a/beem/transactionbuilder.py b/beem/transactionbuilder.py
index 0f661b89..7fa915c3 100644
--- a/beem/transactionbuilder.py
+++ b/beem/transactionbuilder.py
@@ -21,9 +21,9 @@ from .exceptions import (
     InsufficientAuthorityError,
     MissingKeyError,
     InvalidWifError,
-    WalletLocked,
     OfflineHasNoRPCException
 )
+from beemstorage.exceptions import WalletLocked
 from beem.instance import shared_blockchain_instance
 log = logging.getLogger(__name__)
 
diff --git a/beem/version.py b/beem/version.py
index 37facaf7..1f0a7ac7 100644
--- a/beem/version.py
+++ b/beem/version.py
@@ -1,2 +1,2 @@
 """THIS FILE IS GENERATED FROM beem SETUP.PY."""
-version = '0.23.13'
+version = '0.24.0'
diff --git a/beem/wallet.py b/beem/wallet.py
index 2fa65481..94341f4f 100644
--- a/beem/wallet.py
+++ b/beem/wallet.py
@@ -6,33 +6,18 @@ from builtins import str, bytes
 from builtins import object
 import logging
 import os
-import hashlib
-from beemgraphenebase import bip38
 from beemgraphenebase.account import PrivateKey
 from beem.instance import shared_blockchain_instance
 from .account import Account
-from .aes import AESCipher
 from .exceptions import (
     MissingKeyError,
     InvalidWifError,
     WalletExists,
-    WalletLocked,
-    WrongMasterPasswordException,
-    NoWalletException,
     OfflineHasNoRPCException,
-    AccountDoesNotExistsException,
+    AccountDoesNotExistsException
 )
-from beemapi.exceptions import NoAccessApi
-from beemgraphenebase.py23 import py23_bytes
-from .storage import get_default_config_storage, get_default_key_storage, get_default_token_storage
-try:
-    import keyring
-    if not isinstance(keyring.get_keyring(), keyring.backends.fail.Keyring):
-        KEYRING_AVAILABLE = True
-    else:
-        KEYRING_AVAILABLE = False
-except ImportError:
-    KEYRING_AVAILABLE = False
+from beemstorage.exceptions import KeyAlreadyInStoreException, WalletLocked
+
 
 log = logging.getLogger(__name__)
 
@@ -99,18 +84,7 @@ class Wallet(object):
                   import format (wif) (starting with a ``5``).
 
     """
-    masterpassword = None
-
-    # Keys from database
-    configStorage = None
-    MasterPassword = None
-    keyStorage = None
-    tokenStorage = None
 
-    # Manually provided keys
-    keys = {}  # struct with pubkey as key and wif as value
-    token = {}
-    keyMap = {}  # wif pairs to force certain keys
 
     def __init__(self, blockchain_instance=None, *args, **kwargs):
         if blockchain_instance is None:
@@ -123,28 +97,20 @@ class Wallet(object):
         # Compatibility after name change from wif->keys
         if "wif" in kwargs and "keys" not in kwargs:
             kwargs["keys"] = kwargs["wif"]
-        master_password_set = False
+
         if "keys" in kwargs and len(kwargs["keys"]) > 0:
+            from beemstorage import InRamPlainKeyStore
+            self.store = InRamPlainKeyStore()
             self.setKeys(kwargs["keys"])
         else:
             """ If no keys are provided manually we load the SQLite
                 keyStorage
             """
-            from .storage import MasterPassword
-            self.MasterPassword = MasterPassword
-            master_password_set = True
-            self.keyStorage = get_default_key_storage()
-
-        if "token" in kwargs:
-            self.setToken(kwargs["token"])
-        else:
-            """ If no keys are provided manually we load the SQLite
-                keyStorage
-            """
-            if not master_password_set:
-                from .storage import MasterPassword
-                self.MasterPassword = MasterPassword
-            self.tokenStorage = get_default_token_storage()
+            from beemstorage import SqliteEncryptedKeyStore
+            self.store = kwargs.get(
+                "key_store",
+                SqliteEncryptedKeyStore(config=self.blockchain.config, **kwargs),
+            )
 
     @property
     def prefix(self):
@@ -163,105 +129,65 @@ class Wallet(object):
 
     def setKeys(self, loadkeys):
         """ This method is strictly only for in memory keys that are
-            passed to Wallet/Steem with the ``keys`` argument
+            passed to Wallet with the ``keys`` argument
         """
-        log.debug(
-            "Force setting of private keys. Not using the wallet database!")
-        self.clear_local_keys()
+        log.debug("Force setting of private keys. Not using the wallet database!")
         if isinstance(loadkeys, dict):
-            Wallet.keyMap = loadkeys
             loadkeys = list(loadkeys.values())
-        elif isinstance(loadkeys, tuple):
-            loadkeys = list(loadkeys)
-        elif not isinstance(loadkeys, list):
+        elif not isinstance(loadkeys, (list, set)):
             loadkeys = [loadkeys]
-
         for wif in loadkeys:
-            if isinstance(wif, list):
-                for w in wif:
-                    pub = self._get_pub_from_wif(w)
-                    Wallet.keys[pub] = str(w)
-            else:
-                pub = self._get_pub_from_wif(wif)
-                Wallet.keys[pub] = str(wif)
-
-    def setToken(self, loadtoken):
-        """ This method is strictly only for in memory token that are
-            passed to Wallet/Steem with the ``token`` argument
-        """
-        log.debug(
-            "Force setting of private token. Not using the wallet database!")
-        self.clear_local_token()
-        if isinstance(loadtoken, dict):
-            Wallet.token = loadtoken
-        else:
-            raise ValueError("token must be a dict variable!")
+            pub = self.publickey_from_wif(wif)
+            self.store.add(str(wif), pub)
 
-    def unlock(self, pwd=None):
-        """ Unlock the wallet database
+    def is_encrypted(self):
+        """ Is the key store encrypted?
         """
-        if not self.created():
-            raise NoWalletException
+        return self.store.is_encrypted()
 
-        if not pwd:
-            self.tryUnlockFromEnv()
-        else:
-            if (self.masterpassword is None and self.blockchain.config[self.MasterPassword.config_key]):
-                self.masterpwd = self.MasterPassword(pwd)
-                self.masterpassword = self.masterpwd.decrypted_master
-
-    def tryUnlockFromEnv(self):
-        """ Try to fetch the unlock password from UNLOCK environment variable and keyring when no password is given.
-        """
-        password_storage = self.blockchain.config["password_storage"]
-        if password_storage == "environment" and "UNLOCK" in os.environ:
-            log.debug("Trying to use environmental variable to unlock wallet")
-            pwd = os.environ.get("UNLOCK")
-            self.unlock(pwd)
-        elif password_storage == "keyring" and KEYRING_AVAILABLE:
-            log.debug("Trying to use keyring to unlock wallet")
-            pwd = keyring.get_password("beem", "wallet")
-            self.unlock(pwd)
-        else:
-            raise WrongMasterPasswordException
+    def unlock(self, pwd):
+        """ Unlock the wallet database
+        """
+        unlock_ok = None
+        if self.store.is_encrypted():
+            unlock_ok = self.store.unlock(pwd)
+        return unlock_ok
 
     def lock(self):
         """ Lock the wallet database
         """
-        self.masterpassword = None
+        lock_ok = False
+        if self.store.is_encrypted():
+            lock_ok =  self.store.lock()       
+        return lock_ok
 
     def unlocked(self):
         """ Is the wallet database unlocked?
         """
-        return not self.locked()
+        unlocked = True
+        if self.store.is_encrypted():
+            unlocked = not self.store.locked()   
+        return unlocked
 
     def locked(self):
         """ Is the wallet database locked?
         """
-        if Wallet.keys:  # Keys have been manually provided!
+        if self.store.is_encrypted():
+            return self.store.locked()
+        else:
             return False
-        try:
-            self.tryUnlockFromEnv()
-        except WrongMasterPasswordException:
-            pass
-        return not bool(self.masterpassword)
 
     def changePassphrase(self, new_pwd):
         """ Change the passphrase for the wallet database
         """
-        if self.locked():
-            raise AssertionError()
-        self.masterpwd.changePassword(new_pwd)
+        self.store.change_password(new_pwd)
 
     def created(self):
         """ Do we have a wallet database already?
         """
-        if len(self.getPublicKeys()):
+        if len(self.store.getPublicKeys()):
             # Already keys installed
             return True
-        elif self.MasterPassword.config_key in self.blockchain.config:
-            # no keys but a master password
-            return True
         else:
             return False
 
@@ -279,190 +205,42 @@ class Wallet(object):
         """
         if self.created():
             raise WalletExists("You already have created a wallet!")
-        self.masterpwd = self.MasterPassword(pwd)
-        self.masterpassword = self.masterpwd.decrypted_master
-        self.masterpwd.saveEncrytpedMaster()
-
-    def wipe(self, sure=False):
-        """ Purge all data in wallet database
-        """
-        if not sure:
-            log.error(
-                "You need to confirm that you are sure "
-                "and understand the implications of "
-                "wiping your wallet!"
-            )
-            return
-        else:
-            from .storage import (
-                MasterPassword
-            )
-            keyStorage = get_default_key_storage()
-            tokenStorage = get_default_token_storage()
-            MasterPassword.wipe(sure)
-            keyStorage.wipe(sure)
-            tokenStorage.wipe(sure)
-            self.clear_local_keys()
+        self.store.unlock(pwd)
 
-    def clear_local_keys(self):
-        """Clear all manually provided keys"""
-        Wallet.keys = {}
-        Wallet.keyMap = {}
+    def privatekey(self, key):
+        return PrivateKey(key, prefix=self.prefix)
 
-    def clear_local_token(self):
-        """Clear all manually provided token"""
-        Wallet.token = {}
-
-    def encrypt_wif(self, wif):
-        """ Encrypt a wif key
-        """
-        if self.locked():
-            raise AssertionError()
-        return format(
-            bip38.encrypt(PrivateKey(wif, prefix=self.prefix), self.masterpassword), "encwif")
-
-    def decrypt_wif(self, encwif):
-        """ decrypt a wif key
-        """
-        try:
-            # Try to decode as wif
-            PrivateKey(encwif, prefix=self.prefix)
-            return encwif
-        except (ValueError, AssertionError):
-            pass
-        if self.locked():
-            raise AssertionError()
-        return format(bip38.decrypt(encwif, self.masterpassword), "wif")
-
-    def deriveChecksum(self, s):
-        """ Derive the checksum
-        """
-        checksum = hashlib.sha256(py23_bytes(s, "ascii")).hexdigest()
-        return checksum[:4]
-
-    def encrypt_token(self, token):
-        """ Encrypt a token key
-        """
-        if self.locked():
-            raise AssertionError()
-        aes = AESCipher(self.masterpassword)
-        return "{}${}".format(self.deriveChecksum(token), aes.encrypt(token))
-
-    def decrypt_token(self, enctoken):
-        """ decrypt a wif key
-        """
-        if self.locked():
-            raise AssertionError()
-        aes = AESCipher(self.masterpassword)
-        checksum, encrypted_token = enctoken.split("$")
-        try:
-            decrypted_token = aes.decrypt(encrypted_token)
-        except:
-            raise WrongMasterPasswordException
-        if checksum != self.deriveChecksum(decrypted_token):
-            raise WrongMasterPasswordException
-        return decrypted_token
-
-    def _get_pub_from_wif(self, wif):
-        """ Get the pubkey as string, from the wif key as string
-        """
-        # it could be either graphenebase or steem so we can't check
-        # the type directly
-        if isinstance(wif, PrivateKey):
-            wif = str(wif)
-        try:
-            return format(PrivateKey(wif).pubkey, self.prefix)
-        except:
-            raise InvalidWifError(
-                "Invalid Private Key Format. Please use WIF!")
-
-    def addToken(self, name, token):
-        if self.tokenStorage:
-            if not self.created():
-                raise NoWalletException
-            self.tokenStorage.add(name, self.encrypt_token(token))
-
-    def getTokenForAccountName(self, name):
-        """ Obtain the private token for a given public name
-
-            :param str name: Public name
-        """
-        if(Wallet.token):
-            if name in Wallet.token:
-                return Wallet.token[name]
-            else:
-                raise MissingKeyError("No private token for {} found".format(name))
-        else:
-            # Test if wallet exists
-            if not self.created():
-                raise NoWalletException
-
-            if not self.unlocked():
-                raise WalletLocked
-
-            enctoken = self.tokenStorage.getTokenForPublicName(name)
-            if not enctoken:
-                raise MissingKeyError("No private token for {} found".format(name))
-            return self.decrypt_token(enctoken)
-
-    def removeTokenFromPublicName(self, name):
-        """ Remove a token from the wallet database
-
-            :param str name: token to be removed
-        """
-        if self.tokenStorage:
-            # Test if wallet exists
-            if not self.created():
-                raise NoWalletException
-            self.tokenStorage.delete(name)
+    def publickey_from_wif(self, wif):
+        return str(self.privatekey(str(wif)).pubkey)
 
     def addPrivateKey(self, wif):
         """Add a private key to the wallet database
 
             :param str wif: Private key
         """
-        pub = self._get_pub_from_wif(wif)
-        if isinstance(wif, PrivateKey):
-            wif = str(wif)
-        if self.keyStorage:
-            # Test if wallet exists
-            if not self.created():
-                raise NoWalletException
-            self.keyStorage.add(self.encrypt_wif(wif), pub)
+        try:
+            pub = self.publickey_from_wif(wif)
+        except Exception:
+            raise InvalidWifError("Invalid Key format!")
+        if str(pub) in self.store:
+            raise KeyAlreadyInStoreException("Key already in the store")
+        self.store.add(str(wif), str(pub))
 
     def getPrivateKeyForPublicKey(self, pub):
         """ Obtain the private key for a given public key
 
             :param str pub: Public Key
         """
-        if(Wallet.keys):
-            if pub in Wallet.keys:
-                return Wallet.keys[pub]
-            else:
-                raise MissingKeyError("No private key for {} found".format(pub))
-        else:
-            # Test if wallet exists
-            if not self.created():
-                raise NoWalletException
-
-            if not self.unlocked():
-                raise WalletLocked
-
-            encwif = self.keyStorage.getPrivateKeyForPublicKey(pub)
-            if not encwif:
-                raise MissingKeyError("No private key for {} found".format(pub))
-            return self.decrypt_wif(encwif)
+        if str(pub) not in self.store:
+            raise MissingKeyError
+        return self.store.getPrivateKeyForPublicKey(str(pub))
 
     def removePrivateKeyFromPublicKey(self, pub):
         """ Remove a key from the wallet database
 
             :param str pub: Public key
         """
-        if self.keyStorage:
-            # Test if wallet exists
-            if not self.created():
-                raise NoWalletException
-            self.keyStorage.delete(pub)
+        self.store.delete(str(pub))
 
     def removeAccount(self, account):
         """ Remove all keys associated with a given account
@@ -472,7 +250,7 @@ class Wallet(object):
         accounts = self.getAccounts()
         for a in accounts:
             if a["name"] == account:
-                self.removePrivateKeyFromPublicKey(a["pubkey"])
+                self.store.delete(a["pubkey"])
 
     def getKeyForAccount(self, name, key_type):
         """ Obtain `key_type` Private Key for an account from the wallet database
@@ -483,34 +261,32 @@ class Wallet(object):
         """
         if key_type not in ["owner", "active", "posting", "memo"]:
             raise AssertionError("Wrong key type")
-        if key_type in Wallet.keyMap:
-            return Wallet.keyMap.get(key_type)
+
+        if self.rpc.get_use_appbase():
+            account = self.rpc.find_accounts({'accounts': [name]}, api="database")['accounts']
         else:
-            if self.rpc.get_use_appbase():
-                account = self.rpc.find_accounts({'accounts': [name]}, api="database")['accounts']
-            else:
-                account = self.rpc.get_account(name)
-            if not account:
-                return
-            if len(account) == 0:
-                return
-            if key_type == "memo":
-                key = self.getPrivateKeyForPublicKey(
-                    account[0]["memo_key"])
-                if key:
-                    return key
-            else:
-                key = None
-                for authority in account[0][key_type]["key_auths"]:
-                    try:
-                        key = self.getPrivateKeyForPublicKey(authority[0])
-                        if key:
-                            return key
-                    except MissingKeyError:
-                        key = None
-                if key is None:
-                    raise MissingKeyError("No private key for {} found".format(name))
+            account = self.rpc.get_account(name)
+        if not account:
+            return
+        if len(account) == 0:
             return
+        if key_type == "memo":
+            key = self.getPrivateKeyForPublicKey(
+                account[0]["memo_key"])
+            if key:
+                return key
+        else:
+            key = None
+            for authority in account[0][key_type]["key_auths"]:
+                try:
+                    key = self.getPrivateKeyForPublicKey(authority[0])
+                    if key:
+                        return key
+                except MissingKeyError:
+                    key = None
+            if key is None:
+                raise MissingKeyError("No private key for {} found".format(name))
+        return
 
     def getKeysForAccount(self, name, key_type):
         """ Obtain a List of `key_type` Private Keys for an account from the wallet database
@@ -521,36 +297,34 @@ class Wallet(object):
         """
         if key_type not in ["owner", "active", "posting", "memo"]:
             raise AssertionError("Wrong key type")
-        if key_type in Wallet.keyMap:
-            return Wallet.keyMap.get(key_type)
+
+        if self.rpc.get_use_appbase():
+            account = self.rpc.find_accounts({'accounts': [name]}, api="database")['accounts']
         else:
-            if self.rpc.get_use_appbase():
-                account = self.rpc.find_accounts({'accounts': [name]}, api="database")['accounts']
-            else:
-                account = self.rpc.get_account(name)
-            if not account:
-                return
-            if len(account) == 0:
-                return
-            if key_type == "memo":
-                key = self.getPrivateKeyForPublicKey(
-                    account[0]["memo_key"])
-                if key:
-                    return [key]
-            else:
-                keys = []
-                key = None
-                for authority in account[0][key_type]["key_auths"]:
-                    try:
-                        key = self.getPrivateKeyForPublicKey(authority[0])
-                        if key:
-                            keys.append(key)
-                    except MissingKeyError:
-                        key = None
-                if key is None:
-                    raise MissingKeyError("No private key for {} found".format(name))
-                return keys
+            account = self.rpc.get_account(name)
+        if not account:
+            return
+        if len(account) == 0:
             return
+        if key_type == "memo":
+            key = self.getPrivateKeyForPublicKey(
+                account[0]["memo_key"])
+            if key:
+                return [key]
+        else:
+            keys = []
+            key = None
+            for authority in account[0][key_type]["key_auths"]:
+                try:
+                    key = self.getPrivateKeyForPublicKey(authority[0])
+                    if key:
+                        keys.append(key)
+                except MissingKeyError:
+                    key = None
+            if key is None:
+                raise MissingKeyError("No private key for {} found".format(name))
+            return keys
+        return
 
     def getOwnerKeyForAccount(self, name):
         """ Obtain owner Private Key for an account from the wallet database
@@ -590,7 +364,7 @@ class Wallet(object):
     def getAccountFromPrivateKey(self, wif):
         """ Obtain account name from private key
         """
-        pub = self._get_pub_from_wif(wif)
+        pub = self.publickey_from_wif(wif)
         return self.getAccountFromPublicKey(pub)
 
     def getAccountsFromPublicKey(self, pub):
@@ -669,9 +443,9 @@ class Wallet(object):
         """
         for authority in ["owner", "active", "posting"]:
             for key in account[authority]["key_auths"]:
-                if pub == key[0]:
+                if str(pub) == key[0]:
                     return authority
-        if pub == account["memo_key"]:
+        if str(pub) == account["memo_key"]:
             return "memo"
         return None
 
@@ -686,18 +460,36 @@ class Wallet(object):
                 accounts.extend(self.getAllAccounts(pubkey))
         return accounts
 
-    def getPublicKeys(self):
+    def getPublicKeys(self, current=False):
         """ Return all installed public keys
+            :param bool current: If true, returns only keys for currently
+                connected blockchain
         """
-        if self.keyStorage:
-            return self.keyStorage.getPublicKeys(prefix=self.blockchain.prefix)
-        else:
-            return list(Wallet.keys.keys())
+        pubkeys = self.store.getPublicKeys()
+        if not current:
+            return pubkeys
+        pubs = []
+        for pubkey in pubkeys:
+            # Filter those keys not for our network
+            if pubkey[: len(self.prefix)] == self.prefix:
+                pubs.append(pubkey)
+        return pubs
 
     def getPublicNames(self):
         """ Return all installed public token
         """
-        if self.tokenStorage:
-            return self.tokenStorage.getPublicNames()
+        if self.token_store is None:
+            return
+        return self.token_store.getPublicNames()
+
+    def wipe(self, sure=False):
+        if not sure:
+            log.error(
+                "You need to confirm that you are sure "
+                "and understand the implications of "
+                "wiping your wallet!"
+            )
+            return
         else:
-            return list(Wallet.token.keys())
+            self.store.wipe()
+            self.store.wipe_masterpassword()
diff --git a/beemapi/version.py b/beemapi/version.py
index 37facaf7..1f0a7ac7 100644
--- a/beemapi/version.py
+++ b/beemapi/version.py
@@ -1,2 +1,2 @@
 """THIS FILE IS GENERATED FROM beem SETUP.PY."""
-version = '0.23.13'
+version = '0.24.0'
diff --git a/beembase/version.py b/beembase/version.py
index 37facaf7..1f0a7ac7 100644
--- a/beembase/version.py
+++ b/beembase/version.py
@@ -1,2 +1,2 @@
 """THIS FILE IS GENERATED FROM beem SETUP.PY."""
-version = '0.23.13'
+version = '0.24.0'
diff --git a/beemgraphenebase/__init__.py b/beemgraphenebase/__init__.py
index f7a36fbf..22f84273 100644
--- a/beemgraphenebase/__init__.py
+++ b/beemgraphenebase/__init__.py
@@ -8,6 +8,7 @@ from .version import version as __version__
 # from . import dictionary as BrainKeyDictionary
 
 __all__ = ['account',
+           'aes',
            'base58',
            'bip32'
            'bip38',
diff --git a/beem/aes.py b/beemgraphenebase/aes.py
similarity index 100%
rename from beem/aes.py
rename to beemgraphenebase/aes.py
diff --git a/beemgraphenebase/bip38.py b/beemgraphenebase/bip38.py
index 0dd4b470..f804a95f 100644
--- a/beemgraphenebase/bip38.py
+++ b/beemgraphenebase/bip38.py
@@ -57,6 +57,11 @@ def encrypt(privkey, passphrase):
     :rtype: Base58
 
     """
+    if isinstance(privkey, str):
+        privkey = PrivateKey(privkey)
+    else:
+        privkey = PrivateKey(repr(privkey))    
+
     privkeyhex = repr(privkey)   # hex
     addr = format(privkey.bitcoin.address, "BTC")
     a = py23_bytes(addr, 'ascii')
diff --git a/beemgraphenebase/version.py b/beemgraphenebase/version.py
index 37facaf7..1f0a7ac7 100644
--- a/beemgraphenebase/version.py
+++ b/beemgraphenebase/version.py
@@ -1,2 +1,2 @@
 """THIS FILE IS GENERATED FROM beem SETUP.PY."""
-version = '0.23.13'
+version = '0.24.0'
diff --git a/beemstorage/__init__.py b/beemstorage/__init__.py
new file mode 100644
index 00000000..24c31a26
--- /dev/null
+++ b/beemstorage/__init__.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# Load modules from other classes
+# # Inspired by https://raw.githubusercontent.com/xeroc/python-graphenelib/master/graphenestorage/__init__.py
+from .base import (
+    InRamConfigurationStore,
+    InRamPlainKeyStore,
+    InRamEncryptedKeyStore,
+    InRamPlainTokenStore,
+    InRamEncryptedTokenStore,    
+    SqliteConfigurationStore,
+    SqlitePlainKeyStore,
+    SqliteEncryptedKeyStore,
+    SqlitePlainTokenStore,
+    SqliteEncryptedTokenStore,    
+)
+from .sqlite import SQLiteFile, SQLiteCommon
+
+__all__ = ["interfaces", "masterpassword", "base", "sqlite", "ram"]
+
+
+def get_default_config_store(*args, **kwargs):
+    """ This method returns the default **configuration** store
+        that uses an SQLite database internally.
+        :params str appname: The appname that is used internally to distinguish
+            different SQLite files
+    """
+    kwargs["appname"] = kwargs.get("appname", "beem")
+    return SqliteConfigurationStore(*args, **kwargs)
+
+
+def get_default_key_store(*args, config, **kwargs):
+    """ This method returns the default **key** store
+        that uses an SQLite database internally.
+        :params str appname: The appname that is used internally to distinguish
+            different SQLite files
+    """
+    kwargs["appname"] = kwargs.get("appname", "beem")
+    return SqliteEncryptedKeyStore(config=config, **kwargs)
diff --git a/beemstorage/base.py b/beemstorage/base.py
new file mode 100644
index 00000000..3934592a
--- /dev/null
+++ b/beemstorage/base.py
@@ -0,0 +1,302 @@
+# -*- coding: utf-8 -*-
+# Inspired by https://raw.githubusercontent.com/xeroc/python-graphenelib/master/graphenestorage/base.py
+import logging
+
+from .masterpassword import MasterPassword
+from .interfaces import KeyInterface, ConfigInterface, EncryptedKeyInterface, TokenInterface, EncryptedTokenInterface
+from .ram import InRamStore
+from .sqlite import SQLiteStore
+from .exceptions import KeyAlreadyInStoreException
+
+log = logging.getLogger(__name__)
+
+
+# Configuration
+class InRamConfigurationStore(InRamStore, ConfigInterface):
+    """ A simple example that stores configuration in RAM.
+
+        Internally, this works by simply inheriting
+        :class:`beemstorage.ram.InRamStore`. The interface is defined in
+        :class:`beemstorage.interfaces.ConfigInterface`.
+    """
+
+    pass
+
+
+class SqliteConfigurationStore(SQLiteStore, ConfigInterface):
+    """ This is the configuration storage that stores key/value
+        pairs in the `config` table of the SQLite3 database.
+
+        Internally, this works by simply inheriting
+        :class:`beemstorage.sqlite.SQLiteStore`. The interface is defined
+        in :class:`beemstorage.interfaces.ConfigInterface`.
+    """
+
+    #: The table name for the configuration
+    __tablename__ = "config"
+    #: The name of the 'key' column
+    __key__ = "key"
+    #: The name of the 'value' column
+    __value__ = "value"
+
+
+# Keys
+class InRamPlainKeyStore(InRamStore, KeyInterface):
+    """ A simple in-RAM Store that stores keys unencrypted in RAM
+
+        Internally, this works by simply inheriting
+        :class:`beemstorage.ram.InRamStore`. The interface is defined in
+        :class:`beemstorage.interfaces.KeyInterface`.
+    """
+
+    def getPublicKeys(self):
+        return [k for k, v in self.items()]
+
+    def getPrivateKeyForPublicKey(self, pub):
+        return self.get(str(pub), None)
+
+    def add(self, wif, pub):
+        if str(pub) in self:
+            raise KeyAlreadyInStoreException
+        self[str(pub)] = str(wif)
+
+    def delete(self, pub):
+        InRamStore.delete(self, str(pub))
+
+
+class SqlitePlainKeyStore(SQLiteStore, KeyInterface):
+    """ This is the key storage that stores the public key and the
+        **unencrypted** private key in the `keys` table in the SQLite3
+        database.
+
+        Internally, this works by simply inheriting
+        :class:`beemstorage.ram.InRamStore`. The interface is defined in
+        :class:`beemstorage.interfaces.KeyInterface`.
+    """
+
+    #: The table name for the configuration
+    __tablename__ = "keys"
+    #: The name of the 'key' column
+    __key__ = "pub"
+    #: The name of the 'value' column
+    __value__ = "wif"
+
+    def getPublicKeys(self):
+        return [k for k, v in self.items()]
+
+    def getPrivateKeyForPublicKey(self, pub):
+        return self[pub]
+
+    def add(self, wif, pub):
+        if str(pub) in self:
+            raise KeyAlreadyInStoreException
+        self[str(pub)] = str(wif)
+
+    def delete(self, pub):
+        SQLiteStore.delete(self, str(pub))
+
+    def is_encrypted(self):
+        """ Returns False, as we are not encrypted here
+        """
+        return False
+
+
+class KeyEncryption(MasterPassword, EncryptedKeyInterface):
+    """ This is an interface class that provides the methods required for
+        EncryptedKeyInterface and links them to the MasterPassword-provided
+        functionatlity, accordingly.
+    """
+
+    def __init__(self, *args, **kwargs):
+        EncryptedKeyInterface.__init__(self, *args, **kwargs)
+        MasterPassword.__init__(self, *args, **kwargs)
+
+    # Interface to deal with encrypted keys
+    def getPublicKeys(self):
+        return [k for k, v in self.items()]
+
+    def getPrivateKeyForPublicKey(self, pub):
+        wif = self.get(str(pub), None)
+        if wif:
+            return self.decrypt(wif)  # From Masterpassword
+
+    def add(self, wif, pub):
+        if str(pub) in self:
+            raise KeyAlreadyInStoreException
+        self[str(pub)] = self.encrypt(str(wif))  # From Masterpassword
+
+    def is_encrypted(self):
+        return True
+
+
+class InRamEncryptedKeyStore(InRamStore, KeyEncryption):
+    """ An in-RAM Store that stores keys **encrypted** in RAM.
+
+        Internally, this works by simply inheriting
+        :class:`beemstorage.ram.InRamStore`. The interface is defined in
+        :class:`beemstorage.interfaces.KeyInterface`.
+
+        .. note:: This module also inherits
+            :class:`beemstorage.masterpassword.MasterPassword` which offers
+            additional methods and deals with encrypting the keys.
+    """
+
+    def __init__(self, *args, **kwargs):
+        InRamStore.__init__(self, *args, **kwargs)
+        KeyEncryption.__init__(self, *args, **kwargs)
+
+
+class SqliteEncryptedKeyStore(SQLiteStore, KeyEncryption):
+    """ This is the key storage that stores the public key and the
+        **encrypted** private key in the `keys` table in the SQLite3 database.
+
+        Internally, this works by simply inheriting
+        :class:`beemstorage.ram.InRamStore`. The interface is defined in
+        :class:`beemstorage.interfaces.KeyInterface`.
+
+        .. note:: This module also inherits
+            :class:`beemstorage.masterpassword.MasterPassword` which offers
+            additional methods and deals with encrypting the keys.
+    """
+
+    __tablename__ = "keys"
+    __key__ = "pub"
+    __value__ = "wif"
+
+    def __init__(self, *args, **kwargs):
+        SQLiteStore.__init__(self, *args, **kwargs)
+        KeyEncryption.__init__(self, *args, **kwargs)
+
+
+# Token
+class InRamPlainTokenStore(InRamStore, TokenInterface):
+    """ A simple in-RAM Store that stores token unencrypted in RAM
+
+        Internally, this works by simply inheriting
+        :class:`beemstorage.ram.InRamStore`. The interface is defined in
+        :class:`beemstorage.interfaces.TokenInterface`.
+    """
+
+    def getPublicNames(self):
+        return [k for k, v in self.items()]
+
+    def getPrivateKeyForPublicKey(self, pub):
+        return self.get(str(pub), None)
+
+    def add(self, token, name):
+        if str(name) in self:
+            raise KeyAlreadyInStoreException
+        self[str(name)] = str(token)
+
+    def delete(self, name):
+        InRamStore.delete(self, str(name))
+
+
+class SqlitePlainTokenStore(SQLiteStore, TokenInterface):
+    """ This is the token storage that stores the public key and the
+        **unencrypted** private key in the `tokens` table in the SQLite3
+        database.
+
+        Internally, this works by simply inheriting
+        :class:`beemstorage.ram.InRamStore`. The interface is defined in
+        :class:`beemstorage.interfaces.TokenInterface`.
+    """
+
+    #: The table name for the configuration
+    __tablename__ = "token"
+    #: The name of the 'key' column
+    __key__ = "name"
+    #: The name of the 'value' column
+    __value__ = "token"
+
+    def getPublicNames(self):
+        return [k for k, v in self.items()]
+
+    def getPrivateKeyForPublicKey(self, name):
+        return self[name]
+
+    def add(self, token, name):
+        if str(name) in self:
+            raise KeyAlreadyInStoreException
+        self[str(name)] = str(token)
+
+    def updateToken(self, name, token):
+        self[str(name)] = str(token)  # From Masterpassword
+
+    def delete(self, name):
+        SQLiteStore.delete(self, str(name))
+
+    def is_encrypted(self):
+        """ Returns False, as we are not encrypted here
+        """
+        return False
+
+
+class TokenEncryption(MasterPassword, EncryptedTokenInterface):
+    """ This is an interface class that provides the methods required for
+        EncryptedTokenInterface and links them to the MasterPassword-provided
+        functionatlity, accordingly.
+    """
+
+    def __init__(self, *args, **kwargs):
+        EncryptedTokenInterface.__init__(self, *args, **kwargs)
+        MasterPassword.__init__(self, *args, **kwargs)
+
+    # Interface to deal with encrypted keys
+    def getPublicNames(self):
+        return [k for k, v in self.items()]
+
+    def getPrivateKeyForPublicKey(self, name):
+        token = self.get(str(name), None)
+        if token:
+            return self.decrypt_text(token)  # From Masterpassword
+
+    def add(self, token, name):
+        if str(name) in self:
+            raise KeyAlreadyInStoreException
+        self[str(name)] = self.encrypt_text(str(token))  # From Masterpassword
+
+    def updateToken(self, name, token):
+        self[str(name)] = self.encrypt_text(str(token))  # From Masterpassword
+
+    def is_encrypted(self):
+        return True
+
+
+class InRamEncryptedTokenStore(InRamStore, TokenEncryption):
+    """ An in-RAM Store that stores token **encrypted** in RAM.
+
+        Internally, this works by simply inheriting
+        :class:`beemstorage.ram.InRamStore`. The interface is defined in
+        :class:`beemstorage.interfaces.TokenInterface`.
+
+        .. note:: This module also inherits
+            :class:`beemstorage.masterpassword.MasterPassword` which offers
+            additional methods and deals with encrypting the keys.
+    """
+
+    def __init__(self, *args, **kwargs):
+        InRamStore.__init__(self, *args, **kwargs)
+        TokenEncryption.__init__(self, *args, **kwargs)
+
+
+class SqliteEncryptedTokenStore(SQLiteStore, TokenEncryption):
+    """ This is the key storage that stores the account name and the
+        **encrypted** token in the `token` table in the SQLite3 database.
+
+        Internally, this works by simply inheriting
+        :class:`beemstorage.ram.InRamStore`. The interface is defined in
+        :class:`beemstorage.interfaces.TokenInterface`.
+
+        .. note:: This module also inherits
+            :class:`beemstorage.masterpassword.MasterPassword` which offers
+            additional methods and deals with encrypting the token.
+    """
+
+    __tablename__ = "token"
+    __key__ = "name"
+    __value__ = "token"
+
+    def __init__(self, *args, **kwargs):
+        SQLiteStore.__init__(self, *args, **kwargs)
+        TokenEncryption.__init__(self, *args, **kwargs)
diff --git a/beemstorage/exceptions.py b/beemstorage/exceptions.py
new file mode 100644
index 00000000..7b30abe7
--- /dev/null
+++ b/beemstorage/exceptions.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+# Inspired by https://raw.githubusercontent.com/xeroc/python-graphenelib/master/graphenestorage/exceptions.py
+class WalletLocked(Exception):
+    pass
+
+
+class WrongMasterPasswordException(Exception):
+    """ The password provided could not properly unlock the wallet
+    """
+
+    pass
+
+
+class KeyAlreadyInStoreException(Exception):
+    """ The key of a key/value pair is already in the store
+    """
+
+    pass
diff --git a/beemstorage/interfaces.py b/beemstorage/interfaces.py
new file mode 100644
index 00000000..9b3a77bb
--- /dev/null
+++ b/beemstorage/interfaces.py
@@ -0,0 +1,257 @@
+# -*- coding: utf-8 -*-
+# Inspired by https://raw.githubusercontent.com/xeroc/python-graphenelib/master/graphenestorage/interfaces.py
+class StoreInterface(dict):
+
+    """ The store interface is the most general store that we can have.
+
+        It inherits dict and thus behaves like a dictionary. As such any
+        key/value store can be used as store with or even without an adaptor.
+
+        .. note:: This class defines ``defaults`` that are used to return
+            reasonable defaults for the library.
+
+        .. warning:: If you are trying to obtain a value for a key that does
+            **not** exist in the store, the library will **NOT** raise but
+            return a ``None`` value. This represents the biggest difference to
+            a regular ``dict`` class.
+
+        Methods that need to be implemented:
+
+          * ``def setdefault(cls, key, value)``
+          * ``def __init__(self, *args, **kwargs)``
+          * ``def __setitem__(self, key, value)``
+          * ``def __getitem__(self, key)``
+          * ``def __iter__(self)``
+          * ``def __len__(self)``
+          * ``def __contains__(self, key)``
+
+
+        .. note:: Configuration and Key classes are subclasses of this to allow
+            storing keys separate from configuration.
+
+    """
+
+    defaults = {}
+
+    @classmethod
+    def setdefault(cls, key, value):
+        """ Allows to define default values
+        """
+        cls.defaults[key] = value
+
+    def __init__(self, *args, **kwargs):
+        pass
+
+    def __setitem__(self, key, value):
+        """ Sets an item in the store
+        """
+        return dict.__setitem__(self, key, value)
+
+    def __getitem__(self, key):
+        """ Gets an item from the store as if it was a dictionary
+
+            .. note:: Special behavior! If a key is not found, ``None`` is
+                returned instead of raising an exception, unless a default
+                value is found, then that is returned.
+        """
+        if key in self:
+            return dict.__getitem__(self, key)
+        elif key in self.defaults:
+            return self.defaults[key]
+        else:
+            return None
+
+    def __iter__(self):
+        """ Iterates through the store
+        """
+        return dict.__iter__(self)
+
+    def __len__(self):
+        """ return lenght of store
+        """
+        return dict.__len__(self)
+
+    def __contains__(self, key):
+        """ Tests if a key is contained in the store.
+        """
+        return dict.__contains__(self, key)
+
+    def items(self):
+        """ Returns all items off the store as tuples
+        """
+        return dict.items(self)
+
+    def get(self, key, default=None):
+        """ Return the key if exists or a default value
+        """
+        return dict.get(self, key, default)
+
+    # Specific for this library
+    def delete(self, key):
+        """ Delete a key from the store
+        """
+        raise NotImplementedError
+
+    def wipe(self):
+        """ Wipe the store
+        """
+        raise NotImplementedError
+
+
+class KeyInterface(StoreInterface):
+    """ The KeyInterface defines the interface for key storage.
+
+        .. note:: This class inherits
+            :class:`beemstorage.interfaces.StoreInterface` and defines
+            additional key-specific methods.
+    """
+
+    def is_encrypted(self):
+        """ Returns True/False to indicate required use of unlock
+        """
+        return False
+
+    # Interface to deal with encrypted keys
+    def getPublicKeys(self):
+        """ Returns the public keys stored in the database
+        """
+        raise NotImplementedError
+
+    def getPrivateKeyForPublicKey(self, pub):
+        """ Returns the (possibly encrypted) private key that
+            corresponds to a public key
+
+           :param str pub: Public key
+
+           The encryption scheme is BIP38
+        """
+        raise NotImplementedError
+
+    def add(self, wif, pub=None):
+        """ Add a new public/private key pair (correspondence has to be
+            checked elsewhere!)
+
+           :param str pub: Public key
+           :param str wif: Private key
+        """
+        raise NotImplementedError
+
+    def delete(self, pub):
+        """ Delete a pubkey/privatekey pair from the store
+
+           :param str pub: Public key
+        """
+        raise NotImplementedError
+
+
+class EncryptedKeyInterface(KeyInterface):
+    """ The EncryptedKeyInterface extends KeyInterface to work with encrypted
+        keys
+    """
+
+    def is_encrypted(self):
+        """ Returns True/False to indicate required use of unlock
+        """
+        return True
+
+    def unlock(self, password):
+        """ Tries to unlock the wallet if required
+
+           :param str password: Plain password
+        """
+        raise NotImplementedError
+
+    def locked(self):
+        """ is the wallet locked?
+        """
+        return False
+
+    def lock(self):
+        """ Lock the wallet again
+        """
+        raise NotImplementedError
+
+
+class ConfigInterface(StoreInterface):
+    """ The BaseKeyStore defines the interface for key storage
+
+        .. note:: This class inherits
+            :class:`beemstorage.interfaces.StoreInterface` and defines
+            **no** additional configuration-specific methods.
+    """
+
+    pass
+
+
+class TokenInterface(StoreInterface):
+    """ The TokenInterface defines the interface for token storage.
+
+        .. note:: This class inherits
+            :class:`beemstorage.interfaces.StoreInterface` and defines
+            additional key-specific methods.
+    """
+
+    def is_encrypted(self):
+        """ Returns True/False to indicate required use of unlock
+        """
+        return False
+
+    # Interface to deal with encrypted keys
+    def getPublicKeys(self):
+        """ Returns the public keys stored in the database
+        """
+        raise NotImplementedError
+
+    def getPrivateKeyForPublicKey(self, pub):
+        """ Returns the (possibly encrypted) private key that
+            corresponds to a public key
+
+           :param str pub: Public key
+
+           The encryption scheme is BIP38
+        """
+        raise NotImplementedError
+
+    def add(self, wif, pub=None):
+        """ Add a new public/private key pair (correspondence has to be
+            checked elsewhere!)
+
+           :param str pub: Public key
+           :param str wif: Private key
+        """
+        raise NotImplementedError
+
+    def delete(self, pub):
+        """ Delete a pubkey/privatekey pair from the store
+
+           :param str pub: Public key
+        """
+        raise NotImplementedError
+
+
+class EncryptedTokenInterface(TokenInterface):
+    """ The EncryptedKeyInterface extends KeyInterface to work with encrypted
+        tokens
+    """
+
+    def is_encrypted(self):
+        """ Returns True/False to indicate required use of unlock
+        """
+        return True
+
+    def unlock(self, password):
+        """ Tries to unlock the wallet if required
+
+           :param str password: Plain password
+        """
+        raise NotImplementedError
+
+    def locked(self):
+        """ is the wallet locked?
+        """
+        return False
+
+    def lock(self):
+        """ Lock the wallet again
+        """
+        raise NotImplementedError
diff --git a/beemstorage/masterpassword.py b/beemstorage/masterpassword.py
new file mode 100644
index 00000000..673a94f2
--- /dev/null
+++ b/beemstorage/masterpassword.py
@@ -0,0 +1,243 @@
+# -*- coding: utf-8 -*-
+# Inspired by https://raw.githubusercontent.com/xeroc/python-graphenelib/master/graphenestorage/masterpassword.py
+import os
+import hashlib
+import logging
+import warnings
+
+from binascii import hexlify
+from beemgraphenebase.py23 import py23_bytes
+from beemgraphenebase import bip38
+from beemgraphenebase.aes import AESCipher
+from .exceptions import WrongMasterPasswordException, WalletLocked
+try:
+    import keyring
+    if not isinstance(keyring.get_keyring(), keyring.backends.fail.Keyring):
+        KEYRING_AVAILABLE = True
+    else:
+        KEYRING_AVAILABLE = False
+except ImportError:
+    KEYRING_AVAILABLE = False
+
+log = logging.getLogger(__name__)
+
+
+class MasterPassword(object):
+    """ The keys are encrypted with a Masterpassword that is stored in
+        the configurationStore. It has a checksum to verify correctness
+        of the password
+        The encrypted private keys in `keys` are encrypted with a random
+        **masterkey/masterpassword** that is stored in the configuration
+        encrypted by the user-provided password.
+
+        :param ConfigStore config: Configuration store to get access to the
+            encrypted master password
+    """
+
+    def __init__(self, config=None, **kwargs):
+        if config is None:
+            raise ValueError("If using encrypted store, a config store is required!")
+        self.config = config
+        self.password = None
+        self.decrypted_master = None
+        self.config_key = "encrypted_master_password"
+
+    @property
+    def masterkey(self):
+        """ Contains the **decrypted** master key
+        """
+        return self.decrypted_master
+
+    def has_masterpassword(self):
+        """ Tells us if the config store knows an encrypted masterpassword
+        """
+        return self.config_key in self.config
+
+    def locked(self):
+        """ Is the store locked. E.g. Is a valid password known that can be
+            used to decrypt the master key?
+        """
+        return not self.unlocked()
+
+    def unlocked(self):
+        """ Is the store unlocked so that I can decrypt the content?
+        """
+        if self.password is not None:
+            return bool(self.password)
+        else:
+            password_storage = self.config["password_storage"]
+            if (
+                "UNLOCK" in os.environ
+                and os.environ["UNLOCK"]
+                and self.config_key in self.config
+                and self.config[self.config_key]
+                and password_storage == "environment"
+            ):
+                log.debug("Trying to use environmental " "variable to unlock wallet")
+                self.unlock(os.environ.get("UNLOCK"))
+                return bool(self.password)
+            elif (
+                password_storage == "keyring"
+                and KEYRING_AVAILABLE
+                and self.config_key in self.config
+                and self.config[self.config_key]                
+            ):
+                log.debug("Trying to use keyring to unlock wallet")
+                pwd = keyring.get_password("beem", "wallet")
+                self.unlock(pwd)
+                return bool(self.password)
+        return False
+
+    def lock(self):
+        """ Lock the store so that we can no longer decrypt the content of the
+            store
+        """
+        self.password = None
+        self.decrypted_master = None
+
+    def unlock(self, password):
+        """ The password is used to encrypt this masterpassword. To
+            decrypt the keys stored in the keys database, one must use
+            BIP38, decrypt the masterpassword from the configuration
+            store with the user password, and use the decrypted
+            masterpassword to decrypt the BIP38 encrypted private keys
+            from the keys storage!
+
+            :param str password: Password to use for en-/de-cryption
+        """
+        self.password = password
+        if self.config_key in self.config and self.config[self.config_key]:
+            self._decrypt_masterpassword()
+        else:
+            self._new_masterpassword(password)
+            self._save_encrypted_masterpassword()
+
+    def wipe_masterpassword(self):
+        """ Removes the encrypted masterpassword from config storage"""
+        if self.config_key in self.config and self.config[self.config_key]:
+            self.config[self.config_key] = None
+
+    def _decrypt_masterpassword(self):
+        """ Decrypt the encrypted masterkey
+        """
+        aes = AESCipher(self.password)
+        checksum, encrypted_master = self.config[self.config_key].split("$")
+        try:
+            decrypted_master = aes.decrypt(encrypted_master)
+        except Exception:
+            self._raise_wrongmasterpassexception()
+        if checksum != self._derive_checksum(decrypted_master):
+            self._raise_wrongmasterpassexception()
+        self.decrypted_master = decrypted_master
+
+    def _raise_wrongmasterpassexception(self):
+        self.password = None
+        raise WrongMasterPasswordException
+
+    def _save_encrypted_masterpassword(self):
+        self.config[self.config_key] = self._get_encrypted_masterpassword()
+
+    def _new_masterpassword(self, password):
+        """ Generate a new random masterkey, encrypt it with the password and
+            store it in the store.
+
+            :param str password: Password to use for en-/de-cryption
+        """
+        # make sure to not overwrite an existing key
+        if self.config_key in self.config and self.config[self.config_key]:
+            raise Exception("Storage already has a masterpassword!")
+
+        self.decrypted_master = hexlify(os.urandom(32)).decode("ascii")
+
+        # Encrypt and save master
+        self.password = password
+        self._save_encrypted_masterpassword()
+        return self.masterkey
+
+    def _derive_checksum(self, s):
+        """ Derive the checksum
+
+            :param str s: Random string for which to derive the checksum
+        """
+        checksum = hashlib.sha256(bytes(s, "ascii")).hexdigest()
+        return checksum[:4]
+
+    def _get_encrypted_masterpassword(self):
+        """ Obtain the encrypted masterkey
+
+            .. note:: The encrypted masterkey is checksummed, so that we can
+                figure out that a provided password is correct or not. The
+                checksum is only 4 bytes long!
+        """
+        if not self.unlocked():
+            raise WalletLocked
+        aes = AESCipher(self.password)
+        return "{}${}".format(
+            self._derive_checksum(self.masterkey), aes.encrypt(self.masterkey)
+        )
+
+    def change_password(self, newpassword):
+        """ Change the password that allows to decrypt the master key
+        """
+        if not self.unlocked():
+            raise WalletLocked
+        self.password = newpassword
+        self._save_encrypted_masterpassword()
+
+    def decrypt(self, wif):
+        """ Decrypt the content according to BIP38
+
+            :param str wif: Encrypted key
+        """
+        if not self.unlocked():
+            raise WalletLocked
+        return format(bip38.decrypt(wif, self.masterkey), "wif")
+
+    def deriveChecksum(self, s):
+        """ Derive the checksum
+        """
+        checksum = hashlib.sha256(py23_bytes(s, "ascii")).hexdigest()
+        return checksum[:4]
+
+    def encrypt_text(self, txt):
+        """ Encrypt the content according to BIP38
+
+            :param str wif: Unencrypted key
+        """
+        if not self.unlocked():
+            raise WalletLocked
+        aes = AESCipher(self.masterkey)
+        return "{}${}".format(self.deriveChecksum(txt), aes.encrypt(txt))
+
+    def decrypt_text(self, enctxt):
+        """ Decrypt the content according to BIP38
+
+            :param str wif: Encrypted key
+        """
+        if not self.unlocked():
+            raise WalletLocked
+        aes = AESCipher(self.masterkey)
+        checksum, encrypted_text = enctxt.split("$")
+        try:
+            decrypted_text = aes.decrypt(encrypted_text)
+        except:
+            raise WrongMasterPasswordException
+        if checksum != self.deriveChecksum(decrypted_text):
+            raise WrongMasterPasswordException
+        return decrypted_text
+
+    def encrypt(self, wif):
+        """ Encrypt the content according to BIP38
+
+            :param str wif: Unencrypted key
+        """
+        if not self.unlocked():
+            raise WalletLocked
+        return format(bip38.encrypt(str(wif), self.masterkey), "encwif")
+
+    def changePassword(self, newpassword):  # pragma: no cover
+        warnings.warn(
+            "changePassword will be replaced by change_password in the future",
+            PendingDeprecationWarning,
+        )
+        return self.change_password(newpassword)
diff --git a/beemstorage/ram.py b/beemstorage/ram.py
new file mode 100644
index 00000000..b27433ad
--- /dev/null
+++ b/beemstorage/ram.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Inspired by https://raw.githubusercontent.com/xeroc/python-graphenelib/master/graphenestorage/ram.py
+from .interfaces import StoreInterface
+
+
+# StoreInterface is done first, then dict which overwrites the interface
+# methods
+class InRamStore(StoreInterface):
+    """ The InRamStore inherits
+        :class:`beemstorage.interfaces.StoreInterface` and extends it by two
+        further calls for wipe and delete.
+
+        The store is syntactically equivalent to a regular dictionary.
+
+        .. warning:: If you are trying to obtain a value for a key that does
+            **not** exist in the store, the library will **NOT** raise but
+            return a ``None`` value. This represents the biggest difference to
+            a regular ``dict`` class.
+    """
+
+    # Specific for this library
+    def delete(self, key):
+        """ Delete a key from the store
+        """
+        self.pop(key, None)
+
+    def wipe(self):
+        """ Wipe the store
+        """
+        keys = list(self.keys()).copy()
+        for key in keys:
+            self.delete(key)
diff --git a/beemstorage/sqlite.py b/beemstorage/sqlite.py
new file mode 100644
index 00000000..fff61293
--- /dev/null
+++ b/beemstorage/sqlite.py
@@ -0,0 +1,352 @@
+# -*- coding: utf-8 -*-
+# Inspired by https://raw.githubusercontent.com/xeroc/python-graphenelib/master/graphenestorage/sqlite.py
+import os
+import sqlite3
+import logging
+import shutil
+from datetime import datetime
+from appdirs import user_data_dir
+import time
+
+from .interfaces import StoreInterface
+
+log = logging.getLogger(__name__)
+timeformat = "%Y%m%d-%H%M%S"
+
+
+class SQLiteFile:
+    """ This class ensures that the user's data is stored in its OS
+        preotected user directory:
+
+        **OSX:**
+
+         * `~/Library/Application Support/<AppName>`
+
+        **Windows:**
+
+         * `C:\\Documents and Settings\\<User>\\Application Data\\Local Settings\\<AppAuthor>\\<AppName>`
+         * `C:\\Documents and Settings\\<User>\\Application Data\\<AppAuthor>\\<AppName>`
+
+        **Linux:**
+
+         * `~/.local/share/<AppName>`
+
+         Furthermore, it offers an interface to generated backups
+         in the `backups/` directory every now and then.
+
+         .. note:: The file name can be overwritten when providing a keyword
+            argument ``profile``.
+    """
+
+    def __init__(self, *args, **kwargs):
+        appauthor = "beem"
+        appname = kwargs.get("appname", "beem")
+        self.data_dir = kwargs.get("data_dir", user_data_dir(appname, appauthor))
+
+        if "profile" in kwargs:
+            self.storageDatabase = "{}.sqlite".format(kwargs["profile"])
+        else:
+            self.storageDatabase = "{}.sqlite".format(appname)
+        
+        self.sqlite_file = os.path.join(self.data_dir, self.storageDatabase)
+
+        """ Ensure that the directory in which the data is stored
+            exists
+        """
+        if os.path.isdir(self.data_dir):  # pragma: no cover
+            return
+        else:  # pragma: no cover
+            os.makedirs(self.data_dir)
+
+    def sqlite3_backup(self, backupdir):
+        """ Create timestamped database copy
+        """
+        if not os.path.isdir(backupdir):
+            os.mkdir(backupdir)
+        backup_file = os.path.join(
+            backupdir,
+            os.path.basename(self.storageDatabase) +
+            datetime.utcnow().strftime("-" + timeformat))
+        self.sqlite3_copy(self.sqlite_file, backup_file)
+
+    def sqlite3_copy(self, src, dst):
+        """Copy sql file from src to dst"""
+        if not os.path.isfile(src):
+            return
+        connection = sqlite3.connect(self.sqlite_file)
+        cursor = connection.cursor()
+        # Lock database before making a backup
+        cursor.execute('begin immediate')
+        # Make new backup file
+        shutil.copyfile(src, dst)
+        log.info("Creating {}...".format(dst))
+        # Unlock database
+        connection.rollback()
+
+    def recover_with_latest_backup(self, backupdir="backups"):
+        """ Replace database with latest backup"""
+        file_date = 0
+        if not os.path.isdir(backupdir):
+            backupdir = os.path.join(self.data_dir, backupdir)
+        if not os.path.isdir(backupdir):
+            return
+        newest_backup_file = None
+        for filename in os.listdir(backupdir):
+            backup_file = os.path.join(backupdir, filename)
+            if os.stat(backup_file).st_ctime > file_date:
+                if os.path.isfile(backup_file):
+                    file_date = os.stat(backup_file).st_ctime
+                    newest_backup_file = backup_file
+        if newest_backup_file is not None:
+            self.sqlite3_copy(newest_backup_file, self.sqlite_file)
+
+    def clean_data(self, backupdir="backups"):
+        """ Delete files older than 70 days
+        """
+        log.info("Cleaning up old backups")
+        backupdir = os.path.join(self.data_dir, backupdir)
+        for filename in os.listdir(backupdir):
+            backup_file = os.path.join(backupdir, filename)
+            if os.stat(backup_file).st_ctime < (time.time() - 70 * 86400):
+                if os.path.isfile(backup_file):
+                    os.remove(backup_file)
+                    log.info("Deleting {}...".format(backup_file))
+
+    def refreshBackup(self):
+        """ Make a new backup
+        """
+        backupdir = os.path.join(self.data_dir, "backups")
+        self.sqlite3_backup(backupdir)
+        self.clean_data(backupdir)
+
+
+class SQLiteCommon(object):
+    """ This class abstracts away common sqlite3 operations.
+
+        This class should not be used directly.
+
+        When inheriting from this class, the following instance members must
+        be defined:
+
+            * ``sqlite_file``: Path to the SQLite Database file
+    """
+    def sql_fetchone(self, query):
+        connection = sqlite3.connect(self.sqlite_file)
+        try:
+            cursor = connection.cursor()
+            cursor.execute(*query)
+            result = cursor.fetchone()
+        finally:
+            connection.close()
+        return result
+
+    def sql_fetchall(self, query):
+        connection = sqlite3.connect(self.sqlite_file)
+        try:
+            cursor = connection.cursor()
+            cursor.execute(*query)
+            results = cursor.fetchall()
+        finally:
+            connection.close()
+        return results
+
+    def sql_execute(self, query, lastid=False):
+        connection = sqlite3.connect(self.sqlite_file)
+        try:
+            cursor = connection.cursor()
+            cursor.execute(*query)
+            connection.commit()
+        except:
+            connection.close()
+            raise
+        ret = None
+        try:
+            if lastid:
+                cursor = connection.cursor()
+                cursor.execute("SELECT last_insert_rowid();")
+                ret = cursor.fetchone()[0]
+        finally:
+            connection.close()
+        return ret
+
+
+class SQLiteStore(SQLiteFile, SQLiteCommon, StoreInterface):
+    """ The SQLiteStore deals with the sqlite3 part of storing data into a
+        database file.
+
+        .. note:: This module is limited to two columns and merely stores
+            key/value pairs into the sqlite database
+
+        On first launch, the database file as well as the tables are created
+        automatically.
+
+        When inheriting from this class, the following three class members must
+        be defined:
+
+            * ``__tablename__``: Name of the table
+            * ``__key__``: Name of the key column
+            * ``__value__``: Name of the value column
+    """
+
+    #:
+    __tablename__ = None
+    __key__ = None
+    __value__ = None
+
+    def __init__(self, *args, **kwargs):
+        #: Storage
+        SQLiteFile.__init__(self, *args, **kwargs)
+        StoreInterface.__init__(self, *args, **kwargs)
+        if self.__tablename__ is None or self.__key__ is None or self.__value__ is None:
+            raise ValueError("Values missing for tablename, key, or value!")
+        if not self.exists():  # pragma: no cover
+            self.create()
+
+    def _haveKey(self, key):
+        """ Is the key `key` available?
+        """
+        query = (
+            "SELECT {} FROM {} WHERE {}=?".format(
+                self.__value__,
+                self.__tablename__,
+                self.__key__
+            ), (key,))
+        return True if self.sql_fetchone(query) else False
+
+    def __setitem__(self, key, value):
+        """ Sets an item in the store
+
+            :param str key: Key
+            :param str value: Value
+        """
+        if self._haveKey(key):
+            query = (
+                "UPDATE {} SET {}=? WHERE {}=?".format(
+                    self.__tablename__, self.__value__, self.__key__
+                ),
+                (value, key),
+            )
+        else:
+            query = (
+                "INSERT INTO {} ({}, {}) VALUES (?, ?)".format(
+                    self.__tablename__, self.__key__, self.__value__
+                ),
+                (key, value),
+            )
+        self.sql_execute(query)
+
+    def __getitem__(self, key):
+        """ Gets an item from the store as if it was a dictionary
+
+            :param str value: Value
+        """
+        query = (
+            "SELECT {} FROM {} WHERE {}=?".format(
+                self.__value__,
+                self.__tablename__,
+                self.__key__
+            ), (key,))
+        result = self.sql_fetchone(query)
+        if result:
+            return result[0]
+        else:
+            if key in self.defaults:
+                return self.defaults[key]
+            else:
+                return None
+
+    def __iter__(self):
+        """ Iterates through the store
+        """
+        return iter(self.keys())
+
+    def keys(self):
+        query = ("SELECT {} from {}".format(
+            self.__key__,
+            self.__tablename__), )
+        return [x[0] for x in self.sql_fetchall(query)]
+
+    def __len__(self):
+        """ return lenght of store
+        """
+        query = ("SELECT id from {}".format(self.__tablename__), )
+        return len(self.sql_fetchall(query))
+
+    def __contains__(self, key):
+        """ Tests if a key is contained in the store.
+
+            May test againsts self.defaults
+
+            :param str value: Value
+        """
+        if self._haveKey(key) or key in self.defaults:
+            return True
+        else:
+            return False
+
+    def items(self):
+        """ returns all items off the store as tuples
+        """
+        query = ("SELECT {}, {} from {}".format(
+            self.__key__,
+            self.__value__,
+            self.__tablename__), )
+        r = []
+        for key, value in self.sql_fetchall(query):
+            r.append((key, value))
+        return r
+
+    def get(self, key, default=None):
+        """ Return the key if exists or a default value
+
+            :param str value: Value
+            :param str default: Default value if key not present
+        """
+        if key in self:
+            return self.__getitem__(key)
+        else:
+            return default
+
+    # Specific for this library
+    def delete(self, key):
+        """ Delete a key from the store
+
+            :param str value: Value
+        """
+        query = (
+            "DELETE FROM {} WHERE {}=?".format(
+                self.__tablename__,
+                self.__key__
+            ), (key,))
+        self.sql_execute(query)
+
+    def wipe(self):
+        """ Wipe the store
+        """
+        query = ("DELETE FROM {}".format(self.__tablename__), )
+        self.sql_execute(query)
+
+    def exists(self):
+        """ Check if the database table exists
+        """
+        query = ("SELECT name FROM sqlite_master " +
+                 "WHERE type='table' AND name=?",
+                 (self.__tablename__, ))
+        return True if self.sql_fetchone(query) else False
+
+    def create(self):  # pragma: no cover
+        """ Create the new table in the SQLite database
+        """
+        query = ((
+            """
+            CREATE TABLE {} (
+                id INTEGER PRIMARY KEY AUTOINCREMENT,
+                {} STRING(256),
+                {} STRING(256)
+            )"""
+        ).format(
+            self.__tablename__,
+            self.__key__,
+            self.__value__
+        ), )
+        self.sql_execute(query)
diff --git a/docs/beem.aes.rst b/docs/beem.aes.rst
deleted file mode 100644
index e784b7cd..00000000
--- a/docs/beem.aes.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-beem\.aes
-=========
-
-.. automodule:: beem.aes
-    :members:
-    :undoc-members:
-    :show-inheritance:
\ No newline at end of file
diff --git a/docs/beemgraphenebase.aes.rst b/docs/beemgraphenebase.aes.rst
new file mode 100644
index 00000000..817bc8f0
--- /dev/null
+++ b/docs/beemgraphenebase.aes.rst
@@ -0,0 +1,7 @@
+beemgraphenebase\.aes
+=====================
+
+.. automodule:: beemgraphenebase.aes
+    :members:
+    :undoc-members:
+    :show-inheritance:
\ No newline at end of file
diff --git a/docs/beemstorage.base.rst b/docs/beemstorage.base.rst
new file mode 100644
index 00000000..240b3c05
--- /dev/null
+++ b/docs/beemstorage.base.rst
@@ -0,0 +1,7 @@
+beemstorage\.base
+=================
+
+.. automodule:: beemstorage.base
+    :members:
+    :undoc-members:
+    :show-inheritance:
\ No newline at end of file
diff --git a/docs/beemstorage.exceptions.rst b/docs/beemstorage.exceptions.rst
new file mode 100644
index 00000000..9e3a20b1
--- /dev/null
+++ b/docs/beemstorage.exceptions.rst
@@ -0,0 +1,7 @@
+beemstorage\.exceptions
+=======================
+
+.. automodule:: beemstorage.exceptions
+    :members:
+    :undoc-members:
+    :show-inheritance:
\ No newline at end of file
diff --git a/docs/beemstorage.interfaces.rst b/docs/beemstorage.interfaces.rst
new file mode 100644
index 00000000..fb4b45fe
--- /dev/null
+++ b/docs/beemstorage.interfaces.rst
@@ -0,0 +1,7 @@
+beemstorage\.interfaces
+=======================
+
+.. automodule:: beemstorage.interfaces
+    :members:
+    :undoc-members:
+    :show-inheritance:
\ No newline at end of file
diff --git a/docs/beemstorage.masterpassword.rst b/docs/beemstorage.masterpassword.rst
new file mode 100644
index 00000000..1d34c62c
--- /dev/null
+++ b/docs/beemstorage.masterpassword.rst
@@ -0,0 +1,7 @@
+beemstorage\.masterpassword
+===========================
+
+.. automodule:: beemstorage.masterpassword
+    :members:
+    :undoc-members:
+    :show-inheritance:
\ No newline at end of file
diff --git a/docs/beemstorage.ram.rst b/docs/beemstorage.ram.rst
new file mode 100644
index 00000000..3497f986
--- /dev/null
+++ b/docs/beemstorage.ram.rst
@@ -0,0 +1,7 @@
+beemstorage\.ram
+================
+
+.. automodule:: beemstorage.ram
+    :members:
+    :undoc-members:
+    :show-inheritance:
\ No newline at end of file
diff --git a/docs/beemstorage.sqlite.rst b/docs/beemstorage.sqlite.rst
new file mode 100644
index 00000000..69afcc2b
--- /dev/null
+++ b/docs/beemstorage.sqlite.rst
@@ -0,0 +1,7 @@
+beemstorage\.sqlite
+===================
+
+.. automodule:: beemstorage.sqlite
+    :members:
+    :undoc-members:
+    :show-inheritance:
\ No newline at end of file
diff --git a/docs/modules.rst b/docs/modules.rst
index a2bc2d45..fbdb33e8 100644
--- a/docs/modules.rst
+++ b/docs/modules.rst
@@ -7,7 +7,6 @@ beem Modules
 .. toctree::
 
    beem.account
-   beem.aes
    beem.amount
    beem.asciichart
    beem.asset
@@ -71,6 +70,7 @@ beemgraphenebase Modules
 .. toctree::
 
    beemgraphenebase.account
+   beemgraphenebase.aes
    beemgraphenebase.base58
    beemgraphenebase.bip32
    beemgraphenebase.bip38
@@ -79,4 +79,17 @@ beemgraphenebase Modules
    beemgraphenebase.objecttypes
    beemgraphenebase.operations
    beemgraphenebase.signedtransactions
-   beemgraphenebase.unsignedtransactions
\ No newline at end of file
+   beemgraphenebase.unsignedtransactions
+
+
+beemstorage Modules
+-------------------
+
+.. toctree::
+
+   beemstorage.base
+   beemstorage.exceptions
+   beemstorage.interfaces
+   beemstorage.masterpassword
+   beemstorage.ram
+   beemstorage.sqlite
diff --git a/setup.py b/setup.py
index 8aabccd7..8a9029e2 100755
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,7 @@ except LookupError:
     ascii = codecs.lookup('ascii')
     codecs.register(lambda name, enc=ascii: {True: enc}.get(name == 'mbcs'))
 
-VERSION = '0.23.13'
+VERSION = '0.24.0'
 
 tests_require = ['mock >= 2.0.0', 'pytest', 'pytest-mock', 'parameterized']
 
diff --git a/tests/beem/test_aes.py b/tests/beem/test_aes.py
index d3688903..7ff65083 100644
--- a/tests/beem/test_aes.py
+++ b/tests/beem/test_aes.py
@@ -10,7 +10,7 @@ import random
 import unittest
 import base64
 from pprint import pprint
-from beem.aes import AESCipher
+from beemgraphenebase.aes import AESCipher
 
 
 class Testcases(unittest.TestCase):
diff --git a/tests/beem/test_connection.py b/tests/beem/test_connection.py
index 6cfabbc3..f3f7f409 100644
--- a/tests/beem/test_connection.py
+++ b/tests/beem/test_connection.py
@@ -1,5 +1,5 @@
 import unittest
-from beem import Steem
+from beem import Hive, Steem
 from beem.account import Account
 from beem.instance import set_shared_steem_instance, SharedInstance
 from beem.blockchainobject import BlockchainObject
@@ -13,15 +13,15 @@ class Testcases(unittest.TestCase):
 
     def test_stm1stm2(self):
         nodelist = NodeList()
-        nodelist.update_nodes(steem_instance=Steem(node=nodelist.get_nodes(exclude_limited=False), num_retries=10))
+        nodelist.update_nodes(steem_instance=Hive(node=nodelist.get_hive_nodes(), num_retries=10))
         b1 = Steem(
             node="https://api.steemit.com",
             nobroadcast=True,
             num_retries=10
         )
-        node_list = nodelist.get_nodes(exclude_limited=True)
+        node_list = nodelist.get_hive_nodes()
 
-        b2 = Steem(
+        b2 = Hive(
             node=node_list,
             nobroadcast=True,
             num_retries=10
@@ -31,10 +31,10 @@ class Testcases(unittest.TestCase):
 
     def test_default_connection(self):
         nodelist = NodeList()
-        nodelist.update_nodes(steem_instance=Steem(node=nodelist.get_nodes(exclude_limited=False), num_retries=10))
+        nodelist.update_nodes(steem_instance=Hive(node=nodelist.get_hive_nodes(), num_retries=10))
 
-        b2 = Steem(
-            node=nodelist.get_nodes(exclude_limited=True),
+        b2 = Hive(
+            node=nodelist.get_hive_nodes(),
             nobroadcast=True,
         )
         set_shared_steem_instance(b2)
diff --git a/tests/beem/test_hive.py b/tests/beem/test_hive.py
index 85a90282..35922886 100644
--- a/tests/beem/test_hive.py
+++ b/tests/beem/test_hive.py
@@ -12,18 +12,16 @@ import json
 from pprint import pprint
 from beem import Hive, exceptions
 from beem.amount import Amount
-from beem.memo import Memo
 from beem.version import version as beem_version
-from beem.wallet import Wallet
-from beem.witness import Witness
 from beem.account import Account
 from beemgraphenebase.account import PrivateKey
-from beem.instance import set_shared_steem_instance
 from beem.nodelist import NodeList
 # Py3 compatibility
 import sys
 
 wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"
+wif2 = "5JKu2dFfjKAcD6aP1HqBDxMNbdwtvPS99CaxBzvMYhY94Pt6RDS"
+wif3 = "5K1daXjehgPZgUHz6kvm55ahEArBHfCHLy6ew8sT7sjDb76PU2P"
 
 
 class Testcases(unittest.TestCase):
@@ -37,7 +35,7 @@ class Testcases(unittest.TestCase):
             nobroadcast=True,
             unsigned=True,
             data_refresh_time_seconds=900,
-            keys={"active": wif, "owner": wif, "memo": wif},
+            keys={"active": wif, "owner": wif2, "memo": wif3},
             num_retries=10)
         cls.account = Account("test", full=True, steem_instance=cls.bts)
 
@@ -65,7 +63,7 @@ class Testcases(unittest.TestCase):
                     nobroadcast=True,
                     unsigned=True,
                     data_refresh_time_seconds=900,
-                    keys={"active": wif, "owner": wif, "memo": wif},
+                    keys={"active": wif, "owner": wif2, "memo": wif3},
                     num_retries=10)
         core_unit = "STM"
         name = ''.join(random.choice(string.ascii_lowercase) for _ in range(12))
@@ -134,7 +132,7 @@ class Testcases(unittest.TestCase):
                     nobroadcast=True,
                     unsigned=True,
                     data_refresh_time_seconds=900,
-                    keys={"active": wif, "owner": wif, "memo": wif},
+                    keys={"active": wif, "owner": wif2, "memo": wif3},
                     num_retries=10)
         core_unit = "STM"
         name = ''.join(random.choice(string.ascii_lowercase) for _ in range(12))
@@ -364,7 +362,7 @@ class Testcases(unittest.TestCase):
         bts = Hive(node=self.nodelist.get_hive_nodes(),
                     offline=True,
                     data_refresh_time_seconds=900,
-                    keys={"active": wif, "owner": wif, "memo": wif})
+                    keys={"active": wif, "owner": wif2, "memo": wif3})
         bts.refresh_data("feed_history")
         self.assertTrue(bts.get_feed_history(use_stored_data=False) is None)
         self.assertTrue(bts.get_feed_history(use_stored_data=True) is None)
@@ -393,7 +391,7 @@ class Testcases(unittest.TestCase):
         bts = Hive(node=self.nodelist.get_hive_nodes(),
                     nobroadcast=True,
                     data_refresh_time_seconds=900,
-                    keys={"active": wif, "owner": wif, "memo": wif},
+                    keys={"active": wif, "owner": wif2, "memo": wif3},
                     num_retries=10)
         self.assertTrue(bts.is_hive)
         self.assertTrue(bts.get_feed_history(use_stored_data=False) is not None)
diff --git a/tests/beem/test_steem.py b/tests/beem/test_steem.py
index 8e25ba59..8a730260 100644
--- a/tests/beem/test_steem.py
+++ b/tests/beem/test_steem.py
@@ -12,19 +12,16 @@ import json
 from pprint import pprint
 from beem import Steem, exceptions
 from beem.amount import Amount
-from beem.memo import Memo
 from beem.version import version as beem_version
-from beem.wallet import Wallet
-from beem.witness import Witness
 from beem.account import Account
 from beemgraphenebase.account import PrivateKey
-from beem.instance import set_shared_steem_instance
 from beem.nodelist import NodeList
 # Py3 compatibility
 import sys
 
 wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"
-
+wif2 = "5JKu2dFfjKAcD6aP1HqBDxMNbdwtvPS99CaxBzvMYhY94Pt6RDS"
+wif3 = "5K1daXjehgPZgUHz6kvm55ahEArBHfCHLy6ew8sT7sjDb76PU2P"
 
 class Testcases(unittest.TestCase):
 
@@ -37,7 +34,7 @@ class Testcases(unittest.TestCase):
             nobroadcast=True,
             unsigned=True,
             data_refresh_time_seconds=900,
-            keys={"active": wif, "owner": wif, "memo": wif},
+            keys={"active": wif, "owner": wif2, "memo": wif3},
             num_retries=10)
         cls.account = Account("test", full=True, steem_instance=cls.bts)
 
@@ -65,7 +62,7 @@ class Testcases(unittest.TestCase):
                     nobroadcast=True,
                     unsigned=True,
                     data_refresh_time_seconds=900,
-                    keys={"active": wif, "owner": wif, "memo": wif},
+                    keys={"active": wif, "owner": wif2, "memo": wif3},
                     num_retries=10)
         core_unit = "STM"
         name = ''.join(random.choice(string.ascii_lowercase) for _ in range(12))
@@ -134,7 +131,7 @@ class Testcases(unittest.TestCase):
                     nobroadcast=True,
                     unsigned=True,
                     data_refresh_time_seconds=900,
-                    keys={"active": wif, "owner": wif, "memo": wif},
+                    keys={"active": wif, "owner": wif2, "memo": wif3},
                     num_retries=10)
         core_unit = "STM"
         name = ''.join(random.choice(string.ascii_lowercase) for _ in range(12))
@@ -364,7 +361,7 @@ class Testcases(unittest.TestCase):
         bts = Steem(node=self.nodelist.get_steem_nodes(),
                     offline=True,
                     data_refresh_time_seconds=900,
-                    keys={"active": wif, "owner": wif, "memo": wif})
+                    keys={"active": wif, "owner": wif2, "memo": wif3})
         bts.refresh_data("feed_history")
         self.assertTrue(bts.get_feed_history(use_stored_data=False) is None)
         self.assertTrue(bts.get_feed_history(use_stored_data=True) is None)
@@ -393,7 +390,7 @@ class Testcases(unittest.TestCase):
         bts = Steem(node=self.nodelist.get_steem_nodes(),
                     nobroadcast=True,
                     data_refresh_time_seconds=900,
-                    keys={"active": wif, "owner": wif, "memo": wif},
+                    keys={"active": wif, "owner": wif2, "memo": wif3},
                     num_retries=10)
         self.assertTrue(bts.get_feed_history(use_stored_data=False) is not None)
         self.assertTrue(bts.get_reward_funds(use_stored_data=False) is not None)
diff --git a/tests/beem/test_storage.py b/tests/beem/test_storage.py
index 7dd89f17..cca88694 100644
--- a/tests/beem/test_storage.py
+++ b/tests/beem/test_storage.py
@@ -49,7 +49,7 @@ class Testcases(unittest.TestCase):
 
         cls.wallet = Wallet(steem_instance=cls.stm)
         cls.wallet.wipe(True)
-        cls.wallet.newWallet(pwd="TestingOneTwoThree")
+        cls.wallet.newWallet("TestingOneTwoThree")
         cls.wallet.unlock(pwd="TestingOneTwoThree")
         cls.wallet.addPrivateKey(wif)
 
diff --git a/tests/beem/test_testnet.py b/tests/beem/test_testnet.py
index baaa3bb3..b26963f8 100644
--- a/tests/beem/test_testnet.py
+++ b/tests/beem/test_testnet.py
@@ -12,9 +12,9 @@ from beem import Steem
 from beem.exceptions import (
     InsufficientAuthorityError,
     MissingKeyError,
-    InvalidWifError,
-    WalletLocked
+    InvalidWifError
 )
+from beemstorage.exceptions import WalletLocked
 from beemapi import exceptions
 from beem.amount import Amount
 from beem.witness import Witness
diff --git a/tests/beem/test_txbuffers.py b/tests/beem/test_txbuffers.py
index c977e6de..63838310 100644
--- a/tests/beem/test_txbuffers.py
+++ b/tests/beem/test_txbuffers.py
@@ -17,14 +17,16 @@ from beem.amount import Amount
 from beem.exceptions import (
     InsufficientAuthorityError,
     MissingKeyError,
-    InvalidWifError,
-    WalletLocked
+    InvalidWifError
 )
+from beemstorage.exceptions import WalletLocked
 from beemapi import exceptions
 from beem.wallet import Wallet
 from beem.utils import formatTimeFromNow
 from beem.nodelist import NodeList
 wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"
+wif2 = "5JKu2dFfjKAcD6aP1HqBDxMNbdwtvPS99CaxBzvMYhY94Pt6RDS"
+wif3 = "5K1daXjehgPZgUHz6kvm55ahEArBHfCHLy6ew8sT7sjDb76PU2P"
 
 
 class Testcases(unittest.TestCase):
@@ -36,14 +38,14 @@ class Testcases(unittest.TestCase):
         node_list = nodelist.get_nodes(exclude_limited=True)
         cls.stm = Steem(
             node=node_list,
-            keys={"active": wif, "owner": wif, "memo": wif},
+            keys={"active": wif, "owner": wif2, "memo": wif3},
             nobroadcast=True,
             num_retries=10
         )
         cls.steemit = Steem(
             node="https://api.steemit.com",
             nobroadcast=True,
-            keys={"active": wif, "owner": wif, "memo": wif},
+            keys={"active": wif, "owner": wif2, "memo": wif3},
             num_retries=10
         )
         set_shared_steem_instance(cls.stm)
diff --git a/tests/beem/test_wallet.py b/tests/beem/test_wallet.py
index 7278cf51..78c8dc07 100644
--- a/tests/beem/test_wallet.py
+++ b/tests/beem/test_wallet.py
@@ -138,30 +138,3 @@ class Testcases(unittest.TestCase):
         ):
             self.wallet.getPostingKeysForAccount("test")
 
-    def test_encrypt(self):
-        stm = self.stm
-        self.wallet.steem = stm
-        self.wallet.unlock(pwd="TestingOneTwoThree")
-        self.wallet.masterpassword = "TestingOneTwoThree"
-        self.assertEqual([self.wallet.encrypt_wif("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"),
-                          self.wallet.encrypt_wif("5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR")],
-                         ["6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi",
-                          "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg"])
-        self.wallet.masterpassword = "Satoshi"
-        self.assertEqual([self.wallet.encrypt_wif("5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5")],
-                         ["6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq"])
-        self.wallet.masterpassword = "TestingOneTwoThree"
-
-    def test_deencrypt(self):
-        stm = self.stm
-        self.wallet.steem = stm
-        self.wallet.unlock(pwd="TestingOneTwoThree")
-        self.wallet.masterpassword = "TestingOneTwoThree"
-        self.assertEqual([self.wallet.decrypt_wif("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi"),
-                          self.wallet.decrypt_wif("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg")],
-                         ["5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd",
-                          "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"])
-        self.wallet.masterpassword = "Satoshi"
-        self.assertEqual([self.wallet.decrypt_wif("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq")],
-                         ["5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"])
-        self.wallet.masterpassword = "TestingOneTwoThree"
diff --git a/tests/beemapi/test_noderpc.py b/tests/beemapi/test_noderpc.py
index 43f15745..d15e41c9 100644
--- a/tests/beemapi/test_noderpc.py
+++ b/tests/beemapi/test_noderpc.py
@@ -21,6 +21,8 @@ from beem.nodelist import NodeList
 import sys
 
 wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"
+wif2 = "5JKu2dFfjKAcD6aP1HqBDxMNbdwtvPS99CaxBzvMYhY94Pt6RDS"
+wif3 = "5K1daXjehgPZgUHz6kvm55ahEArBHfCHLy6ew8sT7sjDb76PU2P"
 core_unit = "STM"
 
 
@@ -38,7 +40,7 @@ class Testcases(unittest.TestCase):
         cls.appbase = Steem(
             node=cls.nodes,
             nobroadcast=True,
-            keys={"active": wif, "owner": wif, "memo": wif},
+            keys={"active": wif, "owner": wif2, "memo": wif3},
             num_retries=10
         )
         cls.rpc = NodeRPC(urls=cls.nodes_steemit)
diff --git a/tests/beemstorage/__init__.py b/tests/beemstorage/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/beemstorage/test_keystorage.py b/tests/beemstorage/test_keystorage.py
new file mode 100644
index 00000000..ee86c5b1
--- /dev/null
+++ b/tests/beemstorage/test_keystorage.py
@@ -0,0 +1,111 @@
+from builtins import chr
+from builtins import range
+from builtins import str
+import unittest
+import hashlib
+from binascii import hexlify, unhexlify
+import os
+import json
+from pprint import pprint
+from beemstorage import SqliteEncryptedKeyStore, InRamEncryptedKeyStore, InRamPlainKeyStore, SqlitePlainKeyStore, InRamConfigurationStore
+from beemstorage import InRamEncryptedTokenStore, InRamPlainTokenStore
+from beemstorage.exceptions import WalletLocked, KeyAlreadyInStoreException
+from beemgraphenebase.account import PrivateKey
+from beemgraphenebase.bip38 import SaltException
+
+
+
+def pubprivpair(wif):
+    return (str(wif), str(PrivateKey(wif).pubkey))
+
+
+class Testcases(unittest.TestCase):
+
+    def test_inramkeystore(self):
+        self.do_keystore(InRamPlainKeyStore())
+
+    def test_inramencryptedkeystore(self):
+        self.do_keystore(
+            InRamEncryptedKeyStore(config=InRamConfigurationStore())
+        )
+
+    def test_sqlitekeystore(self):
+        s = SqlitePlainKeyStore(profile="testing")
+        s.wipe()
+        self.do_keystore(s)
+        self.assertFalse(s.is_encrypted())
+
+    def test_sqliteencryptedkeystore(self):
+        self.do_keystore(
+            SqliteEncryptedKeyStore(
+                profile="testing", config=InRamConfigurationStore()
+            )
+        )
+
+    def do_keystore(self, keys):
+        keys.wipe()
+        password = "foobar"
+    
+        if isinstance(
+            keys, (SqliteEncryptedKeyStore, InRamEncryptedKeyStore)
+        ):
+            keys.config.wipe()
+            with self.assertRaises(WalletLocked):
+                keys.decrypt(
+                    "6PRViepa2zaXXGEQTYUsoLM1KudLmNBB1t812jtdKx1TEhQtvxvmtEm6Yh"
+                )
+            with self.assertRaises(WalletLocked):
+                keys.encrypt(
+                    "6PRViepa2zaXXGEQTYUsoLM1KudLmNBB1t812jtdKx1TEhQtvxvmtEm6Yh"
+                )
+            with self.assertRaises(WalletLocked):
+                keys._get_encrypted_masterpassword()
+    
+            # set the first MasterPassword here!
+            keys._new_masterpassword(password)
+            keys.lock()
+            keys.unlock(password)
+            assert keys.unlocked()
+            assert keys.is_encrypted()
+    
+            with self.assertRaises(SaltException):
+                keys.decrypt(
+                    "6PRViepa2zaXXGEQTYUsoLM1KudLmNBB1t812jtdKx1TEhQtvxvmtEm6Yh"
+                )
+    
+        keys.add(*pubprivpair("5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"))
+        # Duplicate key
+        with self.assertRaises(KeyAlreadyInStoreException):
+            keys.add(
+                *pubprivpair("5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3")
+            )
+        self.assertIn(
+            "STM6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
+            keys.getPublicKeys(),
+        )
+    
+        self.assertEqual(
+            keys.getPrivateKeyForPublicKey(
+                "STM6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV"
+            ),
+            "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3",
+        )
+        self.assertEqual(keys.getPrivateKeyForPublicKey("GPH6MRy"), None)
+        self.assertEqual(len(keys.getPublicKeys()), 1)
+        keys.add(*pubprivpair("5Hqr1Rx6v3MLAvaYCxLYqaSEsm4eHaDFkLksPF2e1sDS7omneaZ"))
+        self.assertEqual(len(keys.getPublicKeys()), 2)
+        self.assertEqual(
+            keys.getPrivateKeyForPublicKey(
+                "STM5u9tEsKaqtCpKibrXJAMhaRUVBspB5pr9X34PPdrSbvBb6ajZY"
+            ),
+            "5Hqr1Rx6v3MLAvaYCxLYqaSEsm4eHaDFkLksPF2e1sDS7omneaZ",
+        )
+        keys.delete("STM5u9tEsKaqtCpKibrXJAMhaRUVBspB5pr9X34PPdrSbvBb6ajZY")
+        self.assertEqual(len(keys.getPublicKeys()), 1)
+    
+        if isinstance(
+            keys, (SqliteEncryptedKeyStore, InRamEncryptedKeyStore)
+        ):
+            keys.lock()
+            keys.wipe()
+            keys.config.wipe()
diff --git a/tests/beemstorage/test_masterpassword.py b/tests/beemstorage/test_masterpassword.py
new file mode 100644
index 00000000..74222813
--- /dev/null
+++ b/tests/beemstorage/test_masterpassword.py
@@ -0,0 +1,74 @@
+from builtins import chr
+from builtins import range
+from builtins import str
+import unittest
+import hashlib
+from binascii import hexlify, unhexlify
+import os
+import json
+from pprint import pprint
+from beemstorage.base import InRamConfigurationStore, InRamEncryptedKeyStore
+from beemstorage.exceptions import WrongMasterPasswordException
+
+
+class Testcases(unittest.TestCase):
+    def test_masterpassword(self):
+        password = "foobar"
+        config = InRamConfigurationStore()
+        keys = InRamEncryptedKeyStore(config=config)
+        self.assertFalse(keys.has_masterpassword())
+        master = keys._new_masterpassword(password)
+        self.assertEqual(
+            len(master),
+            len("66eaab244153031e8172e6ffed321" "7288515ddb63646bbefa981a654bdf25b9f"),
+        )
+        with self.assertRaises(Exception):
+            keys._new_masterpassword(master)
+
+        keys.lock()
+
+        with self.assertRaises(Exception):
+            keys.change_password("foobar")
+
+        keys.unlock(password)
+        self.assertEqual(keys.decrypted_master, master)
+
+        new_pass = "new_secret_password"
+        keys.change_password(new_pass)
+        keys.lock()
+        keys.unlock(new_pass)
+        self.assertEqual(keys.decrypted_master, master)
+
+    def test_wrongmastermass(self):
+        config = InRamConfigurationStore()
+        keys = InRamEncryptedKeyStore(config=config)
+        keys._new_masterpassword("foobar")
+        keys.lock()
+        with self.assertRaises(WrongMasterPasswordException):
+            keys.unlock("foobar2")
+
+    def test_masterpwd(self):
+        with self.assertRaises(Exception):
+            InRamEncryptedKeyStore()
+        config = InRamConfigurationStore()
+        config["password_storage"] = "environment"
+        keys = InRamEncryptedKeyStore(config=config)
+        self.assertTrue(keys.locked())
+        keys.unlock("foobar")
+        keys.password = "FOoo"
+        with self.assertRaises(Exception):
+            keys._decrypt_masterpassword()
+        keys.lock()
+
+        with self.assertRaises(WrongMasterPasswordException):
+            keys.unlock("foobar2")
+
+        with self.assertRaises(Exception):
+            keys._get_encrypted_masterpassword()
+
+        self.assertFalse(keys.unlocked())
+
+        os.environ["UNLOCK"] = "foobar"
+        self.assertTrue(keys.unlocked())
+
+        self.assertFalse(keys.locked())
diff --git a/tests/beemstorage/test_sqlite.py b/tests/beemstorage/test_sqlite.py
new file mode 100644
index 00000000..f02af06f
--- /dev/null
+++ b/tests/beemstorage/test_sqlite.py
@@ -0,0 +1,41 @@
+from builtins import chr
+from builtins import range
+from builtins import str
+import unittest
+import hashlib
+from binascii import hexlify, unhexlify
+import os
+import json
+from pprint import pprint
+from beemstorage.sqlite import SQLiteStore
+
+class MyStore(SQLiteStore):
+    __tablename__ = "testing"
+    __key__ = "key"
+    __value__ = "value"
+
+    defaults = {"default": "value"}
+
+
+class Testcases(unittest.TestCase):
+    def test_init(self):
+        store = MyStore()
+        self.assertEqual(store.storageDatabase, "beem.sqlite")
+        store = MyStore(profile="testing")
+        self.assertEqual(store.storageDatabase, "testing.sqlite")
+
+        directory = "/tmp/temporaryFolder"
+        expected = os.path.join(directory, "testing.sqlite")
+
+        store = MyStore(profile="testing", data_dir=directory)
+        self.assertEqual(store.sqlite_file, expected)
+
+    def test_initialdata(self):
+        store = MyStore()
+        store["foobar"] = "banana"
+        self.assertEqual(store["foobar"], "banana")
+
+        self.assertIsNone(store["empty"])
+
+        self.assertEqual(store["default"], "value")
+        self.assertEqual(len(store), 1)
\ No newline at end of file
-- 
GitLab