Java tutorial
/** * Copyright (c) 2001-2012 "Redbasin Networks, INC" [http://redbasin.org] * * This file is part of Redbasin OpenDocShare community project. * * Redbasin OpenDocShare is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ldap; import ldap.ActiveLogin; import util.LdapConstants; import util.LdapUtil; import util.Base64; import util.JCrypt; import javax.naming.*; import javax.naming.directory.*; import java.util.Hashtable; import java.security.MessageDigest; import java.security.SecureRandom; import java.io.UnsupportedEncodingException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * (c) Chris Betts: Groupmind Project */ public class ActiveLoginImpl implements ActiveLogin { protected final Log logger = LogFactory.getLog(getClass()); public static boolean debug = false; public static boolean verbose = false; DirContext context; // this is the link to the LDAP directory private SecureRandom random; public ActiveLoginImpl() { random = new SecureRandom(); } public ActiveLoginImpl(boolean codeDebug, boolean verboseDebugTrace) throws NamingException { debug = codeDebug; verbose = verboseDebugTrace; /* try { //context = setupJNDIConnection(Config.DIRECTORY_URL, Config.DIRECTORY_ADMIN, Config.DIRECTORY_PWD, verboseDebugTrace); context = setupJNDIConnection(LdapConstants.ldapDirectoryUrl,LdapConstants.ldapDirectoryAdmin, LdapConstants.ldapDirectoryPwd, verboseDebugTrace); } catch (AuthenticationException e) { //logger.info("There was an error establishing the 'admin' connection to the directory"); //logger.info("The directory rejected the administration credentials:"); //logger.info(" user: " + Config.DIRECTORY_ADMIN); //logger.info(" user: " + LdapConstants.ldapDirectoryAdmin); //logger.info(" pwd: " + Config.DIRECTORY_PWD + "\n" + e.getMessage()); //logger.info(" pwd: " + LdapConstants.ldapDirectoryPwd + "\n" + e.getMessage()); //e.printStackTrace(); //System.exit(-1); throw e; } catch (Exception e) { //logger.info("There was an error establishing the 'admin' connection to the directory"); //logger.info(" Examine the stack trace below for details, including the LDAP error message" + e.getMessage()); //e.printStackTrace(); //System.exit(-1); } */ } public DirContext getContext() { if (context == null) { logger.info("context is null or not initialized"); } return context; } /** * Returns whether this user is listed in the admin users role * * @param login * @return * @throws Exception */ public boolean isAdmin(String login, DirContext context, String DN) throws Exception { NamingEnumeration result = null; String[] returnAttributes = new String[] { "uniqueMember" }; /* specify search constraints to search subtree */ SearchControls constraints = new SearchControls(); constraints.setSearchScope(SearchControls.OBJECT_SCOPE); constraints.setCountLimit(0); constraints.setTimeLimit(0); constraints.setReturningAttributes(returnAttributes); /* Entry user = null; try { user = searcher.getUser(LdapConstants.ldapAttrLogin, login, context); } catch (NamingException e) { throw new LdapException("getUser NamingException" + e.getMessage(), e); } String DN = null; if (user == null) { logger.info("USER DOES NOT EXIST"); return false; } else { DN = user.getName().toString(); if (DN != null) { logger.info("DN = " + DN); } } */ //result = context.search(LdapConstants.ldapAdminRoleDn, "(uniqueMember="+getUserDN(login)+")", constraints); result = context.search(LdapConstants.ldapAdminRoleDn, "(uniqueMember=" + DN + ")", constraints); if (result.hasMore()) { if (debug) { SearchResult sResult = (SearchResult) result.next(); logger.info("Read Admin Roles Object with members: " + sResult.getAttributes().toString()); } return true; } else if (debug) logger.info("Failed to find admin object with member " + DN); return false; } /** * This updates the UserAccount. * It requires at a minimum a name; * and optionally any other attributes. * * Note that this will REPLACE any attributes passed, deleting any existing values * for the specified attribute (e.g. if the attribute is userPassword, the old userPassword will * be discarded, rather than there being two userPasswords in the entry). * * Modifying the naming attribute will probably result in an error (depending on the directory). * * @param account * @throws Exception */ public void updateAccount(UserAccount account, DirContext context, String userDN) throws Exception { //if (account.get(Config.USER_NAMING_ATT) == null) if (account.get(LdapConstants.ldapDnAttrType) == null) throw new NamingException("UpdateAccount(), UserAccount has no naming Attribute"); // should not be used //logger.info("Updating: \n" + account.getUserDN() + "\n" + account.toString()); logger.info("Updating: \n" + userDN + "\n" + account.toString()); // remove the naming attribute from the account before adding Attributes atts = copyAttributes(account); // create a local copy //atts.remove(Config.USER_NAMING_ATT); // we can't modify the naming attribute this way, so don't try... //atts.remove(LdapConstants.ldapAttrUid); // we can't modify the naming attribute this way, so don't try... atts.remove(LdapConstants.ldapDnAttrType); // we can't modify the naming attribute this way, so don't try... atts = hashPasswordAttribute(atts); // context.modifyAttributes(account.getUserDN(), DirContext.REPLACE_ATTRIBUTE, atts); context.modifyAttributes(userDN, DirContext.REPLACE_ATTRIBUTE, atts); } /** * This deletes a user account. It requires a user naming attribute * (usually the uid). * <p/> * For consistancy, this should use UserAccount instead of a String login. * * @param login * @throws Exception */ public void deleteAccount(String login, DirContext context, String userDN) throws Exception { //String dn = getUserDN(login); context.destroySubcontext(userDN); } /** * This adds a new user. It requires at the minimum a name, it should also * usually have a surname and a password at a minimum. * * @param account * @throws Exception */ public void addAccount(UserAccount account, DirContext context, String userBaseDN) throws Exception { // set some default values for the user entry if they haven't been manually added. //if (account.get(Config.USER_NAMING_ATT) == null) if (account.get(LdapConstants.ldapAttrCn) == null) throw new NamingException("addAccount(), UserAccount has no naming Attribute"); if (account.get(LdapConstants.ldapObjectClass) == null) { //Attribute oc = new BasicAttribute("objectClass"); Attribute oc = new BasicAttribute(LdapConstants.ldapObjectClass); if (LdapConstants.ldapObjectClassEmployeeEnable) { //oc.add("employee"); oc.add(LdapConstants.ldapObjectClassEmployee); } //old redbasin stuff /* if (LdapConstants.ldapAttrTopEnable) { oc.add(LdapConstants.ldapAttrTop); } if (LdapConstants.ldapAttrPersonEnable) { oc.add(LdapConstants.ldapAttrPerson); } if (LdapConstants.ldapAttrOrgPersonEnable) { oc.add(LdapConstants.ldapAttrOrgPerson); } if (LdapConstants.ldapAttrInetOrgPersonEnable) { oc.add(LdapConstants.ldapAttrInetOrgPerson); } */ account.put(oc); } /* made changes */ /* if (account.get("cn") == null) account.put("cn", account.getUserID()); if (account.get("sn") == null) account.put("sn", "xxx"); // put in default value for required attribute */ if (account.get(LdapConstants.ldapAttrCn) == null) account.put(LdapConstants.ldapAttrCn, account.getUserID()); if (account.get(LdapConstants.ldapAttrSn) == null) account.put(LdapConstants.ldapAttrSn, "xxx"); // put in default value for required attribute //logger.info("ADDING: \n" + account.getUserDN() + "\n" + account.toString()); logger.info("ADDING: \n" + userBaseDN + "\n" + account.toString()); /** * deal with the password adding later */ /* Attributes attributes = copyAttributes(account); UserAccount myaccount = hashPasswordAttribute(attributes); */ // use this only when we add the user //context.createSubcontext(account.getUserDN(), account); context.createSubcontext(userBaseDN, account); } /** * This accepts a partial (?) UserAccount, that must have at least a * name, reads the full entry from the directory, and displays the result. * */ //public void listAccount(UserAccount account) throws Exception //{ //} /* UTILITY METHODS * * These do the actual work of talking to the directory */ /** * Active Directory does not allow 'compare' ldap operations against passwords, so we either * use AD specific code (bad) or do a fake bind request (slow). This uses the fake bind request; * for high performance enterprise apps a different approach is necessary (but enterprise apps would not * use AD, so it's a moot point). * * @param login * @param password * @return whether the user login and password are valid. * @throws NamingException */ public boolean testBind(String login, String password, String userDN) throws NamingException { //String userDN = getUserDN(login); try { logger.info("Rebinding as user to test password"); //setupJNDIConnection(Config.DIRECTORY_URL, userDN, password, verbose); setupJNDIConnection(LdapConstants.ldapDirectoryUrl, userDN, password, verbose); return true; } catch (AuthenticationException e) { //throw (e); logger.info(e.getMessage()); return false; } } /** * This does a light copy of an attributes list (such as a UserAccount, if we need to modify it but * want to keep the original userAccount) * @param oldAtts * @return a light copy of the original attributes list. */ public Attributes copyAttributes(Attributes oldAtts) { BasicAttributes atts = new BasicAttributes(); NamingEnumeration attList = oldAtts.getAll(); while (attList.hasMoreElements()) // shouldn't throw an exception, so use normal enumeration methods { Attribute att = (Attribute) attList.nextElement(); atts.put(att); } return atts; } /** * open the directory connection. * @param url * @param tracing * @return * @throws NamingException */ private DirContext setupJNDIConnection(String url, String userDN, String password, boolean tracing) throws NamingException { /* * First, set up a large number of environment variables to sensible default valuse */ Hashtable env = new Hashtable(); // sanity check if (url == null) throw new NamingException("URL not specified in openContext()!"); // set the tracing level now, since it can't be set once the connection is open. if (tracing) env.put("com.sun.jndi.ldap.trace.ber", System.err); // echo trace to standard error output //env.put("java.naming.ldap.version", "3"); // always use ldap v3 - v2 too limited env.put(LdapConstants.ldapVersionStr, LdapConstants.ldapVersion); // always use ldap v3 - v2 too limited //env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); // use default jndi provider env.put(Context.INITIAL_CONTEXT_FACTORY, LdapConstants.ldapContext); // use default jndi provider //env.put("java.naming.ldap.deleteRDN", "false"); // usually what we want env.put(LdapConstants.ldapDeleteRdn, LdapConstants.ldapDeleteRdnValue); // usually what we want //env.put(Context.REFERRAL, "ignore"); //could be: follow, ignore, throw env.put(Context.REFERRAL, LdapConstants.ldapIgnore); //could be: follow, ignore, throw // env.put("java.naming.ldap.derefAliases", "finding"); // could be: finding, searching, etc. env.put(LdapConstants.ldapFindingAliases, LdapConstants.ldapFindingStr); // could be: finding, searching, etc. //env.put(Context.SECURITY_AUTHENTICATION, "simple"); // 'simple' = username + password env.put(Context.SECURITY_AUTHENTICATION, LdapConstants.ldapSecurityAuth); // 'simple' = username + password env.put(Context.SECURITY_PRINCIPAL, userDN); // add the full user dn env.put(Context.SECURITY_CREDENTIALS, password); // stupid jndi requires us to cast this to a string- env.put(Context.PROVIDER_URL, url); // the ldap url to connect to; e.g. "ldap://ca.com:389" /* * Open the actual LDAP session using the above environment variables */ DirContext newContext = new InitialDirContext(env); if (newContext == null) throw new NamingException( "Internal Error with jndi connection: No Context was returned, however no exception was reported by jndi."); return newContext; } /* * New Password Hashing Methods * * Utility Methods for hashing passwords, and comparing hashed passwords with plaintext * passwords. Requires BASE64 encoding class (separate in util package) */ public Attributes hashPasswordAttribute(Attributes account) throws NamingException { Attribute pwdAtt = account.get(LdapConstants.ldapAttrUserPassword); if (pwdAtt == null || pwdAtt.get() == null) throw new NamingException("user password attribute missing!"); logger.info("entered hashPassword()" + pwdAtt); Object o = pwdAtt.get(); logger.info("entered hashPassword()"); byte[] hash = hashPassword(o); logger.info("completed hashPassword()"); account.remove(LdapConstants.ldapAttrUserPassword); logger.info("adding the ldapAttrUserPassword, " + hash); account.put(LdapConstants.ldapAttrUserPassword, hash); byte[] pwd = (byte[]) account.get("userPassword").get(); if (pwd != null) { logger.info("getting the ldapAttrUserPassword, " + pwd); } else { logger.info("hash pwd is null when tried to retrieve it"); } return account; } private byte[] hashPassword(Object o) { // not usually the case; password should be a byte array. String pwd; if (o instanceof String) { logger.info("o instanceof String "); pwd = (String) o; logger.info("o instanceof String completed, " + pwd); } else { logger.info("StringEncode called"); pwd = stringEncode((byte[]) o); logger.info("StringEncode() completed " + pwd); } /** * we decide the encryption scheme */ if (!pwd.startsWith("{")) { logger.info("hashPwd() defaultEncryptionscheme called" + pwd); byte[] hash = hashPwd(pwd, LdapConstants.ldapEncryptionScheme, null); return hash; } else { logger.info("plainDecode for pwd called"); return plainDecode(pwd); // password already hashed. } } /** * Calculates the pwd hash to be stored in the userPassword field. * * @param s The password in plaintext that should be hashed. * @param type The encryption scheme (The {CRYPT} scheme is currently unsupported): t == 2 means MD5 (salt needs to * be null) t == 3 means SMD5 (needs a salt != null) t == 4 means SHA (salt needs to be null) t == 5 means SSHA * (needs a salt != null) * @param salt The salt that is to be used together with the schemes {SMD5} and {SSHA}. Should be between 8 and 16 * Bytes. salt should be null for any other scheme. * @return The base64-encoded hashed pwd with the following format: - {MD5}base64(MD5-hash) for MD5 hashes - * {SHA}base64(SHA-hash) for SHA hashes - {SMD5}base64(MD5-hash+salt bytes) for SMD5 hashes - * {SSHA}base64(SHA-hash+salt bytes) for SSHA hashes Or * {CRYPT}salt + base64(hash) * null if t is not one of 1, 2, 3, 4, 5. */ public byte[] hashPwd(String s, String type, byte[] salt) { try { MessageDigest md; StringBuffer hexString = new StringBuffer(); if (type.equals(LdapConstants.SSHA)) { if (salt == null) salt = getRandomSalt(); md = MessageDigest.getInstance(LdapConstants.SSHA); hexString.append("{" + LdapConstants.SSHA + "}"); } else if (type.equals(LdapConstants.SHA)) { md = MessageDigest.getInstance(LdapConstants.SHA); hexString.append("{" + LdapConstants.SHA + "}"); } else if (type.equals(LdapConstants.SMD5)) { if (salt == null) salt = getRandomSalt(); md = MessageDigest.getInstance(LdapConstants.SMD5); hexString.append("{" + LdapConstants.SMD5 + "}"); } else if (type.equals(LdapConstants.MD5)) { md = MessageDigest.getInstance(LdapConstants.MD5); //hexString.append("{" + MD5 + "}"); hexString.append("{" + LdapConstants.MD5 + "}"); } else if (type.equals(LdapConstants.crypt)) { return hashCrypt(s, salt); } else { logger.warn( "skipping hashing for password with type = " + type + " and has salt " + (salt == null)); return (null); } md.reset(); //md.update(s.getBytes("UTF-8")); md.update(s.getBytes(LdapConstants.UTF8)); if (salt != null) { // The way the salted hashes work is the following: // h=HASH(pwd+salt) // // To be able to restore the salt it needs to be appended // to the resulting hash. // {SMD5|SSHA}base64(h+salt) // So, lets append the salt to the pwd-buffer in md. md.update(salt); // Calculate the hash-value of s+salt byte[] buff = md.digest(); // And append the salt to the hashed pwd. byte[] new_buf = new byte[buff.length + salt.length]; for (int x = 0; x < buff.length; x++) new_buf[x] = buff[x]; for (int x = buff.length; x < new_buf.length; x++) new_buf[x] = salt[x - buff.length]; // New_buf now contains the 16(MD5), resp. 20(SHA1) hash // bytes and the salt. hexString.append(Base64.binaryToString(new_buf)); } else { byte[] buff = md.digest(); hexString.append(Base64.binaryToString(buff)); } //return hexString.toString().getBytes("UTF-8"); return hexString.toString().getBytes(LdapConstants.UTF8); } catch (UnsupportedEncodingException e) { logger.warn("Unexpected error encoding password ", e); e.printStackTrace(); return new byte[0]; } catch (java.security.NoSuchAlgorithmException e) { logger.warn("Unexpected error encoding password ", e); e.printStackTrace(); return new byte[0]; } } /** * Generate four bytes of reasonably random seed for salted algorithms where no seed * has been supplied. * @return four random bytes. */ private byte[] getRandomSalt() { return random.generateSeed(4); } public byte[] hashCrypt(String pwd, byte[] salt) throws UnsupportedEncodingException // not really! { // StringBuffer hexString = new StringBuffer("{CRYPT}"); StringBuffer hexString = new StringBuffer(LdapConstants.cryptWithBraces); hexString.append(JCrypt.crypt(salt, pwd)); //return hexString.toString().getBytes("UTF-8"); return hexString.toString().getBytes(LdapConstants.UTF8); } /** * Verifies a given password against the password stored in the userPassword-attribute. * <p/> * The userPassword-value should follow the following format: - {MD5}base64(MD5-hash) - {SHA}base64(SHA-hash) - * {SMD5}base64(MD5-hash+salt bytes) - {SSHA}base64(SHA-hash+salt bytes) - plaintext password * <p/> * If the userPassword value does not start with one of the prefixes {MD5}, {SMD5}, {SHA} or {SSHA} it will be * handled as a plaintext pwd. * <p/> * The Unix {CRYPT}-Scheme is currently not supported. * * @param originalPassword The original pwd stored in the userPassword value. * @param newPassword The password in plaintext that should be verified against the hashed pwd stored in the userPassword * field. * @return True - if the given plaintext pwd matches with the hashed pwd in the userPassword field, otherwise false. * Returns also false for the {CRYPT}-scheme as it is currently unsupported. */ protected boolean passwordVerify(String originalPassword, String newPassword) { logger.info("passwordVerify() entered "); if (originalPassword.toUpperCase().startsWith("{CRYPT}")) { logger.info("crypt = "); // newPassword is the given pwd in cleartext, so // we create the MD5 hash for the given pwd // and store it again in newPassword. String salt = originalPassword.substring(7, 9); newPassword = new String(hashPwd(newPassword, LdapConstants.crypt, salt.getBytes())); originalPassword = "{CRYPT}" + originalPassword.substring(7); // force prefix to uppsercase } else if (originalPassword.startsWith("{MD5}")) { logger.info("original password starts with MD5"); // newPassword is the given pwd in cleartext, so // we create the MD5 hash for the given pwd // and store it again in newPassword. newPassword = new String(hashPwd(newPassword, LdapConstants.MD5, null)); } else if (originalPassword.startsWith("{SMD5}")) { logger.info("original password starts with SMD5"); // SMD5 means "Salted MD5". The "salt" is a // String of an arbitrary length appended to // the given MD5 hash in originalPassword. // To get the salt bytes first we need to base64-decode // the pwd hash and store it in tmp. byte[] tmp = Base64.stringToBinary(originalPassword.substring(6)); // The length of an MD5-hash is always 16 Bytes. To get // the size of the salt we need to subtract 16 Bytes from // the total length of the base64-decoded String. if (tmp != null) { /* int len = tmp.length - 16; if (len > 0) { // Get the salt Bytes. As the MD5-Hash takes 16 Bytes, // the remaining Bytes will be the salt. byte[] salt = new byte[len]; for (int x = 0; x < len; x++) salt[x] = tmp[x + 16]; // newPassword is the given pwd in cleartext, so // we create the hash for the given pwd // and store it again in newPassword. newPassword = new String(hashPwd(newPassword, SMD5_CONST, salt)); } */ newPassword = new String(hashPwd(newPassword, LdapConstants.SMD5, getSalt(16, tmp))); } } else if (originalPassword.startsWith("{SHA}")) { logger.info("original password starts with SHA"); // newPassword is the given pwd in cleartext, so // we create the hash for the given pwd // and store it again in newPassword. newPassword = new String(hashPwd(newPassword, LdapConstants.SHA, null)); logger.info("newpassowrd = " + newPassword); } else if (originalPassword.startsWith("{SSHA}")) { logger.info("original password starts with SSHA"); // SSHA means "Salted SHA-1". The "salt" is a // String of an arbitrary length appended to // the given SHA-1 hash in originalPassword. // To get the salt bytes first we need to base64-decode // the pwd hash and store it in tmp. byte[] tmp = Base64.stringToBinary(originalPassword.substring(6)); // The length of an SHA-1-hash is always 20 Bytes. To get // the size of the salt we need to subtract 20 Bytes from // the total length of the base64-decoded String. if (tmp != null) { /* int len = tmp.length - 20; if (len > 0) { // Get the salt Bytes. As the SHA-1-Hash takes 20 Bytes, // the remaining Bytes will be the salt. byte[] salt = new byte[len]; for (int x = 0; x < len; x++) salt[x] = tmp[x + 20]; // newPassword is the given pwd in cleartext, so // we create the hash for the given pwd // and store it again in newPassword. newPassword = new String(hashPwd(newPassword, SSHA_CONST, salt)); } */ newPassword = new String(hashPwd(newPassword, LdapConstants.SSHA, getSalt(20, tmp))); } } else { // newPassword is already cleartext, no need for // hashing it. logger.info("newPassword without plainDecode" + newPassword); newPassword = new String(plainDecode(newPassword)); logger.info("newPassword with plainDecode" + newPassword); } // Compare the two hashed pwd-Strings and return true if // they match, otherwise false. If the Unix {CRYPT}-scheme // is used we return false as well. if (newPassword.equals(originalPassword)) return (true); logger.info("newPassword = " + newPassword + " originalPassword = " + originalPassword); return (false); } /** * This method extracts the 'salt' from the END of the password string. This works because * the base password is known to be of set length 'len' * @param len the length of the 'base' part of the password (i.e., the non-salt part) * @param pwdBytes * @return the salt part of a password bytes string */ private final byte[] getSalt(int len, byte[] pwdBytes) { if (pwdBytes != null && len > 0) { int saltLength = pwdBytes.length - len; if (saltLength > 0) { // Get the salt Bytes. As the base pwd takes 'len' bytes // the remaining Bytes will be the salt. byte[] salt = new byte[saltLength]; for (int x = 0; x < saltLength; x++) salt[x] = pwdBytes[x + len]; return salt; } } return null; } protected byte[] plainDecode(String s) { try { return s.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) // this really, really should never ever happen... { logger.info("UNEXPECTED INTERNAL ERROR - JAVA UNABLE TO DECODE UTF-8 ?!, error= " + e.getMessage()); e.printStackTrace(); return new byte[0]; } } /** * Converts between a byte array and text */ public String stringEncode(byte[] b) { if (b == null || b.length == 0) { logger.info("stringEncode, b is null"); return new String(); } else { try { logger.info("stringEncode(UTF-8)"); return new String(b, "UTF-8"); } catch (UnsupportedEncodingException e) // this should never, ever happen { logger.info("Unexpected error decoding password - JDK unable to handle UTF-8?? " + e.getMessage()); e.printStackTrace(); return new String(b); // Fall back on the platform default... not sure this is the best thing to do - CB } } } // short test method public void testMain() throws Exception { //Here are some usage examples to demonstrate how the password hashing API works. //1,2: This shows how we automatically hash a password attribute in a new user. String defaultEncryptionScheme = LdapConstants.SHA; // set the default encryption scheme - usually we only have one scheme per directory Attributes test = new BasicAttributes(); test.put("cn", "test user"); test.put("userPassword", "secret"); test.put("objectClass", "person"); //ActiveLoginImpl login = new ActiveLoginImpl(); //logger.info("1: invoking hashPasswordAttribute: "); Attributes mytest = hashPasswordAttribute(test); String pwd = stringEncode((byte[]) mytest.get("userPassword").get()); // logger.info("1: show automatically hashed password attribute: " + pwd); //logger.info("2: verify SHA hashed password against plaintext = " + passwordVerify("{SHA}5en6G6MezRroT3XKqkdPOmY/BfQ=", "secret")); //logger.info("2: verify SHA hashed password against plaintext = " + passwordVerify(pwd, "secret")); //3,4 This shows using a salted hash; we automatically generate the salt defaultEncryptionScheme = LdapConstants.SSHA; // set the default encryption scheme to a salted hash test.put("userPassword", "secret"); // reset password // logger.info("3) 1: invoking hashPasswordAttribute: "); test = hashPasswordAttribute(test); // hash password generating random salt String saltedPwd = stringEncode((byte[]) test.get("userPassword").get()); // these are commented out. //logger.info("3: show salted hashed password attribute: " + saltedPwd); //logger.info("4: verify SSHA salted hash password against plaintext = " + passwordVerify(saltedPwd, "secret")); //5,6 This shows using an old-style 'crypt' hash directly //logger.info("5: show creation of crypt password 'secret' with salt 'KD' = " + stringEncode(hashPwd("secret", LdapConstants.crypt ,plainDecode("KD")))); // logger.info("6: And verify crypt hashed password = " + passwordVerify("{crypt}KDdVi0RbEzCac", "secret")); } }