package org.limewire.setting; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Properties; import org.limewire.service.ErrorService; /** * Provides a password setting value. As a subclass of * <code>Setting</code>, the setting has a key. * <p> * <code>PasswordSetting</code> encrypts a password, determines if passwords * match and returns the encryption algorithm used. * <p> * Create a <code>PasswordSetting</code> object with a * {@link SettingsFactory#createPasswordSettingMD5(String, String)}. */ public final class PasswordSetting extends AbstractSetting<String> { /* * Q: Why MD5 and not SHA1 as default? * A: MD5 is used in the Digest HTTP Authentication method * and we can recycle the password for it if necessary */ /** MD5 algorithm */ public static final String MD5 = "MD5"; /** Array of supported algorithms */ private static final String[] ALGORITHMS = { MD5 }; /** Separates the algorithm name and the encrypted password */ private static final String SEPERATOR = "/"; /** The hex numbers */ private static final char[] HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; /** The encryption algorithm */ private String algorithm; private String value; /** * Creates a new <tt>PasswordSetting</tt> instance with the specified key * and default value. * * @param key the constant key to use for the setting * @param defaultStr the default value to use for the setting */ PasswordSetting(Properties defaultProps, Properties props, String algorithm, String key, String defaultStr) { super(defaultProps, props, key, (isEncrypted(defaultStr) ? defaultStr : encrypt(algorithm, defaultStr))); postInitWithAlgorithm(algorithm); setPrivate(true); } /** * @return the encryption algorithm */ public String getAlgorithm() { return algorithm; } /** * Returns true if the passed password equals the * current password. */ public boolean equals(String password) { if (password == null) { return false; } if (!isEncrypted(password)) { password = encrypt(algorithm, password); } if (!password.startsWith(algorithm + SEPERATOR)) { throw new IllegalArgumentException("Algorithm mismatch"); } return value.equalsIgnoreCase(password); } /** * @return the value of this setting */ public String get() { return value; } /** * Mutator for this setting. * * @param str the <tt>String</tt> to store */ public void set(String str) { setValueInternal(str); } /** * Load value from property string value. * * @param sValue property string value */ @Override protected void loadValue(String sValue) { if (algorithm != null && !isEncrypted(sValue)) { set(encrypt(algorithm, sValue)); return; } value = sValue; } /** * Sets the algorithm and encrypts the password if * necessary. */ private void postInitWithAlgorithm(String algorithm) { this.algorithm = algorithm; if (algorithm != null && !isEncrypted(value)) { set(encrypt(algorithm, value)); } } /** * Returns true if password is encrypted (i.e. starts with * a known algorithm prefix). */ private static boolean isEncrypted(String password) { for (String algorithm : ALGORITHMS) { if (password.startsWith(algorithm + SEPERATOR)) { return true; } } return false; } /** * Encrypts the password and returns it as hex string. */ private static String encrypt(String algorithm, String password) { return encrypt(algorithm, "UTF-8", password); } /** * Encrypts the password and returns it as hex string. */ private static String encrypt(String algorithm, String encoding, String password) { if (password == null) return null; try { MessageDigest md = MessageDigest.getInstance(algorithm); byte[] digest = md.digest(password.getBytes(encoding)); return algorithm + SEPERATOR + toHexString(digest); } catch (UnsupportedEncodingException err) { ErrorService.error(err); return null; } catch (NoSuchAlgorithmException err) { ErrorService.error(err); return null; } } /** * Encodes and returns b as hex string. */ private static String toHexString(byte[] bytes) { StringBuilder buffer = new StringBuilder(bytes.length * 2); for (byte b : bytes) { buffer.append(HEX[((b >> 4) & 0xF)]).append(HEX[b & 0xF]); } return buffer.toString(); } public static String toEncrypted(String algorithm, String value) { for (String s : ALGORITHMS) { if (s.equals(algorithm)) { return algorithm + SEPERATOR + value; } } throw new IllegalArgumentException("Unknown algorithm: " + algorithm); } }