Hi, attached is my proposed patch to the security service for Unix crypt authentication. This one needs the cryptix32.jar
Please review. I'd like to get this into Turbine-2 (and Fulcrum?). Yes, I know I have to add Apache License and docs to UnixCryptAdaptor and some docs to the libs/README.TXT. It is at the moment just a proposal. Regards Henning Index: turbine-2/conf/TurbineResources.properties =================================================================== RCS file: /cvs/jakarta/turbine-2/conf/TurbineResources.properties,v retrieving revision 1.1.1.1 retrieving revision 1.2 diff -u -r1.1.1.1 -r1.2 --- turbine-2/conf/TurbineResources.properties 14 Dec 2001 12:49:25 -0000 1.1.1.1 +++ turbine-2/conf/TurbineResources.properties 14 Dec 2001 15:11:31 -0000 1.2 @@ -832,6 +832,10 @@ # for encrypting passwords. Check documentation of your JRE for # available algorithms. # +# You can also use "unix" or "crypt" if you want to use standard +# unix crypt(3) encryption which is useful for legacy password +# databases. +# # Default: SHA # Index: turbine-2/src/java/org/apache/turbine/services/security/BaseSecurityService.java =================================================================== RCS file: /cvs/jakarta/turbine-2/src/java/org/apache/turbine/services/security/BaseSecurityService.java,v retrieving revision 1.1.1.1 retrieving revision 1.2 diff -u -r1.1.1.1 -r1.2 --- turbine-2/src/java/org/apache/turbine/services/security/BaseSecurityService.java 14 Dec 2001 12:49:26 -0000 1.1.1.1 +++ turbine-2/src/java/org/apache/turbine/services/security/BaseSecurityService.java + 14 Dec 2001 15:11:31 -0000 1.2 @@ -84,6 +84,8 @@ import java.io.OutputStream; import java.io.ByteArrayOutputStream; +import org.apache.turbine.util.crypt.UnixCryptAdaptor; + /** * This is a common subset of SecurityService implementation. * @@ -100,6 +102,7 @@ * </ul> * * @author <a href="mailto:[EMAIL PROTECTED]">Rafal Krzewski</a> + * @author <a href="mailto:[EMAIL PROTECTED]">Henning P. Schmiedehausen</a> * @version $Id: BaseSecurityService.java,v 1.1.1.1 2001/08/16 05:09:16 jvanzyl Exp $ */ public abstract class BaseSecurityService @@ -142,8 +145,30 @@ * @param password the password to process * @return processed password */ + public String encryptPassword( String password ) { + return encryptPassword( password, null ); + } + + /** + * This method provides client-side encryption of passwords. + * + * If <code>secure.passwords</code> are enabled in TurbineResources, + * the password will be encrypted, if not, it will be returned unchanged. + * The <code>secure.passwords.algorithm</code> property can be used + * to chose which digest algorithm should be used for performing the + * encryption. <code>SHA</code> is used by default. + * + * The used algorithms must be prepared to accept null as a valid parameter for +salt. + * + * @param password the password to process + * @param salt algorithms that needs a salt can provide one here + * @return processed password + */ + + public String encryptPassword( String password, String salt ) + { if(password == null) return null; String secure = getProperties().getProperty( @@ -156,25 +181,50 @@ { try { - MessageDigest md = MessageDigest.getInstance(algorithm); - // We need to use unicode here, to be independent of platform's - // default encoding. Thanks to SGawin for spotting this. - byte[] digest = md.digest(password.getBytes("UTF-8")); - ByteArrayOutputStream bas = new ByteArrayOutputStream(digest.length + digest.length / 3 + 1); - OutputStream encodedStream = MimeUtility.encode(bas, "base64"); - encodedStream.write(digest); - return bas.toString(); + if(algorithm.toLowerCase().equals("unix") || +algorithm.toLowerCase().equals("crypt")) + { + return UnixCryptAdaptor.crypt(password, salt); + } + else + { + MessageDigest md = MessageDigest.getInstance(algorithm); + // We need to use unicode here, to be independent of platform's + // default encoding. Thanks to SGawin for spotting this. + byte[] digest = md.digest(password.getBytes("UTF-8")); + ByteArrayOutputStream bas = new +ByteArrayOutputStream(digest.length + digest.length / 3 + 1); + OutputStream encodedStream = MimeUtility.encode(bas, "base64"); + encodedStream.write(digest); + return bas.toString(); + } } catch (Exception e) { Log.error("Unable to encrypt password."+e.getMessage()); Log.error(e); - + return null; } - } else { + } + else + { return password; } + } + + + /** + * Checks if a supplied password matches the encrypted password + * + * @param checkpw The clear text password supplied by the user + * @param encpw The current, encrypted password + * + * @return true if the password matches, else false + * + */ + + public boolean checkPassword(String checkpw, String encpw) + { + return encryptPassword(checkpw, encpw).equals(encpw); } /** Index: turbine-2/src/java/org/apache/turbine/services/security/SecurityService.java =================================================================== RCS file: /cvs/jakarta/turbine-2/src/java/org/apache/turbine/services/security/SecurityService.java,v retrieving revision 1.1.1.1 retrieving revision 1.2 diff -u -r1.1.1.1 -r1.2 --- turbine-2/src/java/org/apache/turbine/services/security/SecurityService.java 14 Dec 2001 12:49:26 -0000 1.1.1.1 +++ turbine-2/src/java/org/apache/turbine/services/security/SecurityService.java + 14 Dec 2001 15:11:31 -0000 1.2 @@ -89,6 +89,7 @@ * and directory server as the data backend.<br> * * @author <a href="mailto:[EMAIL PROTECTED]">Rafal Krzewski</a> + * @author <a href="mailto:[EMAIL PROTECTED]">Henning P. Schmiedehausen</a> * @version $Id: SecurityService.java,v 1.1.1.1 2001/08/16 05:09:16 jvanzyl Exp $ */ public interface SecurityService @@ -274,6 +275,32 @@ * @return processed password */ public String encryptPassword( String password ); + + /** + * This method provides client-side encryption mechanism for passwords. + * + * This is an utility method that is used by other classes to maintain + * a consistent approach to encrypting password. The behavior of the + * method can be configured in service's properties. + * + * Algorithms that must supply a salt for encryption can use this method to +provide it + * + * @param password the password to process + * @param salt the salt used to encrypt the password + * @return processed password + */ + public String encryptPassword( String password, String salt ); + + /** + * Checks if a supplied password matches the encrypted password + * + * @param checkpw The clear text password supplied by the user + * @param encpw The current, encrypted password + * + * @return true if the password matches, else false + * + */ + public boolean checkPassword(String checkpw, String encpw); /** * Change the password for an User. Index: turbine-2/src/java/org/apache/turbine/services/security/TurbineSecurity.java =================================================================== RCS file: /cvs/jakarta/turbine-2/src/java/org/apache/turbine/services/security/TurbineSecurity.java,v retrieving revision 1.1.1.1 retrieving revision 1.2 diff -u -r1.1.1.1 -r1.2 --- turbine-2/src/java/org/apache/turbine/services/security/TurbineSecurity.java 14 Dec 2001 12:49:26 -0000 1.1.1.1 +++ turbine-2/src/java/org/apache/turbine/services/security/TurbineSecurity.java + 14 Dec 2001 15:11:31 -0000 1.2 @@ -89,6 +89,7 @@ * named 'global' that can be referenced in the code as {@link org.apache.turbine.om.security.Group#GLOBAL_GROUP_NAME}. * * @author <a href="mailto:[EMAIL PROTECTED]">Rafal Krzewski</a> + * @author <a href="mailto:[EMAIL PROTECTED]">Henning P. Schmiedehausen</a> * @version $Id: TurbineSecurity.java,v 1.1.1.1 2001/08/16 05:09:17 jvanzyl Exp $ */ public abstract class TurbineSecurity @@ -124,6 +125,37 @@ return getService().encryptPassword(password); } + /* + * This method provides client-side encryption of passwords. + * + * This is an utility method that is used by other classes to maintain + * a consistent approach to encrypting password. The behavior of the + * method can be configured in service's properties. + * + * @param password the password to process + * @param salt the supplied salt to encrypt the password + * @return processed password + */ + public static String encryptPassword( String password, String salt ) + { + return getService().encryptPassword(password, salt); + } + + /** + * Checks if a supplied password matches the encrypted password + * + * @param checkpw The clear text password supplied by the user + * @param encpw The current, encrypted password + * + * @return true if the password matches, else false + * + */ + + public static boolean checkPassword(String checkpw, String encpw) + { + return getService().checkPassword(checkpw, encpw); + } + /** * Return a Class object representing the system's chosen implementation of * of User interface. Index: turbine-2/src/java/org/apache/turbine/services/security/db/DBUserManager.java =================================================================== RCS file: /cvs/jakarta/turbine-2/src/java/org/apache/turbine/services/security/db/DBUserManager.java,v retrieving revision 1.1.1.1 retrieving revision 1.2 diff -u -r1.1.1.1 -r1.2 --- turbine-2/src/java/org/apache/turbine/services/security/db/DBUserManager.java 14 Dec 2001 12:49:26 -0000 1.1.1.1 +++ turbine-2/src/java/org/apache/turbine/services/security/db/DBUserManager.java + 14 Dec 2001 15:11:31 -0000 1.2 @@ -87,6 +87,7 @@ * @author <a href="mailto:[EMAIL PROTECTED]">Frank Y. Kim</a> * @author <a href="mailto:[EMAIL PROTECTED]">Craig D. Berry</a> * @author <a href="mailto:[EMAIL PROTECTED]">Rafal Krzewski</a> + * @author <a href="mailto:[EMAIL PROTECTED]">Henning P. Schmiedehausen</a> * @version $Id: DBUserManager.java,v 1.1.1.1 2001/08/16 05:09:19 jvanzyl Exp $ */ public class DBUserManager implements UserManager @@ -300,8 +301,14 @@ throw new UnknownEntityException("The account '" + user.getUserName() + "' does not exist"); } - String encrypted = TurbineSecurity.encryptPassword(password); - if(!user.getPassword().equals(encrypted)) + + /* + * Unix crypt needs the existing, encrypted password text as + * salt for checking the supplied password. So we supply it + * into the checkPassword routine + */ + + if(!TurbineSecurity.checkPassword(password, user.getPassword())) { throw new PasswordMismatchException("The passwords do not match"); } @@ -324,13 +331,13 @@ throws PasswordMismatchException, UnknownEntityException, DataBackendException { - String encrypted = TurbineSecurity.encryptPassword(oldPassword); if(!accountExists(user)) { throw new UnknownEntityException("The account '" + user.getUserName() + "' does not exist"); } - if(!user.getPassword().equals(encrypted)) + + if(!TurbineSecurity.checkPassword(oldPassword, user.getPassword())) { throw new PasswordMismatchException( "The supplied old password for '" + user.getUserName() + @@ -389,8 +396,9 @@ throw new EntityExistsException("The account '" + user.getUserName() + "' already exists"); } - String encrypted = TurbineSecurity.encryptPassword(initialPassword); - user.setPassword(encrypted); + + user.setPassword(TurbineSecurity.encryptPassword(initialPassword)); + Criteria criteria = TurbineUserPeer.buildCriteria(user); try { Index: turbine-2/src/java/org/apache/turbine/util/crypt/UnixCryptAdaptor.java =================================================================== RCS file: turbine-2/src/java/org/apache/turbine/util/crypt/UnixCryptAdaptor.java diff -N turbine-2/src/java/org/apache/turbine/util/crypt/UnixCryptAdaptor.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ turbine-2/src/java/org/apache/turbine/util/crypt/UnixCryptAdaptor.java 14 Dec +2001 15:11:31 -0000 1.1 @@ -0,0 +1,32 @@ +package org.apache.turbine.util.crypt; + +import org.apache.turbine.util.Log; + +import cryptix.tools.UnixCrypt; + +public class UnixCryptAdaptor +{ + private static final char[] saltChars = + +("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./".toCharArray()); + + private UnixCryptAdaptor() + { + } + + public static final String crypt(String password, String salt) + throws Exception + { + if(salt == null) + { + java.util.Random randomGenerator = new java.util.Random(); + int numSaltChars = saltChars.length; + + salt = (new StringBuffer()) + .append(saltChars[Math.abs(randomGenerator.nextInt()) % numSaltChars]) + .append(saltChars[Math.abs(randomGenerator.nextInt()) % numSaltChars]) + .toString(); + } + + return new UnixCrypt(salt).crypt(password); + } +} -- Dipl.-Inf. (Univ.) Henning P. Schmiedehausen -- Geschaeftsfuehrer INTERMETA - Gesellschaft fuer Mehrwertdienste mbH [EMAIL PROTECTED] Am Schwabachgrund 22 Fon.: 09131 / 50654-0 [EMAIL PROTECTED] D-91054 Buckenhof Fax.: 09131 / 50654-20 -- To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]> For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>