From 2f838d7a55795db6cbb7df8a5dd7826c99c33834 Mon Sep 17 00:00:00 2001 From: Kazuhiko Shiozaki <kazuhiko@nexedi.com> Date: Mon, 28 Feb 2011 16:35:27 +0000 Subject: [PATCH] introduce AES cipher for much stronger encryption. the old implementation (Cesar cipher) is still kept for backward compatibility, but it is recommended to switch to AES cipher. git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@43825 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5Security/ERP5KeyAuthPlugin.py | 134 ++++++++++++------ .../www/ERP5Security_addERP5KeyAuthPlugin.zpt | 14 ++ .../ERP5Security_editERP5KeyAuthPlugin.zpt | 15 ++ 3 files changed, 117 insertions(+), 46 deletions(-) diff --git a/product/ERP5Security/ERP5KeyAuthPlugin.py b/product/ERP5Security/ERP5KeyAuthPlugin.py index b9ee13625c..c3b94ecb70 100644 --- a/product/ERP5Security/ERP5KeyAuthPlugin.py +++ b/product/ERP5Security/ERP5KeyAuthPlugin.py @@ -55,6 +55,70 @@ from Products.ERP5Security.ERP5UserManager import ERP5UserManager,\ SUPER_USER,\ _AuthenticationFailure +from Crypto.Cipher import AES +from base64 import b16decode, b16encode + +class AESCipher: + mode = AES.MODE_CFB + + def __init__(self, encryption_key): + # AES key must be either 16, 24, or 32 bytes long + self.encryption_key = encryption_key.ljust(32)[:32] + + def encrypt(self, login): + encryptor = AES.new(self.encryption_key, self.mode) + return b16encode(encryptor.encrypt(login.ljust(((len(login)-1)/16+1)*16))) + + def decrypt(self, crypted_login): + decryptor = AES.new(self.encryption_key, self.mode) + return decryptor.decrypt(b16decode(crypted_login)).rstrip() + +# This cipher is weak. Do not use. +class CesarCipher: + block_length = 3 + + def __init__(self, encryption_key): + self.encryption_key = encryption_key + self.encrypted_key = self.transformKey(self.encryption_key); + + def transformKey(self,key): + """Transform the key to number for encryption""" + encrypt_key = [] + for letter in key: + encrypt_key.append(ord(letter)) + return encrypt_key + + def encrypt(self, login): + crypted_login = '' + key_length = len(self.encrypted_key) + for i in range(0,len(login)): + delta = i % key_length + crypted_letter = str(ord(login[i]) + self.encrypted_key[delta]) + #ord is the inverse of chr() for 8-bit (1111 1111 = 256) + #so crypted_letter max id 512 + #we ajust lenght to be able to decrypt by block + crypted_letter = crypted_letter.rjust(self.block_length, '0') + crypted_login += crypted_letter + return crypted_login + + def decrypt(self, crypted_login): + login = '' + #check lenght of the string + clogin_length = len(crypted_login) + if clogin_length % self.block_length != 0: + raise ValueError, "Lenght is not good" + #decrypt block per block + position = 0 + key_length = len(self.encrypted_key) + for block in range(0, clogin_length, self.block_length): + delta = position % key_length + crypted_letter = crypted_login[block:block + self.block_length] + crypted_letter = int(crypted_letter) - self.encrypted_key[delta] + letter = chr(crypted_letter) + login += letter + position += 1 + return login + class ILoginEncryptionPlugin(Interface): """Contract for possible ERP5 Key Auth Plugin""" @@ -70,12 +134,14 @@ manage_addERP5KeyAuthPluginForm = PageTemplateFile( 'www/ERP5Security_addERP5KeyAuthPlugin', globals(), __name__='manage_addERP5KeyAuthPluginForm') -def addERP5KeyAuthPlugin(dispatcher, id,title=None,\ - encryption_key='',cookie_name='',\ +def addERP5KeyAuthPlugin(dispatcher, id, title=None,\ + encryption_key='', cipher='AES', cookie_name='',\ default_cookie_name='',REQUEST=None): """ Add a ERP5KeyAuthPlugin to a Pluggable Auth Service. """ - plugin = ERP5KeyAuthPlugin( id, title,encryption_key,cookie_name,default_cookie_name) + plugin = ERP5KeyAuthPlugin(id=id, title=title, encryption_key=encryption_key, + cipher=cipher, cookie_name=cookie_name, + default_cookie_name=default_cookie_name) dispatcher._setObject(plugin.getId(), plugin) if REQUEST is not None: @@ -103,11 +169,9 @@ class ERP5KeyAuthPlugin(ERP5UserManager, CookieAuthHelper): meta_type = "ERP5 Key Authentication" login_path = 'login_form' security = ClassSecurityInfo() - block_length = 3 cookie_name = "__ac_key" default_cookie_name = "__ac" encryption_key = '' - encrypted_key = '' manage_options = ( ( { 'label': 'Edit', 'action': 'manage_editERP5KeyAuthPluginForm', } @@ -127,7 +191,8 @@ class ERP5KeyAuthPlugin(ERP5UserManager, CookieAuthHelper): + CookieAuthHelper._properties[:] ) - def __init__(self, id, title=None, encryption_key='', cookie_name='', default_cookie_name=''): + def __init__(self, id, title=None, encryption_key='', cipher='AES', + cookie_name='', default_cookie_name=''): #Check parameters if cookie_name is None or cookie_name == '': cookie_name = id @@ -142,56 +207,27 @@ class ERP5KeyAuthPlugin(ERP5UserManager, CookieAuthHelper): self.cookie_name = cookie_name self.default_cookie_name = default_cookie_name self.encryption_key = encryption_key - self.encrypted_key = self.transformKey(self.encryption_key); + self.cipher = cipher + def _getCipher(self): + # If self.cipher does not exist, we use CesarCipher only for + # backward compatibility. + return getattr(self, 'cipher', 'Cesar') ################################ # ILoginEncryptionPlugin # ################################ - security.declarePrivate('transformKey') - def transformKey(self,key): - """Transform the key to number for encryption""" - encrypt_key = [] - for letter in key: - encrypt_key.append(ord(letter)) - return encrypt_key - security.declarePublic('encrypt') def encrypt(self,login): """Encrypt the login""" - crypted_login = '' - key_length = len(self.encrypted_key) - for i in range(0,len(login)): - delta = i % key_length - crypted_letter = str(ord(login[i]) + self.encrypted_key[delta]) - #ord is the inverse of chr() for 8-bit (1111 1111 = 256) - #so crypted_letter max id 512 - #we ajust lenght to be able to decrypt by block - crypted_letter = crypted_letter.rjust(self.block_length, '0') - crypted_login += crypted_letter - - return crypted_login + cipher = globals()['%sCipher' % self._getCipher()](self.encryption_key) + return cipher.encrypt(login) security.declarePrivate('decrypt') def decrypt(self, crypted_login): """Decrypt string and return the login""" - login = '' - #check lenght of the string - clogin_length = len(crypted_login) - if clogin_length % self.block_length != 0: - raise ValueError, "Lenght is not good" - - #decrypt block per block - position = 0 - key_length = len(self.encrypted_key) - for block in range(0, clogin_length, self.block_length): - delta = position % key_length - crypted_letter = crypted_login[block:block + self.block_length] - crypted_letter = int(crypted_letter) - self.encrypted_key[delta] - letter = chr(crypted_letter) - login += letter - position += 1 - return login + cipher = globals()['%sCipher' % self._getCipher()](self.encryption_key) + return cipher.decrypt(crypted_login) #################################### #ILoginPasswordHostExtractionPlugin# @@ -360,7 +396,8 @@ class ERP5KeyAuthPlugin(ERP5UserManager, CookieAuthHelper): __name__='manage_editERP5KeyAuthPluginForm' ) security.declareProtected( ManageUsers, 'manage_editKeyAuthPlugin' ) - def manage_editKeyAuthPlugin(self, encryption_key,cookie_name,default_cookie_name, RESPONSE=None): + def manage_editKeyAuthPlugin(self, encryption_key, cipher, cookie_name, + default_cookie_name, RESPONSE=None): """Edit the object""" error_message = '' @@ -373,7 +410,12 @@ class ERP5KeyAuthPlugin(ERP5UserManager, CookieAuthHelper): error_message += 'Invalid key value ' else: self.encryption_key = encryption_key - self.encrypted_key = self.transformKey(self.encryption_key); + + #Save cipher + if cipher == '' or cipher is None: + error_message += 'Invalid cipher value ' + else: + self.cipher = cipher #Save cookie name if cookie_name == '' or cookie_name is None: diff --git a/product/ERP5Security/www/ERP5Security_addERP5KeyAuthPlugin.zpt b/product/ERP5Security/www/ERP5Security_addERP5KeyAuthPlugin.zpt index a9b5f761ca..da2a3d9662 100644 --- a/product/ERP5Security/www/ERP5Security_addERP5KeyAuthPlugin.zpt +++ b/product/ERP5Security/www/ERP5Security_addERP5KeyAuthPlugin.zpt @@ -36,6 +36,20 @@ <input type="text" name="encryption_key" size="40" /> </td> </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Cipher + </div> + </td> + <td align="left" valign="top"> + <select name="cipher"> + <tal:block tal:repeat="c python:[('AES', 'AES'), ('Cesar', 'Cesar (DO NOT USE)')]"> + <option value="value" tal:attributes="value python:c[0]" tal:content="python:c[1]" /> + </tal:block> + </select> + </td> + </tr> <tr> <td align="left" valign="top"> <div class="form-label"> diff --git a/product/ERP5Security/www/ERP5Security_editERP5KeyAuthPlugin.zpt b/product/ERP5Security/www/ERP5Security_editERP5KeyAuthPlugin.zpt index 53f9c39489..b4727cac77 100644 --- a/product/ERP5Security/www/ERP5Security_editERP5KeyAuthPlugin.zpt +++ b/product/ERP5Security/www/ERP5Security_editERP5KeyAuthPlugin.zpt @@ -8,6 +8,7 @@ <form action="manage_editKeyAuthPlugin" method="POST"> <table tal:define="encryption_key request/encryption_key|context/encryption_key|string:; + cipher request/cipher|context/cipher|string:Cesar; default_cookie_name request/default_cookie_name|context/default_cookie_name|string:; cookie_name request/cookie_name|context/cookie_name|string:;"> @@ -18,6 +19,20 @@ tal:attributes="value encryption_key;" /> </td> </tr> +<tr> + <td align="left" valign="top"> + <div class="form-label"> + Cipher + </div> + </td> + <td align="left" valign="top"> + <select name="cipher"> + <tal:block tal:repeat="c python:[('AES', 'AES'), ('Cesar', 'Cesar (DO NOT USE)')]"> + <option value="value" tal:attributes="value python:c[0]; selected python:c[0]==cipher" tal:content="python:c[1]" /> + </tal:block> + </select> + </td> +</tr> <tr> <td> Cookie Name </td> <td> -- 2.30.9