|
@@ -1,31 +1,32 @@
|
|
|
#============================================================================
|
|
|
# This file is part of Pwman3.
|
|
|
-#
|
|
|
+#
|
|
|
# Pwman3 is free software; you can redistribute it and/or modify
|
|
|
# it under the terms of the GNU General Public License, version 2
|
|
|
-# as published by the Free Software Foundation;
|
|
|
-#
|
|
|
+# as published by the Free Software Foundation;
|
|
|
+#
|
|
|
# Pwman3 is distributed in the hope that it will be useful,
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
# GNU General Public License for more details.
|
|
|
-#
|
|
|
+#
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
# along with Pwman3; if not, write to the Free Software
|
|
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
#============================================================================
|
|
|
# Copyright (C) 2012 Oz Nahum <nahumoz@gmail.com>
|
|
|
#============================================================================
|
|
|
-#============================================================================
|
|
|
# Copyright (C) 2006 Ivan Kelly <ivan@ivankelly.net>
|
|
|
#============================================================================
|
|
|
|
|
|
-"""Encryption Module used by PwmanDatabase
|
|
|
+"""
|
|
|
+Encryption Module used by PwmanDatabase
|
|
|
|
|
|
Supports AES, ARC2, Blowfish, CAST, DES, DES3, IDEA, RC5.
|
|
|
|
|
|
Usage:
|
|
|
import pwman.util.crypto.CryptoEngine as CryptoEngine
|
|
|
+from pwman.util.crypto import CryptoEngine
|
|
|
|
|
|
class myCallback(CryptoEngine.Callback):
|
|
|
def execute(self):
|
|
@@ -41,14 +42,22 @@ ciphertext = crypto.encrypt("plaintext")
|
|
|
plaintext = cyypto.decrypt(ciphertext)
|
|
|
|
|
|
"""
|
|
|
-from Crypto.Cipher import *
|
|
|
+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 cARC2
|
|
|
+from Crypto.Cipher import CAST as cCAST
|
|
|
+from Crypto.Cipher import DES as cDES
|
|
|
+from Crypto.Cipher import DES3 as cDES3
|
|
|
+
|
|
|
from Crypto.Util.randpool import RandomPool
|
|
|
+
|
|
|
from pwman.util.callback import Callback
|
|
|
import pwman.util.config as config
|
|
|
import cPickle
|
|
|
import time
|
|
|
|
|
|
-_instance = None
|
|
|
+_INSTANCE = None
|
|
|
|
|
|
# Use this to tell if crypto is successful or not
|
|
|
_TAG = "PWMANCRYPTO"
|
|
@@ -84,14 +93,15 @@ class CryptoPasswordMismatchException(CryptoException):
|
|
|
"""Entered passwords do not match."""
|
|
|
def __str__(self):
|
|
|
return "CryptoPasswordMismatchException: " + self.message
|
|
|
-
|
|
|
+
|
|
|
|
|
|
class CryptoEngine:
|
|
|
"""Cryptographic Engine"""
|
|
|
_timeoutcount = 0
|
|
|
_instance = None
|
|
|
_callback = None
|
|
|
-
|
|
|
+
|
|
|
+ @classmethod
|
|
|
def get(cls):
|
|
|
"""
|
|
|
get() -> CryptoEngine
|
|
@@ -105,7 +115,7 @@ class CryptoEngine:
|
|
|
else:
|
|
|
CryptoEngine._instance = CryptoEngine()
|
|
|
return CryptoEngine._instance
|
|
|
- get = classmethod(get)
|
|
|
+ #get = classmethod(get)
|
|
|
|
|
|
def __init__(self):
|
|
|
"""Initialise the Cryptographic Engine
|
|
@@ -121,27 +131,27 @@ class CryptoEngine:
|
|
|
if len(algo) > 0:
|
|
|
self._algo = algo
|
|
|
else:
|
|
|
- raise CryptoException("Parameters missing [%s]" % (e) )
|
|
|
+ raise CryptoException("Parameters missing, no algorithm given")
|
|
|
|
|
|
callback = config.get_value("Encryption", "callback")
|
|
|
if isinstance(callback, Callback):
|
|
|
self._callback = callback
|
|
|
else:
|
|
|
self._callback = None
|
|
|
-
|
|
|
+
|
|
|
keycrypted = config.get_value("Encryption", "keycrypted")
|
|
|
if len(keycrypted) > 0:
|
|
|
self._keycrypted = keycrypted
|
|
|
else:
|
|
|
self._keycrypted = None
|
|
|
-
|
|
|
+
|
|
|
timeout = config.get_value("Encryption", "timeout")
|
|
|
if timeout.isdigit():
|
|
|
self._timeout = timeout
|
|
|
else:
|
|
|
self._timeout = -1
|
|
|
self._cipher = None
|
|
|
-
|
|
|
+
|
|
|
def encrypt(self, obj):
|
|
|
"""
|
|
|
encrypt(obj) -> ciphertext
|
|
@@ -150,31 +160,48 @@ class CryptoEngine:
|
|
|
cipher = self._getcipher()
|
|
|
plaintext = self._preparedata(obj, cipher.block_size)
|
|
|
ciphertext = cipher.encrypt(plaintext)
|
|
|
+
|
|
|
return str(ciphertext).encode('base64')
|
|
|
-
|
|
|
+
|
|
|
def decrypt(self, ciphertext):
|
|
|
"""
|
|
|
decrypt(ciphertext) -> obj
|
|
|
Decrypt ciphertext and returns the obj that was encrypted.
|
|
|
If key is bad, a CryptoBadKeyException is raised
|
|
|
Can also raise a CryptoException and CryptoUnsupportedException"""
|
|
|
+ print ciphertext
|
|
|
cipher = self._getcipher()
|
|
|
ciphertext = str(ciphertext).decode('base64')
|
|
|
plaintext = cipher.decrypt(ciphertext)
|
|
|
+ print plaintext
|
|
|
+ #import pdb
|
|
|
+ #pdb.set_trace()
|
|
|
return self._retrievedata(plaintext)
|
|
|
|
|
|
def set_cryptedkey(self, key):
|
|
|
+ """
|
|
|
+ hold _keycrypted
|
|
|
+ """
|
|
|
self._keycrypted = key
|
|
|
|
|
|
def get_cryptedkey(self):
|
|
|
+ """
|
|
|
+ return _keycrypted
|
|
|
+ """
|
|
|
return self._keycrypted
|
|
|
|
|
|
def set_callback(self, callback):
|
|
|
+ """
|
|
|
+ set the callback function
|
|
|
+ """
|
|
|
self._callback = callback
|
|
|
|
|
|
def get_callback(self):
|
|
|
+ """
|
|
|
+ return call back function
|
|
|
+ """
|
|
|
return self._callback
|
|
|
-
|
|
|
+
|
|
|
def changepassword(self):
|
|
|
"""
|
|
|
Creates a new key. The key itself is actually stored in
|
|
@@ -183,106 +210,127 @@ class CryptoEngine:
|
|
|
password for the database.
|
|
|
If oldKeyCrypted is none, then a new password is generated."""
|
|
|
if (self._callback == None):
|
|
|
- raise CryptoNoCallbackException("No call back class has been specified")
|
|
|
+ raise CryptoNoCallbackException("No call back class has been \
|
|
|
+specified")
|
|
|
if (self._keycrypted == None):
|
|
|
# Generate a new key, 32 bits in length, if that's
|
|
|
# too long for the Cipher, _getCipherReal will sort it out
|
|
|
random = RandomPool()
|
|
|
key = str(random.get_bytes(32)).encode('base64')
|
|
|
else:
|
|
|
- password = self._callback.getsecret("Please enter your current password")
|
|
|
+ password = self._callback.getsecret("Please enter your current \
|
|
|
+password")
|
|
|
cipher = self._getcipher_real(password, self._algo)
|
|
|
plainkey = cipher.decrypt(str(self._keycrypted).decode('base64'))
|
|
|
key = self._retrievedata(plainkey)
|
|
|
- newpassword1 = self._callback.getsecret("Please enter your new password");
|
|
|
- newpassword2 = self._callback.getsecret("Please enter your new password again");
|
|
|
+ newpassword1 = self._callback.getsecret("Please enter your new \
|
|
|
+password")
|
|
|
+ newpassword2 = self._callback.getsecret("Please enter your new \
|
|
|
+password again")
|
|
|
if (newpassword1 != newpassword2):
|
|
|
raise CryptoPasswordMismatchException("Passwords do not match")
|
|
|
newcipher = self._getcipher_real(newpassword1, self._algo)
|
|
|
- self._keycrypted = str(newcipher.encrypt(self._preparedata(key, newcipher.block_size))).encode('base64')
|
|
|
-
|
|
|
+ self._keycrypted = str(newcipher.encrypt(self._preparedata(key,
|
|
|
+ newcipher.block_size))).encode('base64')
|
|
|
+
|
|
|
# we also want to create the cipher if there isn't one already
|
|
|
# so this CryptoEngine can be used from now on
|
|
|
if (self._cipher == None):
|
|
|
- self._cipher = self._getcipher_real(str(key).decode('base64'), self._algo)
|
|
|
- CryptoEngine._timeoutcount = time.time()
|
|
|
-
|
|
|
+ self._cipher = self._getcipher_real(str(key).decode('base64'),
|
|
|
+ self._algo)
|
|
|
+ CryptoEngine._timeoutcount = time.time()
|
|
|
+
|
|
|
return self._keycrypted
|
|
|
|
|
|
def alive(self):
|
|
|
+ """
|
|
|
+ check if we have cipher
|
|
|
+ """
|
|
|
if (self._cipher != None):
|
|
|
return True
|
|
|
else:
|
|
|
return False
|
|
|
|
|
|
def forget(self):
|
|
|
+ """
|
|
|
+ discard cipher
|
|
|
+ """
|
|
|
self._cipher = None
|
|
|
-
|
|
|
+
|
|
|
def _getcipher(self):
|
|
|
+ """
|
|
|
+ get cypher from user, to decrypt DB
|
|
|
+ """
|
|
|
if (self._cipher != None
|
|
|
and (self._timeout == -1
|
|
|
- or (time.time() - CryptoEngine._timeoutcount) < self._timeout)):
|
|
|
+ or (time.time() - CryptoEngine._timeoutcount) < self._timeout)):
|
|
|
return self._cipher
|
|
|
if (self._callback == None):
|
|
|
raise CryptoNoCallbackException("No Callback exception")
|
|
|
if (self._keycrypted == None):
|
|
|
raise CryptoNoKeyException("Encryption key has not been generated")
|
|
|
|
|
|
- maxTries = 5
|
|
|
+ max_tries = 5
|
|
|
tries = 0
|
|
|
|
|
|
key = None
|
|
|
|
|
|
- while tries < maxTries:
|
|
|
+ while tries < max_tries:
|
|
|
try:
|
|
|
- password = self._callback.getsecret("Please enter your password")
|
|
|
+ password = self._callback.getsecret("Please enter your\
|
|
|
+password")
|
|
|
tmpcipher = self._getcipher_real(password, self._algo)
|
|
|
- plainkey = tmpcipher.decrypt(str(self._keycrypted).decode('base64'))
|
|
|
+ plainkey = tmpcipher.decrypt(str(self._keycrypted).decode(
|
|
|
+ 'base64'))
|
|
|
key = self._retrievedata(plainkey)
|
|
|
break
|
|
|
- except CryptoBadKeyException, e:
|
|
|
+ except CryptoBadKeyException:
|
|
|
print "Wrong password."
|
|
|
tries += 1
|
|
|
|
|
|
if not key:
|
|
|
- raise Exception("Wrong password entered %s times; giving up" % maxTries)
|
|
|
-
|
|
|
- self._cipher = self._getcipher_real(str(key).decode('base64'), self._algo)
|
|
|
+ raise Exception("Wrong password entered %s times; giving up" \
|
|
|
+ % max_tries)
|
|
|
+
|
|
|
+ self._cipher = self._getcipher_real(str(key).decode('base64'),
|
|
|
+ self._algo)
|
|
|
|
|
|
CryptoEngine._timeoutcount = time.time()
|
|
|
return self._cipher
|
|
|
-
|
|
|
+
|
|
|
|
|
|
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 = AES.new(key, AES.MODE_ECB)
|
|
|
+ cipher = cAES.new(key, cAES.MODE_ECB)
|
|
|
elif (algo == 'ARC2'):
|
|
|
- cipher = ARC2.new(key, ARC2.MODE_ECB)
|
|
|
+ cipher = cARC2.new(key, cARC2.MODE_ECB)
|
|
|
elif (algo == 'ARC4'):
|
|
|
raise CryptoUnsupportedException("ARC4 is currently unsupported")
|
|
|
elif (algo == 'Blowfish'):
|
|
|
- cipher = Blowfish.new(key, Blowfish.MODE_ECB)
|
|
|
+ cipher = cBlowfish.new(key, cBlowfish.MODE_ECB)
|
|
|
elif (algo == 'CAST'):
|
|
|
- cipher = CAST.new(key, CAST.MODE_ECB)
|
|
|
+ cipher = cCAST.new(key, cCAST.MODE_ECB)
|
|
|
elif (algo == 'DES'):
|
|
|
self._padkey(key, [8])
|
|
|
- cipher = DES.new(key, DES.MODE_ECB)
|
|
|
+ cipher = cDES.new(key, cDES.MODE_ECB)
|
|
|
elif (algo == 'DES3'):
|
|
|
key = self._padkey(key, [16, 24])
|
|
|
- cipher = DES3.new(key, DES3.MODE_ECB)
|
|
|
- elif (algo == 'IDEA'):
|
|
|
- key = self._padkey(key, [16])
|
|
|
- cipher = IDEA.new(key, IDEA.MODE_ECB)
|
|
|
- elif (algo == 'RC5'):
|
|
|
- cipher = RC5.new(key, RC5.MODE_ECB)
|
|
|
+ cipher = cDES3.new(key, cDES3.MODE_ECB)
|
|
|
elif (algo == 'XOR'):
|
|
|
raise CryptoUnsupportedException("XOR is currently unsupported")
|
|
|
else:
|
|
|
- raise CryptoException("Invalid algorithm specified")
|
|
|
+ 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):
|
|
@@ -297,6 +345,9 @@ class CryptoEngine:
|
|
|
return key.ljust(newkeylen)
|
|
|
|
|
|
def _preparedata(self, obj, blocksize):
|
|
|
+ """
|
|
|
+ prepare data before encrypting
|
|
|
+ """
|
|
|
plaintext = cPickle.dumps(obj)
|
|
|
plaintext = _TAG + plaintext
|
|
|
numblocks = (len(plaintext)/blocksize) + 1
|
|
@@ -304,6 +355,9 @@ class CryptoEngine:
|
|
|
return plaintext.ljust(newdatasize)
|
|
|
|
|
|
def _retrievedata(self, plaintext):
|
|
|
+ """
|
|
|
+ retrieve encrypted data
|
|
|
+ """
|
|
|
if (plaintext.startswith(_TAG)):
|
|
|
plaintext = plaintext[len(_TAG):]
|
|
|
else:
|
|
@@ -316,7 +370,7 @@ class DummyCryptoEngine(CryptoEngine):
|
|
|
Only for testing and debugging the DB drivers really."""
|
|
|
def __init__(self):
|
|
|
pass
|
|
|
-
|
|
|
+
|
|
|
def encrypt(self, obj):
|
|
|
"""Return the object pickled."""
|
|
|
return cPickle.dumps(obj)
|