Browse Source

Add pure Python crypto engine, and relevant warning

Oz N Tiram 8 years ago
parent
commit
7381bb201b

+ 2 - 1
pwman/util/config.py

@@ -38,7 +38,8 @@ default_config = {'Global': {'umask': '0100', 'colors': 'yes',
                       'dburi': 'sqlite://' + os.path.join(config_dir,
                                                           'pwman.db')},
                   'Readline': {'history': os.path.join(config_dir,
-                                                       'history')}
+                                                       'history')},
+                  'Crypto': {'warning': 'yes'},
                   }
 
 if 'win' in sys.platform:

+ 332 - 0
pwman/util/crypto/AES.py

@@ -0,0 +1,332 @@
+"""
+
+Copyright (c) 2014  Philippe Teuwen <phil@teuwen.org>
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of 
+this software and associated documentation files (the "Software"), to deal in 
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
+IN THE SOFTWARE.
+
+This code is taken from https://github.com/doegox/python-cryptoplus/
+
+"""
+from blockcipher import *
+from rijndael import rijndael
+
+def new(key,mode=MODE_ECB,IV=None,counter=None,segment_size=None):
+    """Create a new cipher object
+
+    Wrapper for pure python implementation rijndael.py
+
+        key = raw string containing the key, AES-128..256 will be selected according to the key length
+            -> when using XTS mode: the key should be a tuple containing the 2 keys needed
+        mode = python_AES.MODE_ECB/CBC/CFB/OFB/CTR/XTS/CMAC, default is ECB
+            -> for every mode, except ECB and CTR, it is important to construct a seperate cipher for encryption and decryption
+        IV = IV as a raw string, default is "all zero" IV
+            -> needed for CBC, CFB and OFB mode
+        counter = counter object (CryptoPlus.Util.util.Counter)
+            -> only needed for CTR mode
+            -> use a seperate counter object for the cipher and decipher: the counter is updated directly, not a copy
+                see CTR example further on in the docstring
+        segment_size = amount of bits to use from the keystream in each chain part
+            -> supported values: multiple of 8 between 8 and the blocksize
+               of the cipher (only per byte access possible), default is 8
+            -> only needed for CFB mode
+
+    Notes:
+        - Always construct a seperate cipher object for encryption and decryption. Once a cipher object has been used for encryption,
+          it can't be used for decryption because it keeps a state (if necessary) for the IV.
+
+    EXAMPLES:
+    **********
+    IMPORTING:
+    -----------
+    >>> from CryptoPlus.Cipher import python_AES
+
+    ECB EXAMPLE:
+    -------------
+    NIST Special Publication 800-38A http://cryptome.org/bcm/sp800-38a.htm#F
+
+    >>> cipher = python_AES.new('2b7e151628aed2a6abf7158809cf4f3c'.decode('hex'))
+    >>> crypted = cipher.encrypt('6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51'.decode('hex'))
+    >>> crypted.encode('hex')
+    '3ad77bb40d7a3660a89ecaf32466ef97f5d3d58503b9699de785895a96fdbaaf'
+    >>> decipher = python_AES.new('2b7e151628aed2a6abf7158809cf4f3c'.decode('hex'))
+    >>> decipher.decrypt(crypted).encode('hex')
+    '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51'
+
+    PADDING EXAMPLE:
+    -----------------
+    >>> cipher = python_AES.new('0123456789012345')
+    >>> crypt = cipher.encrypt('0123456789012')
+    >>> crypt += cipher.final()
+    >>> decipher = python_AES.new('0123456789012345')
+    >>> decipher.decrypt(crypt)
+    '0123456789012\\x03\\x03\\x03'
+
+    CBC EXAMPLE (plaintext = 3 blocksizes):
+    -----------------------------------------
+    NIST Special Publication 800-38A http://cryptome.org/bcm/sp800-38a.htm#F
+
+    >>> key = ('2b7e151628aed2a6abf7158809cf4f3c').decode('hex')
+    >>> IV = ('000102030405060708090a0b0c0d0e0f').decode('hex')
+    >>> plaintext1 = ('6bc1bee22e409f96e93d7e117393172a').decode('hex')
+    >>> plaintext2 = ('ae2d8a571e03ac9c9eb76fac45af8e51').decode('hex')
+    >>> plaintext3 = ('30c81c46a35ce411e5fbc1191a0a52ef').decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_CBC,IV)
+    >>> ciphertext = cipher.encrypt(plaintext1 + plaintext2 + plaintext3)
+    >>> (ciphertext).encode('hex')
+    '7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e22229516'
+    >>> decipher = python_AES.new(key,python_AES.MODE_CBC,IV)
+    >>> plaintext = decipher.decrypt(ciphertext)
+    >>> (plaintext).encode('hex')
+    '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52ef'
+
+    OR: supply plaintext as seperate pieces:
+    ------------------------------------------
+    >>> cipher = python_AES.new(key,python_AES.MODE_CBC,IV)
+    >>> ( cipher.encrypt(plaintext1 + plaintext2[:-2]) ).encode('hex')
+    '7649abac8119b246cee98e9b12e9197d'
+    >>> ( cipher.encrypt(plaintext2[-2:] + plaintext3) ).encode('hex')
+    '5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e22229516'
+    >>> decipher = python_AES.new(key,python_AES.MODE_CBC,IV)
+    >>> (decipher.decrypt(ciphertext[:22])).encode('hex')
+    '6bc1bee22e409f96e93d7e117393172a'
+    >>> (decipher.decrypt(ciphertext[22:])).encode('hex')
+    'ae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52ef'
+
+    CFB EXAMPLE: (CFB8-AES192)
+    ------------
+    NIST Special Publication 800-38A http://cryptome.org/bcm/sp800-38a.htm#F
+    
+    >>> key = '2b7e151628aed2a6abf7158809cf4f3c'.decode('hex')
+    >>> IV = '000102030405060708090a0b0c0d0e0f'.decode('hex')
+    >>> plain = '6bc1bee22e409f96e93d7e117393172a'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_CFB,IV=IV,segment_size=8)
+    >>> ciphertext = cipher.encrypt(plain)
+    >>> ciphertext.encode('hex')
+    '3b79424c9c0dd436bace9e0ed4586a4f'
+    >>> decipher = python_AES.new(key,python_AES.MODE_CFB,IV)
+    >>> decipher.decrypt(ciphertext).encode('hex')
+    '6bc1bee22e409f96e93d7e117393172a'
+
+    CFB EXAMPLE: (CFB128-AES192)
+    ------------
+    NIST Special Publication 800-38A http://cryptome.org/bcm/sp800-38a.htm#F
+
+    >>> key = '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'.decode('hex')
+    >>> IV = '000102030405060708090a0b0c0d0e0f'.decode('hex')
+    >>> plain = '6bc1bee22e409f96e93d7e117393172a'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_CFB,IV=IV,segment_size=128)
+    >>> output1 = cipher.encrypt(plain)
+    >>> output1.encode('hex')
+    'cdc80d6fddf18cab34c25909c99a4174'
+    >>> plain = 'ae2d8a571e03ac9c9eb76fac45af8e51'.decode('hex')
+    >>> output2 = cipher.encrypt(plain)
+    >>> output2.encode('hex')
+    '67ce7f7f81173621961a2b70171d3d7a'
+    >>> decipher = python_AES.new(key,python_AES.MODE_CFB,IV=IV,segment_size=128)
+    >>> decipher.decrypt(output1+output2).encode('hex')
+    '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51'
+
+    CFB EXAMPLE: same as previous but now as a streamcipher
+    ------------
+    >>> key = '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'.decode('hex')
+    >>> IV = '000102030405060708090a0b0c0d0e0f'.decode('hex')
+    >>> plain = '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_CFB,IV=IV,segment_size=128)
+    >>> output = ''
+    >>> for i in plain: output += cipher.encrypt(i)
+    >>> output.encode('hex')
+    'cdc80d6fddf18cab34c25909c99a417467ce7f7f81173621961a2b70171d3d7a'
+
+    OFB EXAMPLE: (OFB128-AES192)
+    ------------
+    NIST Special Publication 800-38A http://cryptome.org/bcm/sp800-38a.htm#F
+
+    >>> key = '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'.decode('hex')
+    >>> IV = '000102030405060708090a0b0c0d0e0f'.decode('hex')
+    >>> plain = '6bc1bee22e409f96e93d7e117393172a'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_OFB,IV)
+    >>> output1 = cipher.encrypt(plain)
+    >>> output1.encode('hex')
+    'cdc80d6fddf18cab34c25909c99a4174'
+    >>> plain = 'ae2d8a571e03ac9c9eb76fac45af8e51'.decode('hex')
+    >>> output2 = cipher.encrypt(plain)
+    >>> output2.encode('hex')
+    'fcc28b8d4c63837c09e81700c1100401'
+    >>> decipher = python_AES.new(key,python_AES.MODE_OFB,IV)
+    >>> decipher.decrypt(output1 + output2).encode('hex')
+    '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51'
+
+    OFB EXAMPLE: same as previous but now as a streamcipher
+    ------------
+    >>> key = '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'.decode('hex')
+    >>> IV = '000102030405060708090a0b0c0d0e0f'.decode('hex')
+    >>> plain = '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_OFB,IV)
+    >>> output = ''
+    >>> for i in plain: output += cipher.encrypt(i)
+    >>> output.encode('hex')
+    'cdc80d6fddf18cab34c25909c99a4174fcc28b8d4c63837c09e81700c1100401'
+
+
+    CTR EXAMPLE:
+    ------------
+    NIST Special Publication 800-38A http://cryptome.org/bcm/sp800-38a.htm#F
+
+    >>> from CryptoPlus.Util.util import Counter
+    >>> key = '2b7e151628aed2a6abf7158809cf4f3c'.decode('hex')
+    >>> counter = Counter('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'.decode('hex'))
+    >>> cipher = python_AES.new(key,python_AES.MODE_CTR,counter=counter)
+    >>> plaintext1 = '6bc1bee22e409f96e93d7e117393172a'.decode('hex')
+    >>> plaintext2 = 'ae2d8a571e03ac9c9eb76fac45af8e51'.decode('hex')
+    >>> plaintext3 = '30c81c46a35ce411e5fbc1191a0a52ef'.decode('hex')
+    >>> ciphertext = cipher.encrypt(plaintext1 + plaintext2 + plaintext3)
+    >>> ciphertext.encode('hex')
+    '874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab'
+    >>> counter2 = Counter('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'.decode('hex'))
+    >>> decipher = python_AES.new(key,python_AES.MODE_CTR,counter=counter2)
+    >>> decipher.decrypt(ciphertext).encode('hex')
+    '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52ef'
+
+    XTS EXAMPLE:
+    ------------
+    XTS-AES-128 applied for a data unit of 512 bytes
+    IEEE P1619/D16: http://grouper.ieee.org/groups/1619/email/pdf00086.pdf
+
+    >>> key = ('27182818284590452353602874713526'.decode('hex'),'31415926535897932384626433832795'.decode('hex'))
+    >>> plaintext = '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
+    >>> ciphertext = cipher.encrypt(plaintext)
+    >>> ciphertext.encode('hex')
+    '27a7479befa1d476489f308cd4cfa6e2a96e4bbe3208ff25287dd3819616e89cc78cf7f5e543445f8333d8fa7f56000005279fa5d8b5e4ad40e736ddb4d35412328063fd2aab53e5ea1e0a9f332500a5df9487d07a5c92cc512c8866c7e860ce93fdf166a24912b422976146ae20ce846bb7dc9ba94a767aaef20c0d61ad02655ea92dc4c4e41a8952c651d33174be51a10c421110e6d81588ede82103a252d8a750e8768defffed9122810aaeb99f9172af82b604dc4b8e51bcb08235a6f4341332e4ca60482a4ba1a03b3e65008fc5da76b70bf1690db4eae29c5f1badd03c5ccf2a55d705ddcd86d449511ceb7ec30bf12b1fa35b913f9f747a8afd1b130e94bff94effd01a91735ca1726acd0b197c4e5b03393697e126826fb6bbde8ecc1e08298516e2c9ed03ff3c1b7860f6de76d4cecd94c8119855ef5297ca67e9f3e7ff72b1e99785ca0a7e7720c5b36dc6d72cac9574c8cbbc2f801e23e56fd344b07f22154beba0f08ce8891e643ed995c94d9a69c9f1b5f499027a78572aeebd74d20cc39881c213ee770b1010e4bea718846977ae119f7a023ab58cca0ad752afe656bb3c17256a9f6e9bf19fdd5a38fc82bbe872c5539edb609ef4f79c203ebb140f2e583cb2ad15b4aa5b655016a8449277dbd477ef2c8d6c017db738b18deb4a427d1923ce3ff262735779a418f20a282df920147beabe421ee5319d0568'
+    >>> decipher = python_AES.new(key,python_AES.MODE_XTS)
+    >>> decipher.decrypt(ciphertext).encode('hex')
+    '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
+
+    using data sequence number n
+
+    >>> key = ('fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0'.decode('hex'),'22222222222222222222222222222222'.decode('hex'))
+    >>> plain ='4444444444444444444444444444444444444444444444444444444444444444'.decode('hex')
+    >>> n = '3333333333'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
+    >>> ciphertext = cipher.encrypt(plain,n)
+    >>> ciphertext.encode('hex')
+    'af85336b597afc1a900b2eb21ec949d292df4c047e0b21532186a5971a227a89'
+    >>> decipher = python_AES.new(key,python_AES.MODE_XTS)
+    >>> decipher.decrypt(ciphertext,n).encode('hex')
+    '4444444444444444444444444444444444444444444444444444444444444444'
+
+    >>> key = ('27182818284590452353602874713526'.decode('hex'),'31415926535897932384626433832795'.decode('hex'))
+    >>> plain ='72efc1ebfe1ee25975a6eb3aa8589dda2b261f1c85bdab442a9e5b2dd1d7c3957a16fc08e526d4b1223f1b1232a11af274c3d70dac57f83e0983c498f1a6f1aecb021c3e70085a1e527f1ce41ee5911a82020161529cd82773762daf5459de94a0a82adae7e1703c808543c29ed6fb32d9e004327c1355180c995a07741493a09c21ba01a387882da4f62534b87bb15d60d197201c0fd3bf30c1500a3ecfecdd66d8721f90bcc4c17ee925c61b0a03727a9c0d5f5ca462fbfa0af1c2513a9d9d4b5345bd27a5f6e653f751693e6b6a2b8ead57d511e00e58c45b7b8d005af79288f5c7c22fd4f1bf7a898b03a5634c6a1ae3f9fae5de4f296a2896b23e7ed43ed14fa5a2803f4d28f0d3ffcf24757677aebdb47bb388378708948a8d4126ed1839e0da29a537a8c198b3c66ab00712dd261674bf45a73d67f76914f830ca014b65596f27e4cf62de66125a5566df9975155628b400fbfb3a29040ed50faffdbb18aece7c5c44693260aab386c0a37b11b114f1c415aebb653be468179428d43a4d8bc3ec38813eca30a13cf1bb18d524f1992d44d8b1a42ea30b22e6c95b199d8d182f8840b09d059585c31ad691fa0619ff038aca2c39a943421157361717c49d322028a74648113bd8c9d7ec77cf3c89c1ec8718ceff8516d96b34c3c614f10699c9abc4ed0411506223bea16af35c883accdbe1104eef0cfdb54e12fb230a'.decode('hex')
+    >>> n = 'ff'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
+    >>> cipher.encrypt(plain,n).encode('hex')
+    '3260ae8dad1f4a32c5cafe3ab0eb95549d461a67ceb9e5aa2d3afb62dece0553193ba50c75be251e08d1d08f1088576c7efdfaaf3f459559571e12511753b07af073f35da06af0ce0bbf6b8f5ccc5cea500ec1b211bd51f63b606bf6528796ca12173ba39b8935ee44ccce646f90a45bf9ccc567f0ace13dc2d53ebeedc81f58b2e41179dddf0d5a5c42f5d8506c1a5d2f8f59f3ea873cbcd0eec19acbf325423bd3dcb8c2b1bf1d1eaed0eba7f0698e4314fbeb2f1566d1b9253008cbccf45a2b0d9c5c9c21474f4076e02be26050b99dee4fd68a4cf890e496e4fcae7b70f94ea5a9062da0daeba1993d2ccd1dd3c244b8428801495a58b216547e7e847c46d1d756377b6242d2e5fb83bf752b54e0df71e889f3a2bb0f4c10805bf3c590376e3c24e22ff57f7fa965577375325cea5d920db94b9c336b455f6e894c01866fe9fbb8c8d3f70a2957285f6dfb5dcd8cbf54782f8fe7766d4723819913ac773421e3a31095866bad22c86a6036b2518b2059b4229d18c8c2ccbdf906c6cc6e82464ee57bddb0bebcb1dc645325bfb3e665ef7251082c88ebb1cf203bd779fdd38675713c8daadd17e1cabee432b09787b6ddf3304e38b731b45df5df51b78fcfb3d32466028d0ba36555e7e11ab0ee0666061d1645d962444bc47a38188930a84b4d561395c73c087021927ca638b7afc8a8679ccb84c26555440ec7f10445cd'
+
+    >>> key = ('2718281828459045235360287471352662497757247093699959574966967627'.decode('hex'),'3141592653589793238462643383279502884197169399375105820974944592'.decode('hex'))
+    >>> plain ='000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'.decode('hex')
+    >>> n = 'ffffffffff'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
+    >>> ciphertext = cipher.encrypt(plain,n)
+    >>> ciphertext.encode('hex')
+    '64497e5a831e4a932c09be3e5393376daa599548b816031d224bbf50a818ed2350eae7e96087c8a0db51ad290bd00c1ac1620857635bf246c176ab463be30b808da548081ac847b158e1264be25bb0910bbc92647108089415d45fab1b3d2604e8a8eff1ae4020cfa39936b66827b23f371b92200be90251e6d73c5f86de5fd4a950781933d79a28272b782a2ec313efdfcc0628f43d744c2dc2ff3dcb66999b50c7ca895b0c64791eeaa5f29499fb1c026f84ce5b5c72ba1083cddb5ce45434631665c333b60b11593fb253c5179a2c8db813782a004856a1653011e93fb6d876c18366dd8683f53412c0c180f9c848592d593f8609ca736317d356e13e2bff3a9f59cd9aeb19cd482593d8c46128bb32423b37a9adfb482b99453fbe25a41bf6feb4aa0bef5ed24bf73c762978025482c13115e4015aac992e5613a3b5c2f685b84795cb6e9b2656d8c88157e52c42f978d8634c43d06fea928f2822e465aa6576e9bf419384506cc3ce3c54ac1a6f67dc66f3b30191e698380bc999b05abce19dc0c6dcc2dd001ec535ba18deb2df1a101023108318c75dc98611a09dc48a0acdec676fabdf222f07e026f059b672b56e5cbc8e1d21bbd867dd927212054681d70ea737134cdfce93b6f82ae22423274e58a0821cc5502e2d0ab4585e94de6975be5e0b4efce51cd3e70c25a1fbbbd609d273ad5b0d59631c531f6a0a57b9'
+    >>> decipher = python_AES.new(key,python_AES.MODE_XTS)
+    >>> decipher.decrypt(ciphertext,n).encode('hex')
+    '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
+
+    using plaintext not a multiple of 16
+
+    >>> key = ('fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0'.decode('hex'),'bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0'.decode('hex'))
+    >>> plaintext = '000102030405060708090a0b0c0d0e0f10111213'.decode('hex')
+    >>> n = '9a78563412'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
+    >>> ciphertext = cipher.encrypt(plaintext,n)
+    >>> ciphertext.encode('hex')
+    '9d84c813f719aa2c7be3f66171c7c5c2edbf9dac'
+    >>> decipher = python_AES.new(key,python_AES.MODE_XTS)
+    >>> decipher.decrypt(ciphertext,n).encode('hex')
+    '000102030405060708090a0b0c0d0e0f10111213'
+
+    >>> key = ('fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0'.decode('hex'),'bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0'.decode('hex'))
+    >>> plaintext = '000102030405060708090a0b0c0d0e0f10'.decode('hex')
+    >>> n = '9a78563412'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
+    >>> ciphertext = cipher.encrypt(plaintext,n)
+    >>> ciphertext.encode('hex')
+    '6c1625db4671522d3d7599601de7ca09ed'
+    >>> decipher = python_AES.new(key,python_AES.MODE_XTS)
+    >>> decipher.decrypt(ciphertext,n).encode('hex')
+    '000102030405060708090a0b0c0d0e0f10'
+
+    >>> key = ('e0e1e2e3e4e5e6e7e8e9eaebecedeeef'.decode('hex'),'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf'.decode('hex'))
+    >>> plaintext = '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'.decode('hex')
+    >>> n = '21436587a9'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
+    >>> ciphertext = cipher.encrypt(plaintext,n)
+    >>> ciphertext.encode('hex')
+    '38b45812ef43a05bd957e545907e223b954ab4aaf088303ad910eadf14b42be68b2461149d8c8ba85f992be970bc621f1b06573f63e867bf5875acafa04e42ccbd7bd3c2a0fb1fff791ec5ec36c66ae4ac1e806d81fbf709dbe29e471fad38549c8e66f5345d7c1eb94f405d1ec785cc6f6a68f6254dd8339f9d84057e01a17741990482999516b5611a38f41bb6478e6f173f320805dd71b1932fc333cb9ee39936beea9ad96fa10fb4112b901734ddad40bc1878995f8e11aee7d141a2f5d48b7a4e1e7f0b2c04830e69a4fd1378411c2f287edf48c6c4e5c247a19680f7fe41cefbd49b582106e3616cbbe4dfb2344b2ae9519391f3e0fb4922254b1d6d2d19c6d4d537b3a26f3bcc51588b32f3eca0829b6a5ac72578fb814fb43cf80d64a233e3f997a3f02683342f2b33d25b492536b93becb2f5e1a8b82f5b883342729e8ae09d16938841a21a97fb543eea3bbff59f13c1a18449e398701c1ad51648346cbc04c27bb2da3b93a1372ccae548fb53bee476f9e9c91773b1bb19828394d55d3e1a20ed69113a860b6829ffa847224604435070221b257e8dff783615d2cae4803a93aa4334ab482a0afac9c0aeda70b45a481df5dec5df8cc0f423c77a5fd46cd312021d4b438862419a791be03bb4d97c0e59578542531ba466a83baf92cefc151b5cc1611a167893819b63fb8a6b18e86de60290fa72b797b0ce59f3'
+    >>> decipher = python_AES.new(key,python_AES.MODE_XTS)
+    >>> decipher.decrypt(ciphertext,n).encode('hex')
+    '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
+
+    CMAC EXAMPLE:
+    -------------
+    NIST publication 800-38B: http://csrc.nist.gov/publications/nistpubs/800-38B/Updated_CMAC_Examples.pdf
+
+    >>> key = '2b7e151628aed2a6abf7158809cf4f3c'.decode('hex')
+    >>> plaintext = '6bc1bee22e409f96e93d7e117393172a'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_CMAC)
+    >>> cipher.encrypt(plaintext).encode('hex')[:16]
+    '070a16b46b4d4144'
+
+    CMAC EXAMPLE2:
+    --------------
+    >>> key = '2b7e151628aed2a6abf7158809cf4f3c'.decode('hex')
+    >>> plaintext = '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411'.decode('hex')
+    >>> cipher = python_AES.new(key,python_AES.MODE_CMAC)
+    >>> cipher.encrypt(plaintext).encode('hex')[:16]
+    'dfa66747de9ae630'
+    """
+    return AES(key,mode,IV,counter,segment_size)
+
+
+block_size = 16
+
+class AES(BlockCipher):
+    
+    key_error_message = ("Key should be 128, 192 or 256 bits")
+    block_size = 16
+    
+    def __init__(self,key,mode,IV,counter,segment_size):
+        cipher_module = rijndael
+        self.blocksize = 16
+        args = {'block_size':16}
+        
+        BlockCipher.__init__(self,key,mode,IV,counter,cipher_module,segment_size,args)
+
+    def keylen_valid(self,key):
+        return len(key) in (16,24,32)
+
+def _test():
+    import doctest
+    doctest.testmod()
+
+if __name__ == "__main__":
+    _test()
+

