/* * JBoss, the OpenSource WebOS * * Distributable under LGPL license. * See terms of license at gnu.org. */ package org.jboss.security.auth.spi; import java.security.acl.Group; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.util.Iterator; import java.util.Map.Entry; import java.util.Properties; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; import javax.naming.directory.SearchResult; import javax.naming.ldap.InitialLdapContext; import javax.security.auth.login.LoginException; import org.apache.xerces.impl.dv.util.Base64; import org.jboss.security.SimpleGroup; import org.jboss.security.SimplePrincipal; import org.jboss.security.auth.spi.UsernamePasswordLoginModule; /** * An implementation of LoginModule that authenticates against an LDAP server * using JNDI, based on the configuration properties. *

* The LoginModule options include whatever options your LDAP JNDI provider * supports. Examples of standard property names are: *

*

* The Context.SECURITY_PRINCIPAL is set to the distinguished name of the user * as obtained by the callback handler and the Context.SECURITY_CREDENTIALS * property is either set to the String password or Object credential depending * on the useObjectCredential option. *

* Additional module properties include: *

* A sample login config: *

* *

 * 
 *  
 *   testLdap {
 *   org.jboss.security.auth.spi.LdapLoginModule required
 *   java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
 *   java.naming.provider.url="ldap://ldaphost.jboss.org:1389/"
 *   java.naming.security.authentication=simple
 *   principalDNPrefix=uid=
 *   uidAttributeID=userid
 *   roleAttributeID=roleName
 *   principalDNSuffix=,ou=People,o=jboss.org
 *   rolesCtxDN=cn=JBossSX Tests,ou=Roles,o=jboss.org
 *   };
 *  
 *   testLdap2 {
 *   org.jboss.security.auth.spi.LdapLoginModule required
 *   java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
 *   java.naming.provider.url="ldap://ldaphost.jboss.org:1389/"
 *   java.naming.security.authentication=simple
 *   principalDNPrefix=uid=
 *   uidAttributeID=userid
 *   roleAttributeID=roleName
 *   principalDNSuffix=,ou=People,o=jboss.org
 *   userRolesCtxDNAttributeName=ou=Roles,dc=user1,dc=com
 *   };
 *  
 *   testLdapToActiveDirectory {
 *   org.jboss.security.auth.spi.LdapLoginModule required
 *   java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
 *   java.naming.provider.url="ldap://ldaphost.jboss.org:1389/"
 *   java.naming.security.authentication=simple
 *   rolesCtxDN=cn=Users,dc=ldaphost,dc=jboss,dc=org
 *   uidAttributeID=userPrincipalName
 *   roleAttributeID=memberOf
 *   roleAttributeIsDN=true
 *   roleNameAttributeID=name
 *   };
 * 
 *   testLdapWithExtraLoginUser {
 *   org.jboss.security.auth.spi.AdvancedLdapLoginModule required
 *   java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
 *   java.naming.provider.url="ldap://ldaphost.jboss.org:1389/"
 *   java.naming.security.authentication=simple
 *   java.naming.ldap.derefAliases=always
 *   principalDNPrefix=uid=
 *   principalDNSuffix=,ou=application-users,dc=ldaphost,dc=jboss,dc=org
 *   uidAttributeID=memberUid
 *   roleAttributeID=cn
 *   rolesCtxDN=ou=application-groups,dc=ldaphost,dc=jboss,dc=org
 *   useBindUserToCheckPassword=true        
 *   bindUserDN=uid=jboss-auth,ou=application-users,dc=ldaphost,dc=jboss,dc=org
 *   bindUserPassword=password
 *   };
 *   
 *  
 * 
* * @author Scott.Stark@jboss.org * @author Marcus Redeker (mredeker@web.de) * * @version $Revision: 1.14 $ */ public class AdvancedLdapLoginModule extends UsernamePasswordLoginModule { private static final String USE_OBJECT_CREDENTIAL_OPT = "useObjectCredential"; private static final String PRINCIPAL_DN_PREFIX_OPT = "principalDNPrefix"; private static final String PRINCIPAL_DN_SUFFIX_OPT = "principalDNSuffix"; private static final String ROLES_CTX_DN_OPT = "rolesCtxDN"; private static final String USER_ROLES_CTX_DN_ATTRIBUTE_ID_OPT = "userRolesCtxDNAttributeName"; private static final String UID_ATTRIBUTE_ID_OPT = "uidAttributeID"; private static final String ROLE_ATTRIBUTE_ID_OPT = "roleAttributeID"; private static final String MATCH_ON_USER_DN_OPT = "matchOnUserDN"; private static final String ROLE_ATTRIBUTE_IS_DN_OPT = "roleAttributeIsDN"; private static final String ROLE_NAME_ATTRIBUTE_ID_OPT = "roleNameAttributeID"; private static final String BIND_USER_DN = "bindUserDN"; private static final String BIND_USER_PASSWORD = "bindUserPassword"; private static final String BIND_USER_CHECKS_PASSWORD = "useBindUserToCheckPassword"; public AdvancedLdapLoginModule() { } private transient SimpleGroup userRoles = new SimpleGroup("Roles"); /** * Overriden to return an empty password string as typically one cannot * obtain a user's password. We also override the validatePassword so this * is ok. * * @return and empty password String */ protected String getUsersPassword() throws LoginException { return ""; } /** * Overriden by subclasses to return the Groups that correspond to the to * the role sets assigned to the user. Subclasses should create at least a * Group named "Roles" that contains the roles assigned to the user. A * second common group is "CallerPrincipal" that provides the application * identity of the user rather than the security domain identity. * * @return Group[] containing the sets of roles */ protected Group[] getRoleSets() throws LoginException { Group[] roleSets = { userRoles }; return roleSets; } /** * Validate the inputPassword by creating a ldap InitialContext with the * SECURITY_CREDENTIALS set to the password. * * @param inputPassword * the password to validate. * @param expectedPassword * ignored */ protected boolean validatePassword(String inputPassword, String expectedPassword) { boolean isValid = false; if (inputPassword != null) { // See if this is an empty password that should be disallowed if (inputPassword.length() == 0) { // Check for an allowEmptyPasswords option boolean allowEmptyPasswords = true; String flag = (String) options.get("allowEmptyPasswords"); if (flag != null) allowEmptyPasswords = Boolean.valueOf(flag).booleanValue(); if (allowEmptyPasswords == false) { super.log.trace("Rejecting empty password due to allowEmptyPasswords"); return false; } } try { // Validate the password by trying to create an initial context String username = getUsername(); createLdapInitContext(username, inputPassword); isValid = true; } catch (NamingException e) { super.log.debug("Failed to validate password", e); } } return isValid; } private void createLdapInitContext(String username, Object credential) throws NamingException { Properties env = new Properties(); // Map all option into the JNDI InitialLdapContext env Iterator iter = options.entrySet().iterator(); while (iter.hasNext()) { Entry entry = (Entry) iter.next(); env.put(entry.getKey(), entry.getValue()); } // Set defaults for key values if they are missing String factoryName = env.getProperty(Context.INITIAL_CONTEXT_FACTORY); if (factoryName == null) { factoryName = "com.sun.jndi.ldap.LdapCtxFactory"; env.setProperty(Context.INITIAL_CONTEXT_FACTORY, factoryName); } String authType = env.getProperty(Context.SECURITY_AUTHENTICATION); if (authType == null) env.setProperty(Context.SECURITY_AUTHENTICATION, "simple"); String protocol = env.getProperty(Context.SECURITY_PROTOCOL); String providerURL = (String) options.get(Context.PROVIDER_URL); if (providerURL == null) providerURL = "ldap://localhost:" + ((protocol != null && protocol.equals("ssl")) ? "636" : "389"); String principalDNPrefix = (String) options.get(PRINCIPAL_DN_PREFIX_OPT); if (principalDNPrefix == null) principalDNPrefix = ""; String principalDNSuffix = (String) options.get(PRINCIPAL_DN_SUFFIX_OPT); if (principalDNSuffix == null) principalDNSuffix = ""; String matchType = (String) options.get(MATCH_ON_USER_DN_OPT); boolean matchOnUserDN = Boolean.valueOf(matchType).booleanValue(); String userDN = principalDNPrefix + username + principalDNSuffix; env.setProperty(Context.PROVIDER_URL, providerURL); env.setProperty(Context.SECURITY_PRINCIPAL, userDN); env.put(Context.SECURITY_CREDENTIALS, credential); // Check for an useBindUserToCheckPassword option boolean useBindUserToCheckPassword = false; String flag = (String) options.get(BIND_USER_CHECKS_PASSWORD); if (flag != null) useBindUserToCheckPassword = Boolean.valueOf(flag).booleanValue(); if (useBindUserToCheckPassword) { String bindUserDN = (String) options.get(BIND_USER_DN); String bindUserPassword = (String) options.get(BIND_USER_PASSWORD); env.setProperty(Context.SECURITY_PRINCIPAL, bindUserDN); env.put(Context.SECURITY_CREDENTIALS, bindUserPassword); } super.log.trace("Logging into LDAP server, env=" + env); InitialLdapContext ctx = new InitialLdapContext(env, null); super.log.trace("Logged into LDAP server, " + ctx); /* * If using extra bind user we first need to check the password of the given * identity before retrieving the roles */ if (useBindUserToCheckPassword) { boolean pwOK = false; String[] returnAttribute = { "userPassword" }; Attributes result = ctx.getAttributes(userDN, returnAttribute); Attribute pwAttribute = result.get("userPassword"); byte[] pwBytes = (byte[]) pwAttribute.get(); String sPasswd = new String(pwBytes); if (sPasswd.indexOf("SHA") != -1) { pwOK = checkSHADigest(sPasswd, (String)credential); } else if (sPasswd.indexOf("CRYPT") != -1) { String sStoredPass = sPasswd.substring(7); String sCryptPass = Crypt.crypt(sStoredPass.substring(0,2), (String)credential); pwOK = sStoredPass.equals(sCryptPass); } else if (sPasswd.indexOf("MD5") != -1) { String sStoredPass = sPasswd.substring(5); byte[] mD5Pass = null; try { MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(((String)credential).getBytes()); mD5Pass = digest.digest(); } catch (Exception e) { log.trace("Could not verify MD5 password", e); } pwOK = MessageDigest.isEqual(mD5Pass, Base64.decode(sStoredPass)); } if (!pwOK) { throw new NamingException("Could not verify user password"); } } /* * If a userRolesCtxDNAttributeName was speocified, see if there is a * user specific roles DN. If there is not, the default rolesCtxDN will * be used. */ String rolesCtxDN = (String) options.get(ROLES_CTX_DN_OPT); String userRolesCtxDNAttributeName = (String) options.get(USER_ROLES_CTX_DN_ATTRIBUTE_ID_OPT); if (userRolesCtxDNAttributeName != null) { // Query the indicated attribute for the roles ctx DN to use String[] returnAttribute = { userRolesCtxDNAttributeName }; try { Attributes result = ctx.getAttributes(userDN, returnAttribute); if (result.get(userRolesCtxDNAttributeName) != null) { rolesCtxDN = result.get(userRolesCtxDNAttributeName).get().toString(); super.log.trace("Found user roles context DN: " + rolesCtxDN); } } catch (NamingException e) { super.log.debug("Failed to query userRolesCtxDNAttributeName", e); } } // Search for any roles associated with the user if (rolesCtxDN != null) { String uidAttrName = (String) options.get(UID_ATTRIBUTE_ID_OPT); if (uidAttrName == null) uidAttrName = "uid"; String roleAttrName = (String) options.get(ROLE_ATTRIBUTE_ID_OPT); if (roleAttrName == null) roleAttrName = "roles"; BasicAttributes matchAttrs = new BasicAttributes(true); if (matchOnUserDN == true) matchAttrs.put(uidAttrName, userDN); else matchAttrs.put(uidAttrName, username); String[] roleAttr = { roleAttrName }; // Is user's role attribute a DN or the role name String roleAttributeIsDNOption = (String) options.get(ROLE_ATTRIBUTE_IS_DN_OPT); boolean roleAttributeIsDN = Boolean.valueOf(roleAttributeIsDNOption).booleanValue(); // If user's role attribute is a DN, what is the role's name // attribute // Default to 'name' (Group name attribute in Active Directory) String roleNameAttributeID = (String) options.get(ROLE_NAME_ATTRIBUTE_ID_OPT); if (roleNameAttributeID == null) roleNameAttributeID = "name"; try { NamingEnumeration answer = ctx.search(rolesCtxDN, matchAttrs, roleAttr); while (answer.hasMore()) { SearchResult sr = (SearchResult) answer.next(); Attributes attrs = sr.getAttributes(); Attribute roles = attrs.get(roleAttrName); for (int r = 0; r < roles.size(); r++) { Object value = roles.get(r); String roleName = null; if (roleAttributeIsDN == true) { // Query the roleDN location for the value of // roleNameAttributeID String roleDN = value.toString(); String[] returnAttribute = { roleNameAttributeID }; super.log.trace("Using roleDN: " + roleDN); try { Attributes result = ctx.getAttributes(roleDN, returnAttribute); if (result.get(roleNameAttributeID) != null) { roleName = result.get(roleNameAttributeID).get().toString(); } } catch (NamingException e) { log.trace("Failed to query roleNameAttrName", e); } } else { // The role attribute value is the role name roleName = value.toString(); } if (roleName != null) { try { Principal p = super.createIdentity(roleName); log.trace("Assign user to role " + roleName); userRoles.addMember(p); } catch (Exception e) { log.debug("Failed to create principal: " + roleName, e); } } } } } catch (NamingException e) { log.trace("Failed to locate roles", e); } } // Close the context to release the connection ctx.close(); } public boolean checkSHADigest(String digest, String identity) { MessageDigest sha = null; try { sha = MessageDigest.getInstance("SHA-1"); } catch (java.security.NoSuchAlgorithmException e) { log.trace("Failed to initialize SHA-a MessageDiges", e); return false; } if (digest.regionMatches(true, 0, "{SHA}", 0, 5)) { digest = digest.substring(5); // ignore the label } else if (digest.regionMatches(true, 0, "{SSHA}", 0, 6)) { digest = digest.substring(6); // ignore the label } byte[][] hs = split(Base64.decode(digest), 20); byte[] hash = hs[0]; byte[] salt = hs[1]; sha.reset(); sha.update(identity.getBytes()); sha.update(salt); byte[] pwhash = sha.digest(); boolean valid = true; if (!MessageDigest.isEqual(hash, pwhash)) { valid = false; } return valid; } /** * split a byte array in two * * @param src * byte array to be split * @param n * element at which to split the byte array * @return byte[][] two byte arrays that have been split */ private static byte[][] split(byte[] src, int n) { byte[] l, r; if (src == null || src.length <= n) { l = src; r = new byte[0]; } else { l = new byte[n]; r = new byte[src.length - n]; System.arraycopy(src, 0, l, 0, n); System.arraycopy(src, n, r, 0, r.length); } byte[][] lr = { l, r }; return lr; } /** * Crypt is the class, that implements UFC crypt (ultra fast crypt implementation ) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * JAVA version. * * @author Michael Glad (glad@daimi.aau.dk) * @author Pawel Veselov (vps@phoenix.math.spbu.ru) */ public static class Crypt { private static Object lock = new Object(); /** * Do 32 but permutation and E selection * * The first index is the byte number in the 32 bit value to be permuted * - second - is the value of this byte * - third - selects the two 32 bit value * * The table is used and generated internally in initDes() to speed it up. */ private static long ePerm32Tab[][][] = new long[4][256][2]; private static int pc1_1 = 128; private static int pc1_2 = 2; private static int pc1_3 = 8; private static int pc2_1 = 128; private static int pc2_2 = 8; /** * doPc1: perform pc1 permutation in the key schedule generation. * * The first index is the byte number in the 8 byte ASCII key * - second - - the two 28 bits halfs on the result * - third - selects the 7 bits actually used of each byte * * The result is kept with 28 bit per 32 bit with the 4 most significant * bits zero. */ private static long doPc1[] = new long[pc1_1*pc1_2*pc1_3]; /** * doPc2: perform pc2 permutation in the key schedule generation. * * The first index is the septet number in the two 28 bit intermediate values * - second - - - speted values * * Knowledge of the structure of the pc2 permutation is used. * * The result is kept with 28 bit per 32 bit with the 4 most significant * bits zero. */ private static long doPc2[] = new long[pc2_1*pc2_2]; /** * efp: undo an extra e selection and do final * permutation gibing the DES result. * * Invoked 6 bit a time on two 48 bit vales * giving two 32 bit longs. */ private static long efp[][][] = new long[16][64][2]; private static int byteMask[]={ 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; /** * Permutation done once on the 56 bit * key derived from the original 8 byte ASCII key. */ private static int pc1[] = { 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 }; /** * How much to rotate each 28 bit half of the pc1 permutated * 56 bit key before using pc2 to give i' key. */ private static int rots[] = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 }; /** * Permutation giving the key of the i' DES round. */ private static int pc2[] = { 14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32 }; /** * The E expansion table wich selects * bits from the 32 bit intermediate result. */ private static int esel[] = { 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1 }; private static int eInverse[] = new int[64]; /** * Permutation done on the result of sbox lookups. */ private static int perm32[] = { 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 }; /** * The sboxes. */ private static int sbox[][][] = { { { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7 }, { 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8 }, { 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0 }, { 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 }}, { { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10 }, { 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5 }, { 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15 }, { 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 }}, { { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8 }, { 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1 }, { 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7 }, { 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 }}, { { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15 }, { 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9 }, { 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4 }, { 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 }}, { { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9 }, { 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6 }, { 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14 }, { 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 }}, { { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11 }, { 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8 }, { 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6 }, { 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 }}, { { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1 }, { 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6 }, { 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2 }, { 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 }}, { { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7 }, { 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2 }, { 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8 }, { 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 }} }; /** * This is the initial permutation matrix */ private static int initialPerm[] = { 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 }; /** * This is final permutation matrix. */ private static int finalPerm[] = { 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25 }; private static long longMask[] = { 0x80000000, 0x40000000, 0x20000000, 0x10000000, 0x08000000, 0x04000000, 0x02000000, 0x01000000, 0x00800000, 0x00400000, 0x00200000, 0x00100000, 0x00080000, 0x00040000, 0x00020000, 0x00010000, 0x00008000, 0x00004000, 0x00002000, 0x00001000, 0x00000800, 0x00000400, 0x00000200, 0x00000100, 0x00000080, 0x00000040, 0x00000020, 0x00000010, 0x00000008, 0x00000004, 0x00000002, 0x00000001 }; /** * The 16 DES keys in BITMASK format */ private static long ufcKeytab[] = new long[16]; /** * sb arrays: * * Workhorses of the inner loop of the DES implementation. * The do sbox lookup, shifting of this value, 32 bit * permutation and E permutation for the next round. * * Kept in 'BITMASK' format. */ private static long sb[][] = new long[4][4096]; private static boolean inited = false; private static byte currentSalt[] = new byte[2]; private static int currentSaltbits; private static int direction = 0; /** * This method test crypt. * * @param show if it is true, print test results to err stream * @return true, if test succeeded, false if failed. */ public static boolean selfTest(boolean show) { String required = "arlEKn0OzVJn."; if (inited) { if (show) { System.err.println("Warning: initialization already done."); } } else { initDes(); } String cryptTest = crypt("ar","foob"); boolean failed = !required.equals(cryptTest); if (failed) { if (show) { System.err.println("TEST FAILED! Got:"+cryptTest+" Wanted"+ required); } return false; } else { if (show) { System.err.println("Test succeeded"); } } return true; } /** * crypt function. * * @param salt two byte array with salt. * @param original array, maximum 8 bytes, string to encrypt */ public static String crypt(byte[] salt, byte[] original) { String originalString = new String(original); return crypt(salt, originalString); } /** * crypt function. * * @param salt String, maximum length is 2 with salt. * @param original String, maximum 8 bytes, string to encrypt */ public static String crypt(String salt, String original) { return crypt(salt.getBytes(), original); } /** * crypt function * * @param salt Two bytes array with salt * @param original String, maximum 8 characters, string to encrypt */ public static String crypt(byte[] salt, String original) { if (!inited) initDes(); String res; synchronized (lock) { byte ktab[] = new byte[9]; for (int i=0; i<9; i++) { ktab[i]=0; } setupSalt(salt); int orig=original.length(); for (int i=0; i<(orig>8?9:orig); i++) { ktab[i] = (byte)original.charAt(i); } ufcMkKeytab(ktab); int s[] = ufcDoit(0,0,0,0,25); res = outputConversion(s[0], s[1], salt); } return res; } /** * Crypt only: convert from 64 bit to 11 bit ASCII */ private static String outputConversion(int v1, int v2, byte[] salt) { char outbuf[] = new char[13]; outbuf[0] = (char)salt[0]; outbuf[1] = (char)((salt[1]!=0)?salt[1]:salt[0]); for (int i = 0; i < 5; i++) { outbuf[i+2] = binToAscii((v1 >>> (26 - 6 * i)) & 0x3f); } int s = (v2 & 0xf) << 2; v2 = (v2 >>> 2) | ((v1 & 0x3) << 30); for (int i = 5; i < 10; i++) { outbuf[i+2] = binToAscii((v2>>>(56 - 6 * i)) & 0x3f); } outbuf[12] = binToAscii(s); return new String(outbuf); } /** * Lookup a 6 bit value in sbox */ private static int sLookup(int i, int s) { return sbox[i] [((s>>>4)&0x02)|(s&0x1)] [(s>>>1)&0xf]; } /** * Function to set a bit (0..23) */ private static long bitMask(long i) { long l = (1<<(11-(i)%12+3)); long r = ((i)<12?16:0); return (l<=0; j--) { if ((j & mask1) != 0) { ePerm32Tab[comesFrom/8][j][bit/24] |= bitMask(bit%24); } } } for (int sg = 0; sg<4; sg++) { for (int j1 = 0; j1 < 64; j1++) { int s1 = sLookup(2*sg, j1); for (int j2 = 0; j2 < 64; j2++) { int s2 = sLookup(2*sg+1,j2); int toPermute = ((s1 << 4) | s2) << (24 - 8 * sg); int inx = ((j1<<6)|j2); sb[sg][inx] =(ePerm32Tab[0][(toPermute>>>24)&0xff][0]<<32)| ePerm32Tab[0][(toPermute>>>24)&0xff][1]; sb[sg][inx]|=(ePerm32Tab[1][(toPermute>>>16)&0xff][0]<<32)| ePerm32Tab[1][(toPermute>>>16)&0xff][1]; sb[sg][inx]|=(ePerm32Tab[2][(toPermute>>> 8)&0xff][0]<<32)| ePerm32Tab[2][(toPermute>>> 8)&0xff][1]; sb[sg][inx]|=(ePerm32Tab[3][(toPermute )&0xff][0]<<32)| ePerm32Tab[3][(toPermute )&0xff][1]; } } } for (int bit=47; bit>=0; bit--) { eInverse[esel[bit] - 1 ] = bit; eInverse[esel[bit] - 1 + 32] = bit + 48; } for (int i=0; i < 16; i++) for (int j=0; j < 64 ; j++) for (int k=0; k < 2; k++) efp[i][j][k] = 0; for (int bit=0; bit < 64; bit++) { int oLong = bit / 32; int oBit = bit % 32; int comesFromFBit = finalPerm[bit] - 1; int comesFromEBit = eInverse[comesFromFBit]; int comesFromWord = comesFromEBit / 6; int bitWithinWord = comesFromEBit % 6; long mask1 = longMask[bitWithinWord + 26]; long mask2 = longMask[oBit]; for (int wordValue = 63; wordValue>=0; wordValue--) { if ((wordValue & mask1) != 0) { efp[comesFromWord][wordValue][oLong] |= mask2; } } } } } /** * Setup the unit for a new salt. * Hopefully we'll not see a new salt in each crypt call. */ private static void setupSalt(byte[] s) { if ((s[0] == currentSalt[0]) && (s[1] == currentSalt[1])) return; currentSalt[0] = s[0]; currentSalt[1] = s[1]; int saltbits = 0; for (int i=0; i<2; i++) { long c = asciiToBin(s[i]); if (c<0 || c>63) { c = 0; } for (int j=0; j < 6; j++) { if (((c>>>j)&0x1) != 0) { saltbits |= bitMask(6*i+j); } } } shuffleSb(sb[0], currentSaltbits ^ saltbits); shuffleSb(sb[1], currentSaltbits ^ saltbits); shuffleSb(sb[2], currentSaltbits ^ saltbits); shuffleSb(sb[3], currentSaltbits ^ saltbits); currentSaltbits = saltbits; } /** * Process the elements of the sb table permuting the * bits swapped in the expansion by the current salt. */ private static void shuffleSb(long[] k, int saltbits) { int i = 0; for (int j=4095; j>=0; j--) { long x=((k[i]>>>32) ^ k[i]) & saltbits; k[i++] ^= (x << 32) | x; } } private static long asciiToBin(byte c) { return (c>='a'?(c-59):c>='A'?(c-53):c-'.'); } private static char binToAscii(long c) { return (char)(c>=38?(c-38+'a'):c>=12?(c-12+'A'):c+'.'); } private static void ufcMkKeytab(byte[] keyInd) { int k1 = 0; int k2 = 0; int key = 0; long v1 = 0; long v2 = 0; for (int i=7; i>=0; i--) { v1 |= doPc1[(int)(k1 + (keyInd[key ] & 0x7f))]; k1 += 128; v2 |= doPc1[(int)(k1 + (keyInd[key++] & 0x7f))]; k1 += 128; } for (int i=0; i< 16; i++) { k1 = 0; v1 = (v1 << rots[i]) | (v1 >>> (28 - rots[i])); long v; v = doPc2[k1+(int)((v1 >>> 21) & 0x7f)]; k1 += 128; v |= doPc2[k1+(int)((v1 >>> 14) & 0x7f)]; k1 += 128; v |= doPc2[k1+(int)((v1 >>> 7) & 0x7f)]; k1 += 128; v |= doPc2[k1+(int)((v1 ) & 0x7f)]; k1 += 128; v <<= 32; v2 = (v2 << rots[i]) | (v2 >>> (28 - rots[i])); v |= doPc2[k1+(int)((v2 >>> 21) & 0x7f)]; k1 += 128; v |= doPc2[k1+(int)((v2 >>> 14) & 0x7f)]; k1 += 128; v |= doPc2[k1+(int)((v2 >>> 7) & 0x7f)]; k1 += 128; v |= doPc2[k1+(int)((v2 ) & 0x7f)]; ufcKeytab[k2] = v; k2++; } direction = 0; } private static long sba(long[] sb, long v) { if (((v>>>3)<<3) != v) { new Exception("FATAL : non aligned V:"+v).printStackTrace(); } return sb[(int)(v>>>3)]; } /** * Undo an extra E selection and do final permutations. */ private static int[] ufcDoFinalPerm(int l1, int l2, int r1, int r2) { int v1,v2,x; int ary[] = new int[2]; x = (l1 ^ l2) & currentSaltbits; l1 ^= x; l2 ^= x; x = (r1 ^ r2) & currentSaltbits; r1 ^= x; r2 ^= x; v1=0; v2=0; l1 >>>= 3; l2 >>>= 3; r1 >>>= 3; r2 >>>= 3; v1 |= efp[15][ r2 & 0x3f][0]; v2 |= efp[15][ r2 & 0x3f][1]; v1 |= efp[14][(r2 >>>= 6) & 0x3f][0]; v2 |= efp[14][ r2 & 0x3f][1]; v1 |= efp[13][(r2 >>>= 10) & 0x3f][0]; v2 |= efp[13][ r2 & 0x3f][1]; v1 |= efp[12][(r2 >>>= 6) & 0x3f][0]; v2 |= efp[12][ r2 & 0x3f][1]; v1 |= efp[11][ r1 & 0x3f][0]; v2 |= efp[11][ r1 & 0x3f][1]; v1 |= efp[10][(r1 >>>= 6) & 0x3f][0]; v2 |= efp[10][ r1 & 0x3f][1]; v1 |= efp[ 9][(r1 >>>= 10) & 0x3f][0]; v2 |= efp[ 9][ r1 & 0x3f][1]; v1 |= efp[ 8][(r1 >>>= 6) & 0x3f][0]; v2 |= efp[ 8][ r1 & 0x3f][1]; v1 |= efp[ 7][ l2 & 0x3f][0]; v2 |= efp[ 7][ l2 & 0x3f][1]; v1 |= efp[ 6][(l2 >>>= 6) & 0x3f][0]; v2 |= efp[ 6][ l2 & 0x3f][1]; v1 |= efp[ 5][(l2 >>>= 10) & 0x3f][0]; v2 |= efp[ 5][ l2 & 0x3f][1]; v1 |= efp[ 4][(l2 >>>= 6) & 0x3f][0]; v2 |= efp[ 4][ l2 & 0x3f][1]; v1 |= efp[ 3][ l1 & 0x3f][0]; v2 |= efp[ 3][ l1 & 0x3f][1]; v1 |= efp[ 2][(l1 >>>= 6) & 0x3f][0]; v2 |= efp[ 2][ l1 & 0x3f][1]; v1 |= efp[ 1][(l1 >>>= 10) & 0x3f][0]; v2 |= efp[ 1][ l1 & 0x3f][1]; v1 |= efp[ 0][(l1 >>>= 6) & 0x3f][0]; v2 |= efp[ 0][ l1 & 0x3f][1]; ary[0] = v1; ary[1] = v2; return ary; } private static int[] ufcDoit(int l1, int l2, int r1, int r2, int itr) { long l = (((long)l1) << 32) | ((long)l2); long r = (((long)r1) << 32) | ((long)r2); while (itr-- != 0) { int k = 0; for (int i=7; i>=0; i--) { long s = ufcKeytab[k++] ^ r; l ^= sba(sb[3], (s >>> 0) & 0xffff); l ^= sba(sb[2], (s >>> 16) & 0xffff); l ^= sba(sb[1], (s >>> 32) & 0xffff); l ^= sba(sb[0], (s >>> 48) & 0xffff); s = ufcKeytab[k++] ^ l; r ^= sba(sb[3], (s >>> 0) & 0xffff); r ^= sba(sb[2], (s >>> 16) & 0xffff); r ^= sba(sb[1], (s >>> 32) & 0xffff); r ^= sba(sb[0], (s >>> 48) & 0xffff); } long s = l; l = r; r = s; } l1 = (int)(l >>> 32); l2 = (int)(l & 0xffffffff); r1 = (int)(r >>> 32); r2 = (int)(r & 0xffffffff); return ufcDoFinalPerm(l1,l2,r1,r2); } } }