فهرست منبع

Co-existing hashed and non-hashed password

This set of changes allows co-existing of both types
of keys in code. The next step would be to silently migrate
between the two.
Co-existing hashed and non-hashed password

This set of changes allows co-existing of both types
of keys in code. The next step would be to silently migrate
between the two.
oz123 11 سال پیش
والد
کامیت
47967c73e1
7فایلهای تغییر یافته به همراه87 افزوده شده و 20 حذف شده
  1. 4 1
      pwman/__init__.py
  2. 1 0
      pwman/data/database.py
  3. 11 8
      pwman/data/drivers/sqlite.py
  4. 2 2
      pwman/data/factory.py
  5. 1 1
      pwman/tests/db_tests.py
  6. 67 6
      pwman/util/crypto.py
  7. 1 2
      scripts/pwman3

+ 4 - 1
pwman/__init__.py

@@ -25,8 +25,11 @@ from util import config
 import sys
 import re
 import data.factory
+from pwman.data.database import __DB_FORMAT__
 
 appname = "pwman3"
+
+
 try:
     version = pkg_resources.get_distribution('pwman3').version
 except pkg_resources.DistributionNotFound:  # pragma: no cover
@@ -183,5 +186,5 @@ def get_db_version(config, dbtype, args):
         if dbver < 0.4 and not args.dbconvert:
             print(_db_warn)
     else:
-        dbver = 0.4
+        dbver = __DB_FORMAT__
     return dbver

+ 1 - 0
pwman/data/database.py

@@ -21,6 +21,7 @@
 
 from pwman.util.crypto import CryptoEngine
 
+__DB_FORMAT__ = 0.5
 
 class DatabaseException(Exception):
     pass  # prage: no cover

+ 11 - 8
pwman/data/drivers/sqlite.py

@@ -22,6 +22,7 @@
 
 """SQLite Database implementation."""
 from pwman.data.database import Database, DatabaseException
+from pwman.data.database import __DB_FORMAT__
 from pwman.data.nodes import NewNode
 from pwman.util.crypto import CryptoEngine
 import sqlite3 as sqlite
@@ -302,21 +303,23 @@ class SQLiteDatabaseNewForm(Database):
             # SQLite does have constraints implemented at the moment
             # so datatype will just be a string
             self._cur.execute("CREATE TABLE NODES (ID INTEGER PRIMARY KEY"
-                              + " AUTOINCREMENT,DATA BLOB NOT NULL)")
+                              " AUTOINCREMENT,DATA BLOB NOT NULL)")
             self._cur.execute("CREATE TABLE TAGS"
-                              + "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
-                              + "DATA BLOB NOT NULL UNIQUE)")
+                              "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
+                              "DATA BLOB NOT NULL UNIQUE)")
             self._cur.execute("CREATE TABLE LOOKUP"
-                              + "(NODE INTEGER NOT NULL, TAG INTEGER NOT NULL,"
-                              + " PRIMARY KEY(NODE, TAG))")
+                              "(NODE INTEGER NOT NULL, TAG INTEGER NOT NULL,"
+                              " PRIMARY KEY(NODE, TAG))")
 
             self._cur.execute("CREATE TABLE KEY"
-                              + "(THEKEY TEXT NOT NULL DEFAULT '')")
+                              "(THEKEY TEXT NOT NULL DEFAULT '')")
             self._cur.execute("INSERT INTO KEY VALUES('')")
             # create a table to hold DB version info
             self._cur.execute("CREATE TABLE DBVERSION"
-                              + "(DBVERSION TEXT NOT NULL DEFAULT '0.4')")
-            self._cur.execute("INSERT INTO DBVERSION VALUES('0.4')")
+                              "(DBVERSION TEXT NOT NULL DEFAULT '%s')" %
+                              __DB_FORMAT__)
+            self._cur.execute("INSERT INTO DBVERSION VALUES('%s')" %
+                              __DB_FORMAT__ )
             try:
                 self._con.commit()
             except DatabaseException as e:  # pragma: no cover

+ 2 - 2
pwman/data/factory.py