+ 0 - 0
pwman/util/crypto/__init__.py


+ 593 - 0
pwman/util/crypto/blockcipher.py

@@ -0,0 +1,593 @@
+# =============================================================================
+# Copyright (c) 2008 Christophe Oosterlynck <christophe.oosterlynck_AT_gmail.com>
+#                    & NXP ( Philippe Teuwen <philippe.teuwen_AT_nxp.com> )
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+# =============================================================================
+import util
+import padding
+
+MODE_ECB = 1
+MODE_CBC = 2
+MODE_CFB = 3
+MODE_OFB = 5
+MODE_CTR = 6
+MODE_XTS = 7
+MODE_CMAC = 8
+
+class BlockCipher():
+    """ Base class for all blockciphers
+    """
+
+    key_error_message = "Wrong key size" #should be overwritten in child classes
+
+    def __init__(self,key,mode,IV,counter,cipher_module,segment_size,args={}):
+        # Cipher classes inheriting from this one take care of:
+        #   self.blocksize
+        #   self.cipher
+        self.key = key
+        self.mode = mode
+        self.cache = ''
+        self.ed = None
+
+        if 'keylen_valid' in dir(self): #wrappers for pycrypto functions don't have this function
+         if not self.keylen_valid(key) and type(key) is not tuple:
+                raise ValueError(self.key_error_message)
+
+        if IV == None:
+            self.IV = '\x00'*self.blocksize
+        else:
+            self.IV = IV
+
+        if mode <> MODE_XTS:
+            self.cipher = cipher_module(self.key,**args)
+        if mode == MODE_ECB:
+            self.chain = ECB(self.cipher, self.blocksize)
+        elif mode == MODE_CBC:
+            if len(self.IV) <> self.blocksize:
+                raise Exception,"the IV length should be %i bytes"%self.blocksize
+            self.chain = CBC(self.cipher, self.blocksize,self.IV)
+        elif mode == MODE_CFB:
+            if len(self.IV) <> self.blocksize:
+                raise Exception,"the IV length should be %i bytes"%self.blocksize
+            if segment_size == None:
+                raise ValueError,"segment size must be defined explicitely for CFB mode"
+            if segment_size > self.blocksize*8 or segment_size%8 <> 0:
+                # current CFB implementation doesn't support bit level acces => segment_size should be multiple of bytes
+                raise ValueError,"segment size should be a multiple of 8 bits between 8 and %i"%(self.blocksize*8)
+            self.chain = CFB(self.cipher, self.blocksize,self.IV,segment_size)
+        elif mode == MODE_OFB:
+            if len(self.IV) <> self.blocksize:
+                raise ValueError("the IV length should be %i bytes"%self.blocksize)
+            self.chain = OFB(self.cipher, self.blocksize,self.IV)
+        elif mode == MODE_CTR:
+            if (counter == None) or  not callable(counter):
+                raise Exception,"Supply a valid counter object for the CTR mode"
+            self.chain = CTR(self.cipher,self.blocksize,counter)
+        elif mode == MODE_XTS:
+            if self.blocksize <> 16:
+                raise Exception,'XTS only works with blockcipher that have a 128-bit blocksize'
+            if not(type(key) == tuple and len(key) == 2):
+                raise Exception,'Supply two keys as a tuple when using XTS'
+            if 'keylen_valid' in dir(self): #wrappers for pycrypto functions don't have this function
+             if not self.keylen_valid(key[0]) or  not self.keylen_valid(key[1]):
+                raise ValueError(self.key_error_message)
+            self.cipher = cipher_module(self.key[0],**args)
+            self.cipher2 = cipher_module(self.key[1],**args)
+            self.chain = XTS(self.cipher, self.cipher2)
+        elif mode == MODE_CMAC:
+            if self.blocksize not in (8,16):
+                raise Exception,'CMAC only works with blockcipher that have a 64 or 128-bit blocksize'
+            self.chain = CMAC(self.cipher,self.blocksize,self.IV)
+        else:
+                raise Exception,"Unknown chaining mode!"
+
+    def encrypt(self,plaintext,n=''):
+        """Encrypt some plaintext
+
+            plaintext   = a string of binary data
+            n           = the 'tweak' value when the chaining mode is XTS
+
+        The encrypt function will encrypt the supplied plaintext.
+        The behavior varies slightly depending on the chaining mode.
+
+        ECB, CBC:
+        ---------
+        When the supplied plaintext is not a multiple of the blocksize
+          of the cipher, then the remaining plaintext will be cached.
+        The next time the encrypt function is called with some plaintext,
+          the new plaintext will be concatenated to the cache and then
+          cache+plaintext will be encrypted.
+
+        CFB, OFB, CTR:
+        --------------
+        When the chaining mode allows the cipher to act as a stream cipher,
+          the encrypt function will always encrypt all of the supplied
+          plaintext immediately. No cache will be kept.
+
+        XTS:
+        ----
+        Because the handling of the last two blocks is linked,
+          it needs the whole block of plaintext to be supplied at once.
+        Every encrypt function called on a XTS cipher will output
+          an encrypted block based on the current supplied plaintext block.
+
+        CMAC:
+        -----
+        Everytime the function is called, the hash from the input data is calculated.
+        No finalizing needed.
+        The hashlength is equal to block size of the used block cipher.
+        """
+        #self.ed = 'e' if chain is encrypting, 'd' if decrypting,
+        # None if nothing happened with the chain yet
+        #assert self.ed in ('e',None) 
+        # makes sure you don't encrypt with a cipher that has started decrypting
+        self.ed = 'e'
+        if self.mode == MODE_XTS:
+            # data sequence number (or 'tweak') has to be provided when in XTS mode
+            return self.chain.update(plaintext,'e',n)
+        else:
+            return self.chain.update(plaintext,'e')
+
+    def decrypt(self,ciphertext,n=''):
+        """Decrypt some ciphertext
+
+            ciphertext  = a string of binary data
+            n           = the 'tweak' value when the chaining mode is XTS
+
+        The decrypt function will decrypt the supplied ciphertext.
+        The behavior varies slightly depending on the chaining mode.
+
+        ECB, CBC:
+        ---------
+        When the supplied ciphertext is not a multiple of the blocksize
+          of the cipher, then the remaining ciphertext will be cached.
+        The next time the decrypt function is called with some ciphertext,
+          the new ciphertext will be concatenated to the cache and then
+          cache+ciphertext will be decrypted.
+
+        CFB, OFB, CTR:
+        --------------
+        When the chaining mode allows the cipher to act as a stream cipher,
+          the decrypt function will always decrypt all of the supplied
+          ciphertext immediately. No cache will be kept.
+
+        XTS:
+        ----
+        Because the handling of the last two blocks is linked,
+          it needs the whole block of ciphertext to be supplied at once.
+        Every decrypt function called on a XTS cipher will output
+          a decrypted block based on the current supplied ciphertext block.
+
+        CMAC:
+        -----
+        Mode not supported for decryption as this does not make sense.
+        """
+        #self.ed = 'e' if chain is encrypting, 'd' if decrypting,
+        # None if nothing happened with the chain yet
+        #assert self.ed in ('d',None)
+        # makes sure you don't decrypt with a cipher that has started encrypting
+        self.ed = 'd'
+        if self.mode == MODE_XTS:
+            # data sequence number (or 'tweak') has to be provided when in XTS mode
+            return self.chain.update(ciphertext,'d',n)
+        else:
+            return self.chain.update(ciphertext,'d')
+
+    def final(self,padfct=padding.PKCS7):
+        # TODO: after calling final, reset the IV? so the cipher is as good as new?
+        """Finalizes the encryption by padding the cache
+
+            padfct = padding function
+                     import from CryptoPlus.Util.padding
+
+        For ECB, CBC: the remaining bytes in the cache will be padded and
+                      encrypted.
+        For OFB,CFB, CTR: an encrypted padding will be returned, making the
+                          total outputed bytes since construction of the cipher
+                          a multiple of the blocksize of that cipher.
+
+        If the cipher has been used for decryption, the final function won't do
+          anything. You have to manually unpad if necessary.
+
+        After finalization, the chain can still be used but the IV, counter etc
+          aren't reset but just continue as they were after the last step (finalization step).
+        """
+        assert self.mode not in (MODE_XTS, MODE_CMAC) # finalizing (=padding) doesn't make sense when in XTS or CMAC mode
+        if self.ed == 'e':
+            # when the chain is in encryption mode, finalizing will pad the cache and encrypt this last block
+            if self.mode in (MODE_OFB,MODE_CFB,MODE_CTR):
+                dummy = '0'*(self.chain.totalbytes%self.blocksize) # a dummy string that will be used to get a valid padding
+            else: #ECB, CBC
+                dummy = self.chain.cache
+            pad = padfct(dummy,padding.PAD,self.blocksize)[len(dummy):] # construct the padding necessary
+            return self.chain.update(pad,'e') # supply the padding to the update function => chain cache will be "cache+padding"
+        else:
+            # final function doesn't make sense when decrypting => padding should be removed manually
+            pass
+
+class ECB:
+    """ECB chaining mode
+    """
+    def __init__(self, codebook, blocksize):
+        self.cache = ''
+        self.codebook = codebook
+        self.blocksize = blocksize
+
+    def update(self, data, ed):
+        """Processes the given ciphertext/plaintext
+
+        Inputs:
+            data: raw string of any length
+            ed:   'e' for encryption, 'd' for decryption
+        Output:
+            processed raw string block(s), if any
+
+        When the supplied data is not a multiple of the blocksize
+          of the cipher, then the remaining input data will be cached.
+        The next time the update function is called with some data,
+          the new data will be concatenated to the cache and then
+          cache+data will be processed and full blocks will be outputted.
+        """
+        output_blocks = []
+        self.cache += data
+        if len(self.cache) < self.blocksize:
+            return ''
+        for i in xrange(0, len(self.cache)-self.blocksize+1, self.blocksize):
+            #the only difference between encryption/decryption in the chain is the cipher block
+            if ed == 'e':
+                output_blocks.append(self.codebook.encrypt( self.cache[i:i + self.blocksize] ))
+            else:
+                output_blocks.append(self.codebook.decrypt( self.cache[i:i + self.blocksize] ))
+        self.cache = self.cache[i+self.blocksize:]
+        return ''.join(output_blocks)
+
+class CBC:
+    """CBC chaining mode
+    """
+    def __init__(self, codebook, blocksize, IV):
+        self.IV = IV
+        self.cache = ''
+        self.codebook = codebook
+        self.blocksize = blocksize
+
+    def update(self, data, ed):
+        """Processes the given ciphertext/plaintext
+
+        Inputs:
+            data: raw string of any length
+            ed:   'e' for encryption, 'd' for decryption
+        Output:
+            processed raw string block(s), if any
+
+        When the supplied data is not a multiple of the blocksize
+          of the cipher, then the remaining input data will be cached.
+        The next time the update function is called with some data,
+          the new data will be concatenated to the cache and then
+          cache+data will be processed and full blocks will be outputted.
+        """
+        if ed == 'e':
+            encrypted_blocks = ''
+            self.cache += data
+            if len(self.cache) < self.blocksize:
+                return ''
+            for i in xrange(0, len(self.cache)-self.blocksize+1, self.blocksize):
+                self.IV = self.codebook.encrypt(util.xorstring(self.cache[i:i+self.blocksize],self.IV))
+                encrypted_blocks += self.IV
+            self.cache = self.cache[i+self.blocksize:]
+            return encrypted_blocks
+        else:
+            decrypted_blocks = ''
+            self.cache += data
+            if len(self.cache) < self.blocksize:
+                return ''
+            for i in xrange(0, len(self.cache)-self.blocksize+1, self.blocksize):
+                plaintext = util.xorstring(self.IV,self.codebook.decrypt(self.cache[i:i + self.blocksize]))
+                self.IV = self.cache[i:i + self.blocksize]
+                decrypted_blocks+=plaintext
+            self.cache = self.cache[i+self.blocksize:]
+            return decrypted_blocks
+
+class CFB:
+    # TODO: bit access instead of only byte level access
+    """CFB Chaining Mode
+
+    Can be accessed as a stream cipher.
+    """
+
+    def __init__(self, codebook, blocksize, IV,segment_size):
+        self.codebook = codebook
+        self.IV = IV
+        self.blocksize = blocksize
+        self.segment_size = segment_size/8
+        self.keystream = []
+        self.totalbytes = 0
+        
+    def update(self, data, ed):
+        """Processes the given ciphertext/plaintext
+
+        Inputs:
+            data: raw string of any multiple of bytes
+            ed:   'e' for encryption, 'd' for decryption
+        Output:
+            processed raw string
+
+        The encrypt/decrypt functions will always process all of the supplied
+          input data immediately. No cache will be kept.
+        """
+        output = list(data)
+
+        for i in xrange(len(data)):
+            if ed =='e':
+                if len(self.keystream) == 0:
+                    block = self.codebook.encrypt(self.IV)
+                    self.keystream = list(block)[:self.segment_size] # keystream consists of the s MSB's
+                    self.IV = self.IV[self.segment_size:] # keeping (b-s) LSB's
+                output[i] = chr(ord(output[i]) ^ ord(self.keystream.pop(0)))
+                self.IV += output[i] # the IV for the next block in the chain is being built byte per byte as the ciphertext flows in
+            else:
+                if len(self.keystream) == 0:
+                    block = self.codebook.encrypt(self.IV)
+                    self.keystream = list(block)[:self.segment_size]
+                    self.IV = self.IV[self.segment_size:]
+                self.IV += output[i]
+                output[i] = chr(ord(output[i]) ^ ord(self.keystream.pop(0)))
+        self.totalbytes += len(output)
+        return ''.join(output)
+
+class OFB:
+    """OFB Chaining Mode
+
+    Can be accessed as a stream cipher.
+    """
+    def __init__(self, codebook, blocksize, IV):
+        self.codebook = codebook
+        self.IV = IV
+        self.blocksize = blocksize
+        self.keystream = []
+        self.totalbytes = 0
+        
+    def update(self, data, ed):
+        """Processes the given ciphertext/plaintext
+
+        Inputs:
+            data: raw string of any multiple of bytes
+            ed:   'e' for encryption, 'd' for decryption
+        Output:
+            processed raw string
+
+        The encrypt/decrypt functions will always process all of the supplied
+          input data immediately. No cache will be kept.
+        """
+        #no difference between encryption and decryption mode
+        n = len(data)
+        blocksize = self.blocksize
+        output = list(data)
+
+        for i in xrange(n):
+            if len(self.keystream) == 0: #encrypt a new counter block when the current keystream is fully used
+                self.IV = self.codebook.encrypt(self.IV)
+                self.keystream = list(self.IV)
+            output[i] = chr(ord(output[i]) ^ ord(self.keystream.pop(0))) #as long as an encrypted counter value is available, the output is just "input XOR keystream"
+        
+        self.totalbytes += len(output)
+        return ''.join(output)
+
+class CTR:
+    """CTR Chaining Mode
+
+    Can be accessed as a stream cipher.
+    """
+    # initial counter value can be choosen, decryption always starts from beginning
+    #   -> you can start from anywhere yourself: just feed the cipher encoded blocks and feed a counter with the corresponding value
+    def __init__(self, codebook, blocksize, counter):
+        self.codebook = codebook
+        self.counter = counter
+        self.blocksize = blocksize
+        self.keystream = [] #holds the output of the current encrypted counter value
+        self.totalbytes = 0
+
+    def update(self, data, ed):
+        """Processes the given ciphertext/plaintext
+
+        Inputs:
+            data: raw string of any multiple of bytes
+            ed:   'e' for encryption, 'd' for decryption
+        Output:
+            processed raw string
+
+        The encrypt/decrypt functions will always process all of the supplied
+          input data immediately. No cache will be kept.
+        """
+        # no need for the encryption/decryption distinction: both are the same
+        n = len(data)
+        blocksize = self.blocksize
+
+        output = list(data)
+        for i in xrange(n):
+            if len(self.keystream) == 0: #encrypt a new counter block when the current keystream is fully used
+                block = self.codebook.encrypt(self.counter())
+                self.keystream = list(block)
+            output[i] = chr(ord(output[i])^ord(self.keystream.pop(0))) #as long as an encrypted counter value is available, the output is just "input XOR keystream"
+        self.totalbytes += len(output)
+        return ''.join(output)
+
+class XTS:
+    """XTS Chaining Mode
+    
+    Usable with blockciphers with a 16-byte blocksize
+    """
+    # TODO: allow other blocksizes besides 16bytes?
+    def __init__(self,codebook1, codebook2):
+        self.cache = ''
+        self.codebook1 = codebook1
+        self.codebook2 = codebook2
+
+    def update(self, data, ed,tweak=''):
+        # supply n as a raw string
+        # tweak = data sequence number
+        """Perform a XTS encrypt/decrypt operation.
+
+        Because the handling of the last two blocks is linked,
+          it needs the whole block of ciphertext to be supplied at once.
+        Every decrypt function called on a XTS cipher will output
+          a decrypted block based on the current supplied ciphertext block.
+        """
+        output = ''
+        assert len(data) > 15, "At least one block of 128 bits needs to be supplied"
+        assert len(data) < 128*pow(2,20)
+
+        # initializing T
+        # e_k2_n = E_K2(tweak)
+        e_k2_n = self.codebook2.encrypt(tweak+ '\x00' * (16-len(tweak)))[::-1]
+        self.T = util.string2number(e_k2_n)
+
+        i=0
+        while i < ((len(data) // 16)-1): #Decrypt all the blocks but one last full block and opt one last partial block
+            # C = E_K1(P xor T) xor T
+            output += self.__xts_step(ed,data[i*16:(i+1)*16],self.T)
+            # T = E_K2(n) mul (a pow i)
+            self.__T_update()
+            i+=1
+
+        # Check if the data supplied is a multiple of 16 bytes -> one last full block and we're done
+        if len(data[i*16:]) == 16:
+            # C = E_K1(P xor T) xor T
+            output += self.__xts_step(ed,data[i*16:(i+1)*16],self.T)
+            # T = E_K2(n) mul (a pow i)
+            self.__T_update()
+        else:
+            T_temp = [self.T]
+            self.__T_update()
+            T_temp.append(self.T)
+            if ed=='d':
+                # Permutation of the last two indexes
+                T_temp.reverse()
+            # Decrypt/Encrypt the last two blocks when data is not a multiple of 16 bytes
+            Cm1 = data[i*16:(i+1)*16]
+            Cm = data[(i+1)*16:]
+            PP = self.__xts_step(ed,Cm1,T_temp[0])
+            Cp = PP[len(Cm):]
+            Pm = PP[:len(Cm)]
+            CC = Cm+Cp
+            Pm1 = self.__xts_step(ed,CC,T_temp[1])
+            output += Pm1 + Pm
+        return output
+
+    def __xts_step(self,ed,tocrypt,T):
+        T_string = util.number2string_N(T,16)[::-1]
+        # C = E_K1(P xor T) xor T
+        if ed == 'd':
+            return util.xorstring(T_string, self.codebook1.decrypt(util.xorstring(T_string, tocrypt)))
+        else:
+            return util.xorstring(T_string, self.codebook1.encrypt(util.xorstring(T_string, tocrypt)))
+
+    def __T_update(self):
+        # Used for calculating T for a certain step using the T value from the previous step
+        self.T = self.T << 1
+        # if (Cout)
+        if self.T >> (8*16):
+            #T[0] ^= GF_128_FDBK;
+            self.T = self.T ^ 0x100000000000000000000000000000087L
+
+
+class CMAC:
+    """CMAC chaining mode
+
+    Supports every cipher with a blocksize available
+      in the list CMAC.supported_blocksizes.
+    The hashlength is equal to block size of the used block cipher.
+    
+    Usable with blockciphers with a 8 or 16-byte blocksize
+    """
+    # TODO: move to hash module?
+    # TODO: change update behaviour to .update() and .digest() as for all hash modules?
+    #       -> other hash functions in pycrypto: calling update, concatenates current input with previous input and hashes everything
+    __Rb_dictionary = {64:0x000000000000001b,128:0x00000000000000000000000000000087}
+    supported_blocksizes = __Rb_dictionary.keys()
+    def __init__(self,codebook,blocksize,IV):
+        # Purpose of init: calculate Lu & Lu2
+        #blocksize (in bytes): to select the Rb constant in the dictionary
+        #Rb as a dictionary: adding support for other blocksizes is easy
+        self.cache=''
+        self.blocksize = blocksize
+        self.codebook = codebook
+        self.IV = IV
+
+        #Rb_dictionary: holds values for Rb for different blocksizes
+        # values for 64 and 128 bits found here: http://www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/omac.html
+        # explanation from: http://csrc.nist.gov/publications/nistpubs/800-38B/SP_800-38B.pdf
+        #             Rb is a representation of a certain irreducible binary polynomial of degree b, namely,
+        #             the lexicographically first among all such polynomials with the minimum possible number of
+        #             nonzero terms. If this polynomial is expressed as ub+cb-1ub-1+...+c2u2+c1u+c0, where the
+        #             coefficients cb-1, cb-2, ..., c2, c1, c0 are either 0 or 1, then Rb is the bit string cb-1cb-2...c2c1c0.
+
+        self.Rb = self.__Rb_dictionary[blocksize*8]
+
+        mask1 = int(('\xff'*blocksize).encode('hex'),16)
+        mask2 = int(('\x80' + '\x00'*(blocksize-1) ).encode('hex'),16)
+
+        L = int(self.codebook.encrypt('\x00'*blocksize).encode('hex'),16)
+
+        if L & mask2:
+            Lu = ((L << 1) & mask1) ^ self.Rb
+        else:
+            Lu = L << 1
+            Lu = Lu & mask1
+
+        if Lu & mask2:
+            Lu2 = ((Lu << 1) & mask1)^ self.Rb
+        else:
+            Lu2 = Lu << 1
+            Lu2 = Lu2 & mask1
+
+        self.Lu =util.number2string_N(Lu,self.blocksize)
+        self.Lu2=util.number2string_N(Lu2,self.blocksize)
+
+    def update(self, data, ed):
+        """Processes the given ciphertext/plaintext
+
+        Inputs:
+            data: raw string of any length
+            ed:   'e' for encryption, 'd' for decryption
+        Output:
+            hashed data as raw string
+
+        This is not really an update function:
+        Everytime the function is called, the hash from the input data is calculated.
+        No finalizing needed.
+        """
+        assert ed == 'e'
+        blocksize = self.blocksize
+
+        m = (len(data)+blocksize-1)/blocksize #m = amount of datablocks
+        i=0
+        for i in range(1,m):
+            self.IV = self.codebook.encrypt( util.xorstring(data[(i-1)*blocksize:(i)*blocksize],self.IV) )
+
+        if len(data[(i)*blocksize:])==blocksize:
+            X = util.xorstring(util.xorstring(data[(i)*blocksize:],self.IV),self.Lu)
+        else:
+            tmp = data[(i)*blocksize:] + '\x80' + '\x00'*(blocksize - len(data[(i)*blocksize:])-1)
+            X = util.xorstring(util.xorstring(tmp,self.IV),self.Lu2)
+
+        T = self.codebook.encrypt(X)
+        return T
+
+

+ 248 - 0
pwman/util/crypto/padding.py

@@ -0,0 +1,248 @@
+# =============================================================================
+# Copyright (c) 2008 Christophe Oosterlynck <christophe.oosterlynck_AT_gmail.com>
+#                    & NXP ( Philippe Teuwen <philippe.teuwen_AT_nxp.com> )
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+# =============================================================================
+
+"""Module for padding functions
+
+padding info here: http://en.wikipedia.org/wiki/Padding_(cryptography)
+"""
+    
+import random
+
+PAD = 0
+UNPAD = 1
+
+def bitPadding (padData, direction, length=None):
+        """Pad a string using bitPadding
+
+            padData = raw string to pad/unpad
+            direction = PAD or UNPAD
+            length = amount of bytes the padded string should be a multiple of
+                     (length variable is not used when unpadding)
+            
+            returns: (un)padded raw string
+            
+            A new block full of padding will be added when padding data that is
+            already a multiple of the length.
+            
+            Example:
+            =========
+            >>> import padding
+
+            >>> padding.bitPadding('test', padding.PAD, 8)
+            'test\\x80\\x00\\x00\\x00'
+            >>> padding.bitPadding(_,padding.UNPAD)
+            'test'"""
+        if direction == PAD:
+            if length == None:
+                raise ValueError,"Supply a valid length"
+            return __bitPadding(padData, length)
+        elif direction == UNPAD:
+            return __bitPadding_unpad(padData)
+        else:
+            raise ValueError,"Supply a valid direction"
+
+def __bitPadding (toPad,length):
+    padded = toPad + '\x80' + '\x00'*(length - len(toPad)%length -1)
+    return padded
+
+def __bitPadding_unpad (padded):
+    if padded.rstrip('\x00')[-1] == '\x80':
+        return padded.rstrip('\x00')[:-1]
+    else:
+        return padded
+
+def zerosPadding (padData, direction, length=None):
+        """Pad a string using zerosPadding
+
+            padData = raw string to pad/unpad
+            direction = PAD or UNPAD
+                        beware: padding and unpadding a string ending in 0's
+                                will remove those 0's too
+            length = amount of bytes the padded string should be a multiple of
+                     (length variable is not used when unpadding)
+            
+            returns: (un)padded raw string
+            
+            No padding will be added when padding data that is already a
+            multiple of the given length.
+            
+            Example:
+            =========
+            >>> import padding
+
+            >>> padding.zerosPadding('12345678',padding.PAD,16)
+            '12345678\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'
+            >>> padding.zerosPadding(_,padding.UNPAD)
+            '12345678'"""
+        if direction == PAD:
+            if length == None:
+                raise ValueError,"Supply a valid length"
+            return __zerosPadding(padData, length)
+        elif direction == UNPAD:
+            return __zerosPadding_unpad(padData)
+        else:
+            raise ValueError,"Supply a valid direction"
+
+def __zerosPadding (toPad, length):
+    padLength = (length - len(toPad))%length
+    return toPad + '\x00'*padLength
+
+def __zerosPadding_unpad (padded ):
+    return padded.rstrip('\x00')
+
+def PKCS7(padData, direction, length=None):
+        """Pad a string using PKCS7
+
+            padData = raw string to pad/unpad
+            direction = PAD or UNPAD
+            length = amount of bytes the padded string should be a multiple of
+                     (length variable is not used when unpadding)
+            
+            returns: (un)padded raw string
+            
+            A new block full of padding will be added when padding data that is
+            already a multiple of the given length.
+            
+            Example:
+            =========
+            >>> import padding
+
+            >>> padding.PKCS7('12345678',padding.PAD,16)
+            '12345678\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08'
+            >>> padding.PKCS7(_,padding.UNPAD)
+            '12345678'"""
+        if direction == PAD:
+            if length == None:
+                raise ValueError,"Supply a valid length"
+            return __PKCS7(padData, length)
+        elif direction == UNPAD:
+            return __PKCS7_unpad(padData)
+        else:
+            raise ValueError,"Supply a valid direction"
+
+
+def __PKCS7 (toPad, length):
+    amount = length - len(toPad)%length
+    pattern = chr(amount)
+    pad = pattern*amount
+    return toPad + pad
+
+def __PKCS7_unpad (padded):
+    pattern = padded[-1]
+    length = ord(pattern)
+    #check if the bytes to be removed are all the same pattern
+    if padded.endswith(pattern*length):
+        return padded[:-length]
+    else:
+        return padded
+        print 'error: padding pattern not recognized'
+
+def ANSI_X923 (padData, direction, length=None):
+        """Pad a string using ANSI_X923
+
+            padData = raw string to pad/unpad
+            direction = PAD or UNPAD
+            length = amount of bytes the padded string should be a multiple of
+                     (length variable is not used when unpadding)
+            
+            returns: (un)padded raw string
+            
+            A new block full of padding will be added when padding data that is
+            already a multiple of the given length.
+            
+            Example:
+            =========
+            >>> import padding
+            
+            >>> padding.ANSI_X923('12345678',padding.PAD,16)
+            '12345678\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x08'
+            >>> padding.ANSI_X923(_,padding.UNPAD)
+            '12345678'"""
+        if direction == PAD:
+            if length == None:
+                raise ValueError,"Supply a valid length"
+            return __ANSI_X923(padData, length)
+        elif direction == UNPAD:
+            return __ANSI_X923_unpad(padData)
+        else:
+            raise ValueError,"Supply a valid direction"
+
+def __ANSI_X923 (toPad, length):
+    bytesToPad = length - len(toPad)%length
+    trail = chr(bytesToPad)
+    pattern = '\x00'*(bytesToPad -1) + trail
+    return toPad + pattern
+
+def __ANSI_X923_unpad (padded):
+    length =ord(padded[-1])
+    #check if the bytes to be removed are all zero
+    if padded.count('\x00',-length,-1) == length - 1:
+        return padded[:-length]
+    else:
+        print 'error: padding pattern not recognized %s' % padded.count('\x00',-length,-1)
+        return padded
+
+def ISO_10126 (padData, direction, length=None):
+        """Pad a string using ISO_10126
+
+            padData = raw string to pad/unpad
+            direction = PAD or UNPAD
+            length = amount of bytes the padded string should be a multiple of
+                     (length variable is not used when unpadding)
+            
+            returns: (un)padded raw string
+            
+            A new block full of padding will be added when padding data that is
+            already a multiple of the given length.
+            
+            Example:
+            =========
+            >>> import padding
+
+            >>> padded = padding.ISO_10126('12345678',padding.PAD,16)
+            >>> padding.ISO_10126(padded,padding.UNPAD)
+            '12345678'"""
+        if direction == PAD:
+            if length == None:
+                raise ValueError,"Supply a valid length"
+            return __ISO_10126(padData, length)
+        elif direction == UNPAD:
+            return __ISO_10126_unpad(padData)
+        else:
+            raise ValueError,"Supply a valid direction"
+
+def __ISO_10126 (toPad, length):
+    bytesToPad = length - len(toPad)%length
+    randomPattern = ''.join(chr(random.randint(0,255)) for x in range(0,bytesToPad-1))
+    return toPad + randomPattern + chr(bytesToPad)
+
+def __ISO_10126_unpad (padded):
+   return padded[0:len(padded)-ord(padded[-1])]
+
+def _test():
+    import doctest
+    doctest.testmod()
+
+if __name__ == "__main__":
+    _test()
+

+ 354 - 0
pwman/util/crypto/pypbkdf2.py

@@ -0,0 +1,354 @@
+#!/usr/bin/python
+# -*- coding: ascii -*-
+###########################################################################
+# PBKDF2.py - PKCS#5 v2.0 Password-Based Key Derivation
+#
+# Copyright (C) 2007, 2008 Dwayne C. Litzenberger <dlitz@dlitz.net>
+# All rights reserved.
+# 
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted,
+# provided that the above copyright notice appear in all copies and that
+# both that copyright notice and this permission notice appear in
+# supporting documentation.
+# 
+# THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR 
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Country of origin: Canada
+#
+###########################################################################
+# Sample PBKDF2 usage:
+#   from Crypto.Cipher import AES
+#   from PBKDF2 import PBKDF2
+#   import os
+#
+#   salt = os.urandom(8)    # 64-bit salt
+#   key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key
+#   iv = os.urandom(16)     # 128-bit IV
+#   cipher = AES.new(key, AES.MODE_CBC, iv)
+#     ...
+#
+# Sample crypt() usage:
+#   from PBKDF2 import crypt
+#   pwhash = crypt("secret")
+#   alleged_pw = raw_input("Enter password: ")
+#   if pwhash == crypt(alleged_pw, pwhash):
+#       print "Password good"
+#   else:
+#       print "Invalid password"
+#
+###########################################################################
+# History:
+#
+#  2007-07-27 Dwayne C. Litzenberger <dlitz@dlitz.net>
+#   - Initial Release (v1.0)
+#
+#  2007-07-31 Dwayne C. Litzenberger <dlitz@dlitz.net>
+#   - Bugfix release (v1.1)
+#   - SECURITY: The PyCrypto XOR cipher (used, if available, in the _strxor
+#   function in the previous release) silently truncates all keys to 64
+#   bytes.  The way it was used in the previous release, this would only be
+#   problem if the pseudorandom function that returned values larger than
+#   64 bytes (so SHA1, SHA256 and SHA512 are fine), but I don't like
+#   anything that silently reduces the security margin from what is
+#   expected.
+#  
+# 2008-06-17 Dwayne C. Litzenberger <dlitz@dlitz.net>
+#   - Compatibility release (v1.2)
+#   - Add support for older versions of Python (2.2 and 2.3).
+#
+###########################################################################
+
+__version__ = "1.2"
+
+from struct import pack
+from binascii import b2a_hex
+from random import randint
+import string
+
+try:
+    # Use PyCrypto (if available)
+    from Crypto.Hash import HMAC, SHA as SHA1
+
+except ImportError:
+    # PyCrypto not available.  Use the Python standard library.
+    import hmac as HMAC
+    import sha as SHA1
+
+def strxor(a, b):
+    return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
+
+def b64encode(data, chars="+/"):
+    tt = string.maketrans("+/", chars)
+    return data.encode('base64').replace("\n", "").translate(tt)
+
+class PBKDF2(object):
+    """PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation
+    
+    This implementation takes a passphrase and a salt (and optionally an
+    iteration count, a digest module, and a MAC module) and provides a
+    file-like object from which an arbitrarily-sized key can be read.
+
+    If the passphrase and/or salt are unicode objects, they are encoded as
+    UTF-8 before they are processed.
+
+    The idea behind PBKDF2 is to derive a cryptographic key from a
+    passphrase and a salt.
+    
+    PBKDF2 may also be used as a strong salted password hash.  The
+    'crypt' function is provided for that purpose.
+    
+    Remember: Keys generated using PBKDF2 are only as strong as the
+    passphrases they are derived from.
+    """
+
+    def __init__(self, passphrase, salt, iterations=1000,
+                 digestmodule=SHA1, macmodule=HMAC):
+        self.__macmodule = macmodule
+        self.__digestmodule = digestmodule
+        self._setup(passphrase, salt, iterations, self._pseudorandom)
+
+    def _pseudorandom(self, key, msg):
+        """Pseudorandom function.  e.g. HMAC-SHA1"""
+        return self.__macmodule.new(key=key, msg=msg,
+            digestmod=self.__digestmodule).digest()
+    
+    def read(self, bytes):
+        """Read the specified number of key bytes."""
+        if self.closed:
+            raise ValueError("file-like object is closed")
+
+        size = len(self.__buf)
+        blocks = [self.__buf]
+        i = self.__blockNum
+        while size < bytes:
+            i += 1
+            if i > 0xffffffffL or i < 1:
+                # We could return "" here, but 
+                raise OverflowError("derived key too long")
+            block = self.__f(i)
+            blocks.append(block)
+            size += len(block)
+        buf = "".join(blocks)
+        retval = buf[:bytes]
+        self.__buf = buf[bytes:]
+        self.__blockNum = i
+        return retval
+    
+    def __f(self, i):
+        # i must fit within 32 bits
+        assert 1 <= i <= 0xffffffffL
+        U = self.__prf(self.__passphrase, self.__salt + pack("!L", i))
+        result = U
+        for j in xrange(2, 1+self.__iterations):
+            U = self.__prf(self.__passphrase, U)
+            result = strxor(result, U)
+        return result
+    
+    def hexread(self, octets):
+        """Read the specified number of octets. Return them as hexadecimal.
+
+        Note that len(obj.hexread(n)) == 2*n.
+        """
+        return b2a_hex(self.read(octets))
+
+    def _setup(self, passphrase, salt, iterations, prf):
+        # Sanity checks:
+        
+        # passphrase and salt must be str or unicode (in the latter
+        # case, we convert to UTF-8)
+        if isinstance(passphrase, unicode):
+            passphrase = passphrase.encode("UTF-8")
+        if not isinstance(passphrase, str):
+            raise TypeError("passphrase must be str or unicode")
+        if isinstance(salt, unicode):
+            salt = salt.encode("UTF-8")
+        if not isinstance(salt, str):
+            raise TypeError("salt must be str or unicode")
+
+        # iterations must be an integer >= 1
+        if not isinstance(iterations, (int, long)):
+            raise TypeError("iterations must be an integer")
+        if iterations < 1:
+            raise ValueError("iterations must be at least 1")
+        
+        # prf must be callable
+        if not callable(prf):
+            raise TypeError("prf must be callable")
+
+        self.__passphrase = passphrase
+        self.__salt = salt
+        self.__iterations = iterations
+        self.__prf = prf
+        self.__blockNum = 0
+        self.__buf = ""
+        self.closed = False
+    
+    def close(self):
+        """Close the stream."""
+        if not self.closed:
+            del self.__passphrase
+            del self.__salt
+            del self.__iterations
+            del self.__prf
+            del self.__blockNum
+            del self.__buf
+            self.closed = True
+
+def crypt(word, salt=None, iterations=None):
+    """PBKDF2-based unix crypt(3) replacement.
+    
+    The number of iterations specified in the salt overrides the 'iterations'
+    parameter.
+
+    The effective hash length is 192 bits.
+    """
+    
+    # Generate a (pseudo-)random salt if the user hasn't provided one.
+    if salt is None:
+        salt = _makesalt()
+
+    # salt must be a string or the us-ascii subset of unicode
+    if isinstance(salt, unicode):
+        salt = salt.encode("us-ascii")
+    if not isinstance(salt, str):
+        raise TypeError("salt must be a string")
+
+    # word must be a string or unicode (in the latter case, we convert to UTF-8)
+    if isinstance(word, unicode):
+        word = word.encode("UTF-8")
+    if not isinstance(word, str):
+        raise TypeError("word must be a string or unicode")
+
+    # Try to extract the real salt and iteration count from the salt
+    if salt.startswith("$p5k2$"):
+        (iterations, salt, dummy) = salt.split("$")[2:5]
+        if iterations == "":
+            iterations = 400
+        else:
+            converted = int(iterations, 16)
+            if iterations != "%x" % converted:  # lowercase hex, minimum digits
+                raise ValueError("Invalid salt")
+            iterations = converted
+            if not (iterations >= 1):
+                raise ValueError("Invalid salt")
+    
+    # Make sure the salt matches the allowed character set
+    allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
+    for ch in salt:
+        if ch not in allowed:
+            raise ValueError("Illegal character %r in salt" % (ch,))
+
+    if iterations is None or iterations == 400:
+        iterations = 400
+        salt = "$p5k2$$" + salt
+    else:
+        salt = "$p5k2$%x$%s" % (iterations, salt)
+    rawhash = PBKDF2(word, salt, iterations).read(24)
+    return salt + "$" + b64encode(rawhash, "./")
+
+# Add crypt as a static method of the PBKDF2 class
+# This makes it easier to do "from PBKDF2 import PBKDF2" and still use
+# crypt.
+PBKDF2.crypt = staticmethod(crypt)
+
+def _makesalt():
+    """Return a 48-bit pseudorandom salt for crypt().
+    
+    This function is not suitable for generating cryptographic secrets.
+    """
+    binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)])
+    return b64encode(binarysalt, "./")
+
+def test_pbkdf2():
+    """Module self-test"""
+    from binascii import a2b_hex
+    
+    #
+    # Test vectors from RFC 3962
+    #
+
+    # Test 1
+    result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1).read(16)
+    expected = a2b_hex("cdedb5281bb2f801565a1122b2563515")
+    if result != expected:
+        raise RuntimeError("self-test failed")
+
+    # Test 2
+    result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1200).hexread(32)
+    expected = ("5c08eb61fdf71e4e4ec3cf6ba1f5512b"
+                "a7e52ddbc5e5142f708a31e2e62b1e13")
+    if result != expected:
+        raise RuntimeError("self-test failed")
+
+    # Test 3
+    result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32)
+    expected = ("139c30c0966bc32ba55fdbf212530ac9"
+                "c5ec59f1a452f5cc9ad940fea0598ed1")
+    if result != expected:
+        raise RuntimeError("self-test failed")
+    
+    # Test 4
+    result = PBKDF2("X"*65, "pass phrase exceeds block size", 1200).hexread(32)
+    expected = ("9ccad6d468770cd51b10e6a68721be61"
+                "1a8b4d282601db3b36be9246915ec82a")
+    if result != expected:
+        raise RuntimeError("self-test failed")
+    
+    #
+    # Other test vectors
+    #
+    
+    # Chunked read
+    f = PBKDF2("kickstart", "workbench", 256)
+    result = f.read(17)
+    result += f.read(17)
+    result += f.read(1)
+    result += f.read(2)
+    result += f.read(3)
+    expected = PBKDF2("kickstart", "workbench", 256).read(40)
+    if result != expected:
+        raise RuntimeError("self-test failed")
+    
+    #
+    # crypt() test vectors
+    #
+
+    # crypt 1
+    result = crypt("cloadm", "exec")
+    expected = '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'
+    if result != expected:
+        raise RuntimeError("self-test failed")
+    
+    # crypt 2
+    result = crypt("gnu", '$p5k2$c$u9HvcT4d$.....')
+    expected = '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'
+    if result != expected:
+        raise RuntimeError("self-test failed")
+
+    # crypt 3
+    result = crypt("dcl", "tUsch7fU", iterations=13)
+    expected = "$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL"
+    if result != expected:
+        raise RuntimeError("self-test failed")
+    
+    # crypt 4 (unicode)
+    result = crypt(u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2',
+        '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ')
+    expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'
+    if result != expected:
+        raise RuntimeError("self-test failed")
+
+if __name__ == '__main__':
+    test_pbkdf2()
+
+# vim:set ts=4 sw=4 sts=4 expandtab:

