/** * Copyright (c) 2009--2014 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package com.redhat.rhn.common.util; import org.apache.commons.codec.binary.Hex; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * MD5Crypt * Utility class to create/check passwords generated by the perl crypt * function. Passwords are in the format of $1$salt$encodedpassword. * @version $Rev$ */ public class MD5Crypt { private static Integer saltLength = 8; // MD5 encoded password salt length /** * MD5Crypt */ private MD5Crypt() { } /** * generateEncodedKey - Handles generating the encoded key from the * final digest. * @param digest - Digest to use for encoding * @param salt - salt to prepend to output * @return Returns encoded string $1$salt$encodedkey */ private static String generateEncodedKey(byte[] digest, String salt) { StringBuilder out = new StringBuilder(CryptHelper.getMD5Prefix()); out.append(salt); out.append("$"); int val = ((digest[0] & 0xff) << 16) | ((digest[6] & 0xff) << 8) | (digest[12] & 0xff); out.append(CryptHelper.to64(val, 4)); val = ((digest[1] & 0xff) << 16) | ((digest[7] & 0xff) << 8) | (digest[13] & 0xff); out.append(CryptHelper.to64(val, 4)); val = ((digest[2] & 0xff) << 16) | ((digest[8] & 0xff) << 8) | (digest[14] & 0xff); out.append(CryptHelper.to64(val, 4)); val = ((digest[3] & 0xff) << 16) | ((digest[9] & 0xff) << 8) | (digest[15] & 0xff); out.append(CryptHelper.to64(val, 4)); val = ((digest[4] & 0xff) << 16) | ((digest[10] & 0xff) << 8) | (digest[5] & 0xff); out.append(CryptHelper.to64(val, 4)); val = (digest[11] & 0xff); out.append(CryptHelper.to64(val, 2)); return out.toString(); } /** * crypt - method to help in setting passwords. * @param key - The key to encode * @return Returns a string in the form of "$1$RandomSalt$encodedkey" */ public static String crypt(String key) { return crypt(key, CryptHelper.generateRandomSalt(saltLength)); } /** * crypt * Encodes a key using a salt (s) in the same manner as the * perl crypt() function. * This method will be called directly when checking passwords. It will * also be called from the crypt(key) function when setting a password. * @param key - The key to encode * @param s - The salt * @return Returns a string in the form of "$1$salt$encodedkey" * @throws MD5CryptException */ public static String crypt(String key, String s) { /** * If this method is called in order for a comparison, s may be * in the form of $1$salt$encodedkey. We'll need to extract * the salt from it. */ String salt = CryptHelper.getSalt(s, CryptHelper.getMD5Prefix(), saltLength); MessageDigest md1; MessageDigest md2; try { md1 = MessageDigest.getInstance("MD5"); md2 = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new MD5CryptException("Problem getting MD5 message digest " + "(NoSuchAlgorithm Exception)."); } byte[] keyBytes = key.getBytes(); byte[] saltBytes = salt.getBytes(); byte[] prefixBytes = CryptHelper.getMD5Prefix().getBytes(); int keylength = key.length(); //Update first MessageDigest - key/prefix/salt md1.update(keyBytes); md1.update(prefixBytes); md1.update(saltBytes); //Update second MessageDigest - key/salt/key md2.update(keyBytes); md2.update(saltBytes); md2.update(keyBytes); byte[] md2Digest = md2.digest(); int md2DigestLength = md2Digest.length; for (int i = keylength; i > 0; i -= md2DigestLength) { if (i > md2DigestLength) { md1.update(md2Digest, 0, md2DigestLength); } else { md1.update(md2Digest, 0, i); } } md2.reset(); for (int i = keylength; i > 0; i >>= 1) { if ((i & 1) == 1) { md1.update((byte) 0); } else { md1.update(keyBytes[0]); } } byte[] md1Digest = md1.digest(); for (int i = 0; i < 1000; i++) { md2.reset(); if ((i & 1) == 1) { md2.update(keyBytes); } else { md2.update(md1Digest); } if ((i % 3) != 0) { md2.update(saltBytes); } if ((i % 7) != 0) { md2.update(keyBytes); } if ((i & 1) != 0) { md2.update(md1Digest); } else { md2.update(keyBytes); } md1Digest = md2.digest(); } return generateEncodedKey(md1Digest, salt); } /** * MD5 and Hexify a string. Take the input string, MD5 encode it * and then turn it into Hex. * @param inputString you want md5hexed * @return md5hexed String. */ public static String md5Hex(String inputString) { byte[] secretBytes; try { secretBytes = inputString.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UnsupportedEncodingException when" + " trying to convert a String into UTF-8. This shouldn't happen.", e); } return md5Hex(secretBytes); } /** * MD5 and Hexify an array of bytes. Take the input array, MD5 encodes it * and then turns it into Hex. * @param secretBytes you want md5hexed * @return md5hexed String. */ public static String md5Hex(byte[] secretBytes) { String retval = null; // add secret MessageDigest md; try { md = MessageDigest.getInstance("MD5"); //byte[] secretBytes = inputString.getBytes("UTF-8"); md.update(secretBytes); // generate the digest byte[] digest = md.digest(); // hexify this puppy retval = new String(Hex.encodeHex(digest)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("NoSuchAlgorithm: MD5. Something" + " weird with your JVM, you should be able to do this.", e); } return retval; } }