@@ -53,9 +53,9 @@ def create(dbtype, version=None, filename=None):
     """
     if dbtype == "SQLite":
         from pwman.data.drivers import sqlite
-        if version == 0.4 and filename:
+        if version >= 0.4 and filename:
             db = sqlite.SQLiteDatabaseNewForm(filename)
-        elif version == 0.4:
+        elif version >= 0.4:
             db = sqlite.SQLiteDatabaseNewForm()
         else:
             db = None

+ 1 - 1
pwman/tests/db_tests.py

@@ -380,7 +380,7 @@ class CLITests(unittest.TestCase):
 class FactoryTest(unittest.TestCase):
 
     def test_factory_check_db_ver(self):
-        self.assertEquals(factory.check_db_version('SQLite'), 0.4)
+        self.assertEquals(factory.check_db_version('SQLite'), 0.5)
 
 
 class ConfigTest(unittest.TestCase):

+ 67 - 6
pwman/util/crypto.py

@@ -45,7 +45,6 @@ plaintext = cyypto.decrypt(ciphertext)
 from Crypto.Cipher import Blowfish as cBlowfish
 from Crypto.Cipher import AES as cAES
 from Crypto.Cipher import ARC2 as cARC2
-from Crypto.Cipher import ARC4 as cARC4
 from Crypto.Cipher import CAST as cCAST
 from Crypto.Cipher import DES as cDES
 from Crypto.Cipher import DES3 as cDES3
@@ -115,13 +114,25 @@ class CryptoPasswordMismatchException(CryptoException):
 
 
 class CryptoEngine(object):
-    """Cryptographic Engine"""
+    """
+    Cryptographic Engine, overrides CryptoEngineOld.
+    The main change is that _getcipher_real is now hashing the key
+    before encrypting it.
+
+    This method can eventually remove the call to _retrievedata,
+    which used to strip the _TAG from the plain text string or return
+    the cPickle object as string.
+    Since we don't use cPickle to serialize object anymore, we can
+    safely aim towards removing this method. Thus, removing also
+    the _TAG in the beginning of each string as per recommendation of
+    Ralf Herzog.
+    """
     _timeoutcount = 0
     _instance = None
     _callback = None
 
     @classmethod
-    def get(cls):
+    def get(cls, dbver=None):
         """
         CryptoEngine.get() -> CryptoEngine
         Return an instance of CryptoEngine.
@@ -131,8 +142,11 @@ class CryptoEngine(object):
             algo = config.get_value("Encryption", "algorithm")
             if algo == "Dummy":
                 CryptoEngine._instance = DummyCryptoEngine()
-            else:
+            elif dbver < 0.5:
+                CryptoEngine._instance = CryptoEngineOld()
+            elif dbver == 0.5:
                 CryptoEngine._instance = CryptoEngine()
+
         return CryptoEngine._instance
 
     def __init__(self):
@@ -180,7 +194,6 @@ class CryptoEngine(object):
         key = str(key).decode('base64')
         self._cipher = self._getcipher_real(key, self._algo)
 
-
     def encrypt(self, obj):
         """
         encrypt(obj) -> ciphertext
@@ -375,7 +388,7 @@ password again")
         """
         prepare data before encrypting
         """
-        #plaintext = cPickle.dumps(obj)
+        # plaintext = cPickle.dumps(obj)
         plaintext = _TAG + obj
         numblocks = (len(plaintext)/blocksize) + 1
         newdatasize = blocksize*numblocks
@@ -419,3 +432,51 @@ class DummyCryptoEngine(CryptoEngine):
 
     def changepassword(self):
         return ''
+
+
+class CryptoEngineOld(CryptoEngine):
+
+    def _getcipher_real(self, key, algo):
+        """
+        do the real job of decrypting using functions
+        form PyCrypto
+        """
+        if (algo == "AES"):
+            key = self._padkey(key, [16, 24, 32])
+            cipher = cAES.new(key, cAES.MODE_ECB)
+        elif (algo == 'ARC2'):
+            cipher = cARC2.new(key, cARC2.MODE_ECB)
+        elif (algo == 'ARC4'):
+            raise CryptoUnsupportedException("ARC4 is currently unsupported")
+        elif (algo == 'Blowfish'):
+            cipher = cBlowfish.new(key, cBlowfish.MODE_ECB)
+        elif (algo == 'CAST'):
+            cipher = cCAST.new(key, cCAST.MODE_ECB)
+        elif (algo == 'DES'):
+            self._padkey(key, [8])
+            cipher = cDES.new(key, cDES.MODE_ECB)
+        elif (algo == 'DES3'):
+            key = self._padkey(key, [16, 24])
+            cipher = cDES3.new(key, cDES3.MODE_ECB)
+        elif (algo == 'XOR'):
+            raise CryptoUnsupportedException("XOR is currently unsupported")
+        else:
+            raise CryptoException("Invalid algorithm specified")
+        return cipher
+
+    def _padkey(self, key, acceptable_lengths):
+        """
+        pad key with extra string
+        """
+        maxlen = max(acceptable_lengths)
+        keylen = len(key)
+        if (keylen > maxlen):
+            return key[0:maxlen]
+        acceptable_lengths.sort()
+        acceptable_lengths.reverse()
+        newkeylen = None
+        for i in acceptable_lengths:
+            if (i < keylen):
+                break
+            newkeylen = i
+        return key.ljust(newkeylen)

+ 1 - 2
scripts/pwman3

@@ -20,7 +20,6 @@
 # Copyright (C) 2006 Ivan Kelly <ivan@ivankelly.net>
 #============================================================================
 from __future__ import print_function
-import os
 import sys
 import shutil
 from pwman import get_conf_options, get_db_version
@@ -70,8 +69,8 @@ def auto_convert():
 def main(args):
     PwmanCliNew, OSX = get_ui_platform(sys.platform)
     xselpath, dbtype = get_conf_options(args, OSX)
-    enc = CryptoEngine.get()
     dbver = get_db_version(config, dbtype, args)
+    enc = CryptoEngine.get(dbver)
 
     if args.dbconvert:
         dbconvertor = PwmanConvertDB(args, config)