+ 417 - 0
pwman/util/crypto/rijndael.py

@@ -0,0 +1,417 @@
+"""
+Copyright (c) 2014  Philippe Teuwen <phil@teuwen.org>
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of 
+this software and associated documentation files (the "Software"), to deal in 
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
+IN THE SOFTWARE.
+
+This code is taken from https://github.com/doegox/python-cryptoplus/
+
+A pure python (slow) implementation of rijndael with a decent interface
+
+To include -
+
+from rijndael import rijndael
+
+To do a key setup -
+
+r = rijndael(key, block_size = 16)
+
+key must be a string of length 16, 24, or 32
+blocksize must be 16, 24, or 32. Default is 16
+
+To use -
+
+ciphertext = r.encrypt(plaintext)
+plaintext = r.decrypt(ciphertext)
+
+If any strings are of the wrong length a ValueError is thrown
+
+
+
+"""
+
+# ported from the Java reference code by Bram Cohen, bram@gawth.com, April 2001
+# this code is public domain, unless someone makes
+# an intellectual property claim against the reference
+# code, in which case it can be made public domain by
+# deleting all the comments and renaming all the variables
+
+import copy
+import string
+
+
+
+#-----------------------
+#TREV - ADDED BECAUSE THERE'S WARNINGS ABOUT INT OVERFLOW BEHAVIOR CHANGING IN
+#2.4.....
+import os
+if os.name != "java":
+    import exceptions
+    if hasattr(exceptions, "FutureWarning"):
+        import warnings
+        warnings.filterwarnings("ignore", category=FutureWarning, append=1)
+#-----------------------
+
+
+
+shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]],
+          [[0, 0], [1, 5], [2, 4], [3, 3]],
+          [[0, 0], [1, 7], [3, 5], [4, 4]]]
+
+# [keysize][block_size]
+num_rounds = {16: {16: 10, 24: 12, 32: 14}, 24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}}
+
+A = [[1, 1, 1, 1, 1, 0, 0, 0],
+     [0, 1, 1, 1, 1, 1, 0, 0],
+     [0, 0, 1, 1, 1, 1, 1, 0],
+     [0, 0, 0, 1, 1, 1, 1, 1],
+     [1, 0, 0, 0, 1, 1, 1, 1],
+     [1, 1, 0, 0, 0, 1, 1, 1],
+     [1, 1, 1, 0, 0, 0, 1, 1],
+     [1, 1, 1, 1, 0, 0, 0, 1]]
+
+# produce log and alog tables, needed for multiplying in the
+# field GF(2^m) (generator = 3)
+alog = [1]
+for i in xrange(255):
+    j = (alog[-1] << 1) ^ alog[-1]
+    if j & 0x100 != 0:
+        j ^= 0x11B
+    alog.append(j)
+
+log = [0] * 256
+for i in xrange(1, 255):
+    log[alog[i]] = i
+
+# multiply two elements of GF(2^m)
+def mul(a, b):
+    if a == 0 or b == 0:
+        return 0
+    return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255]
+
+# substitution box based on F^{-1}(x)
+box = [[0] * 8 for i in xrange(256)]
+box[1][7] = 1
+for i in xrange(2, 256):
+    j = alog[255 - log[i]]
+    for t in xrange(8):
+        box[i][t] = (j >> (7 - t)) & 0x01
+
+B = [0, 1, 1, 0, 0, 0, 1, 1]
+
+# affine transform:  box[i] <- B + A*box[i]
+cox = [[0] * 8 for i in xrange(256)]
+for i in xrange(256):
+    for t in xrange(8):
+        cox[i][t] = B[t]
+        for j in xrange(8):
+            cox[i][t] ^= A[t][j] * box[i][j]
+
+# S-boxes and inverse S-boxes
+S =  [0] * 256
+Si = [0] * 256
+for i in xrange(256):
+    S[i] = cox[i][0] << 7
+    for t in xrange(1, 8):
+        S[i] ^= cox[i][t] << (7-t)
+    Si[S[i] & 0xFF] = i
+
+# T-boxes
+G = [[2, 1, 1, 3],
+    [3, 2, 1, 1],
+    [1, 3, 2, 1],
+    [1, 1, 3, 2]]
+
+AA = [[0] * 8 for i in xrange(4)]
+
+for i in xrange(4):
+    for j in xrange(4):
+        AA[i][j] = G[i][j]
+        AA[i][i+4] = 1
+
+for i in xrange(4):
+    pivot = AA[i][i]
+    if pivot == 0:
+        t = i + 1
+        while AA[t][i] == 0 and t < 4:
+            t += 1
+            assert t != 4, 'G matrix must be invertible'
+            for j in xrange(8):
+                AA[i][j], AA[t][j] = AA[t][j], AA[i][j]
+            pivot = AA[i][i]
+    for j in xrange(8):
+        if AA[i][j] != 0:
+            AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255]
+    for t in xrange(4):
+        if i != t:
+            for j in xrange(i+1, 8):
+                AA[t][j] ^= mul(AA[i][j], AA[t][i])
+            AA[t][i] = 0
+
+iG = [[0] * 4 for i in xrange(4)]
+
+for i in xrange(4):
+    for j in xrange(4):
+        iG[i][j] = AA[i][j + 4]
+
+def mul4(a, bs):
+    if a == 0:
+        return 0
+    r = 0
+    for b in bs:
+        r <<= 8
+        if b != 0:
+            r = r | mul(a, b)
+    return r
+
+T1 = []
+T2 = []
+T3 = []
+T4 = []
+T5 = []
+T6 = []
+T7 = []
+T8 = []
+U1 = []
+U2 = []
+U3 = []
+U4 = []
+
+for t in xrange(256):
+    s = S[t]
+    T1.append(mul4(s, G[0]))
+    T2.append(mul4(s, G[1]))
+    T3.append(mul4(s, G[2]))
+    T4.append(mul4(s, G[3]))
+
+    s = Si[t]
+    T5.append(mul4(s, iG[0]))
+    T6.append(mul4(s, iG[1]))
+    T7.append(mul4(s, iG[2]))
+    T8.append(mul4(s, iG[3]))
+
+    U1.append(mul4(t, iG[0]))
+    U2.append(mul4(t, iG[1]))
+    U3.append(mul4(t, iG[2]))
+    U4.append(mul4(t, iG[3]))
+
+# round constants
+rcon = [1]
+r = 1
+for t in xrange(1, 30):
+    r = mul(2, r)
+    rcon.append(r)
+
+del A
+del AA
+del pivot
+del B
+del G
+del box
+del log
+del alog
+del i
+del j
+del r
+del s
+del t
+del mul
+del mul4
+del cox
+del iG
+
+class rijndael:
+    def __init__(self, key, block_size = 16):
+        if block_size != 16 and block_size != 24 and block_size != 32:
+            raise ValueError('Invalid block size: ' + str(block_size))
+        if len(key) != 16 and len(key) != 24 and len(key) != 32:
+            raise ValueError('Invalid key size: ' + str(len(key)))
+        self.block_size = block_size
+
+        ROUNDS = num_rounds[len(key)][block_size]
+        BC = block_size / 4
+        # encryption round keys
+        Ke = [[0] * BC for i in xrange(ROUNDS + 1)]
+        # decryption round keys
+        Kd = [[0] * BC for i in xrange(ROUNDS + 1)]
+        ROUND_KEY_COUNT = (ROUNDS + 1) * BC
+        KC = len(key) / 4
+
+        # copy user material bytes into temporary ints
+        tk = []
+        for i in xrange(0, KC):
+            tk.append((ord(key[i * 4]) << 24) | (ord(key[i * 4 + 1]) << 16) |
+                (ord(key[i * 4 + 2]) << 8) | ord(key[i * 4 + 3]))
+
+        # copy values into round key arrays
+        t = 0
+        j = 0
+        while j < KC and t < ROUND_KEY_COUNT:
+            Ke[t / BC][t % BC] = tk[j]
+            Kd[ROUNDS - (t / BC)][t % BC] = tk[j]
+            j += 1
+            t += 1
+        tt = 0
+        rconpointer = 0
+        while t < ROUND_KEY_COUNT:
+            # extrapolate using phi (the round key evolution function)
+            tt = tk[KC - 1]
+            tk[0] ^= (S[(tt >> 16) & 0xFF] & 0xFF) << 24 ^  \
+                     (S[(tt >>  8) & 0xFF] & 0xFF) << 16 ^  \
+                     (S[ tt        & 0xFF] & 0xFF) <<  8 ^  \
+                     (S[(tt >> 24) & 0xFF] & 0xFF)       ^  \
+                     (rcon[rconpointer]    & 0xFF) << 24
+            rconpointer += 1
+            if KC != 8:
+                for i in xrange(1, KC):
+                    tk[i] ^= tk[i-1]
+            else:
+                for i in xrange(1, KC / 2):
+                    tk[i] ^= tk[i-1]
+                tt = tk[KC / 2 - 1]
+                tk[KC / 2] ^= (S[ tt        & 0xFF] & 0xFF)       ^ \
+                              (S[(tt >>  8) & 0xFF] & 0xFF) <<  8 ^ \
+                              (S[(tt >> 16) & 0xFF] & 0xFF) << 16 ^ \
+                              (S[(tt >> 24) & 0xFF] & 0xFF) << 24
+                for i in xrange(KC / 2 + 1, KC):
+                    tk[i] ^= tk[i-1]
+            # copy values into round key arrays
+            j = 0
+            while j < KC and t < ROUND_KEY_COUNT:
+                Ke[t / BC][t % BC] = tk[j]
+                Kd[ROUNDS - (t / BC)][t % BC] = tk[j]
+                j += 1
+                t += 1
+        # inverse MixColumn where needed
+        for r in xrange(1, ROUNDS):
+            for j in xrange(BC):
+                tt = Kd[r][j]
+                Kd[r][j] = U1[(tt >> 24) & 0xFF] ^ \
+                           U2[(tt >> 16) & 0xFF] ^ \
+                           U3[(tt >>  8) & 0xFF] ^ \
+                           U4[ tt        & 0xFF]
+        self.Ke = Ke
+        self.Kd = Kd
+
+    def encrypt(self, plaintext):
+        if len(plaintext) != self.block_size:
+            raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext)))
+        Ke = self.Ke
+
+        BC = self.block_size / 4
+        ROUNDS = len(Ke) - 1
+        if BC == 4:
+            SC = 0
+        elif BC == 6:
+            SC = 1
+        else:
+            SC = 2
+        s1 = shifts[SC][1][0]
+        s2 = shifts[SC][2][0]
+        s3 = shifts[SC][3][0]
+        a = [0] * BC
+        # temporary work array
+        t = []
+        # plaintext to ints + key
+        for i in xrange(BC):
+            t.append((ord(plaintext[i * 4    ]) << 24 |
+                      ord(plaintext[i * 4 + 1]) << 16 |
+                      ord(plaintext[i * 4 + 2]) <<  8 |
+                      ord(plaintext[i * 4 + 3])        ) ^ Ke[0][i])
+        # apply round transforms
+        for r in xrange(1, ROUNDS):
+            for i in xrange(BC):
+                a[i] = (T1[(t[ i           ] >> 24) & 0xFF] ^
+                        T2[(t[(i + s1) % BC] >> 16) & 0xFF] ^
+                        T3[(t[(i + s2) % BC] >>  8) & 0xFF] ^
+                        T4[ t[(i + s3) % BC]        & 0xFF]  ) ^ Ke[r][i]
+            t = copy.copy(a)
+        # last round is special
+        result = []
+        for i in xrange(BC):
+            tt = Ke[ROUNDS][i]
+            result.append((S[(t[ i           ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
+            result.append((S[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
+            result.append((S[(t[(i + s2) % BC] >>  8) & 0xFF] ^ (tt >>  8)) & 0xFF)
+            result.append((S[ t[(i + s3) % BC]        & 0xFF] ^  tt       ) & 0xFF)
+        return string.join(map(chr, result), '')
+
+    def decrypt(self, ciphertext):
+        if len(ciphertext) != self.block_size:
+            raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext)))
+        Kd = self.Kd
+
+        BC = self.block_size / 4
+        ROUNDS = len(Kd) - 1
+        if BC == 4:
+            SC = 0
+        elif BC == 6:
+            SC = 1
+        else:
+            SC = 2
+        s1 = shifts[SC][1][1]
+        s2 = shifts[SC][2][1]
+        s3 = shifts[SC][3][1]
+        a = [0] * BC
+        # temporary work array
+        t = [0] * BC
+        # ciphertext to ints + key
+        for i in xrange(BC):
+            t[i] = (ord(ciphertext[i * 4    ]) << 24 |
+                    ord(ciphertext[i * 4 + 1]) << 16 |
+                    ord(ciphertext[i * 4 + 2]) <<  8 |
+                    ord(ciphertext[i * 4 + 3])        ) ^ Kd[0][i]
+        # apply round transforms
+        for r in xrange(1, ROUNDS):
+            for i in xrange(BC):
+                a[i] = (T5[(t[ i           ] >> 24) & 0xFF] ^
+                        T6[(t[(i + s1) % BC] >> 16) & 0xFF] ^
+                        T7[(t[(i + s2) % BC] >>  8) & 0xFF] ^
+                        T8[ t[(i + s3) % BC]        & 0xFF]  ) ^ Kd[r][i]
+            t = copy.copy(a)
+        # last round is special
+        result = []
+        for i in xrange(BC):
+            tt = Kd[ROUNDS][i]
+            result.append((Si[(t[ i           ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
+            result.append((Si[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
+            result.append((Si[(t[(i + s2) % BC] >>  8) & 0xFF] ^ (tt >>  8)) & 0xFF)
+            result.append((Si[ t[(i + s3) % BC]        & 0xFF] ^  tt       ) & 0xFF)
+        return string.join(map(chr, result), '')
+
+def encrypt(key, block):
+    return rijndael(key, len(block)).encrypt(block)
+
+def decrypt(key, block):
+    return rijndael(key, len(block)).decrypt(block)
+
+def test():
+    def t(kl, bl):
+        b = 'b' * bl
+        r = rijndael('a' * kl, bl)
+        assert r.decrypt(r.encrypt(b)) == b
+    t(16, 16)
+    t(16, 24)
+    t(16, 32)
+    t(24, 16)
+    t(24, 24)
+    t(24, 32)
+    t(32, 16)
+    t(32, 24)
+    t(32, 32)

+ 81 - 0
pwman/util/crypto/util.py

@@ -0,0 +1,81 @@
+"""
+Copyright (c) 2014  Philippe Teuwen <phil@teuwen.org>
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of 
+this software and associated documentation files (the "Software"), to deal in 
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
+IN THE SOFTWARE.
+
+This code is taken from https://github.com/doegox/python-cryptoplus/
+"""
+
+def number2string(i):
+    """Convert a number to a string
+
+    Input: long or integer
+    Output: string (big-endian)
+    """
+    s=hex(i)[2:].rstrip('L')
+    if len(s) % 2:
+        s = '0' + s
+    return s.decode('hex')
+
+def number2string_N(i, N):
+    """Convert a number to a string of fixed size
+
+    i: long or integer
+    N: length of string
+    Output: string (big-endian)
+    """
+    s = '%0*x' % (N*2, i)
+    return s.decode('hex')
+
+def string2number(i):
+    """ Convert a string to a number
+
+    Input: string (big-endian)
+    Output: long or integer
+    """
+    return int(i.encode('hex'),16)
+
+def xorstring(a,b):
+    """XOR two strings of same length
+
+    For more complex cases, see CryptoPlus.Cipher.XOR"""
+    assert len(a) == len(b)
+    return number2string_N(string2number(a)^string2number(b), len(a))
+
+class Counter(str):
+    #found here: http://www.lag.net/pipermail/paramiko/2008-February.txt
+    """Necessary for CTR chaining mode
+
+    Initializing a counter object (ctr = Counter('xxx'), gives a value to the counter object.
+    Everytime the object is called ( ctr() ) it returns the current value and increments it by 1.
+    Input/output is a raw string.
+
+    Counter value is big endian"""
+    def __init__(self, initial_ctr):
+        if not isinstance(initial_ctr, str):
+            raise TypeError("nonce must be str")
+        self.c = int(initial_ctr.encode('hex'), 16)
+    def __call__(self):
+        # This might be slow, but it works as a demonstration
+        ctr = ("%032x" % (self.c,)).decode('hex')
+        self.c += 1
+        return ctr
+
+

+ 23 - 6
pwman/util/crypto_engine.py

@@ -19,16 +19,30 @@
 
 from __future__ import print_function
 import base64
+import binascii
 import ctypes
-import string
 import os
+import random
+import string
 import sys
-import binascii
 import time
-import random
-from Crypto.Cipher import AES
-from Crypto.Protocol.KDF import PBKDF2
+
+try:
+    from Crypto.Cipher import AES
+    from Crypto.Protocol.KDF import PBKDF2
+except ImportError:
+    # PyCryptop not found, we use a compatible implementation
+    # in pure Python.
+    # This is good for Windows where software installation suck
+    # or embeded devices where compilation is a bit harder
+    from pwman.util.crypto import AES
+    from pwman.util.crypto.pypbkdf2 import PBKDF2
+    print("WARNING: You are not using PyCrypto!!!")
+    print("WARNING: You should install PyCrypto for better security and perfomance")
+    print("WARNING: You can supress this warning by editing pwman config file.")
+
 from pwman.util.callback import Callback
+
 if sys.version_info.major > 2:  # pragma: no cover
     raw_input = input
 
@@ -74,7 +88,10 @@ def get_digest(password, salt):
     iterations = 5000
     if isinstance(password, bytes):
         password = password.decode()
-    return PBKDF2(password, salt, dkLen=32, count=iterations)
+    try:
+        return PBKDF2(password, salt, dkLen=32, count=iterations)
+    except TypeError:
+        return PBKDF2(password, salt, iterations=iterations).read(32)
 
 
 def get_cipher(password, salt):