/* * This file is part of the OdinMS Maple Story Server Copyright (C) 2008 ~ 2010 * Patrick Huy <patrick.huy@frz.cc> Matthias Butz <matze@odinms.de> Jan * Christian Meyer <vimes@odinms.de> * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation. You may not use, modify or distribute this * program under any other version of the GNU Affero General Public License. * * 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 Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package javastory.client; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Random; /** * Provides cryptographic functions for password hashing. * * Legacy purpose as the method done here is insecure by hashing multiple times * and overly complicated. Will go away when/if official oms has no more users * with legacy passhashes. * * @author Nol888 * @version 0.1 */ public class LoginCryptoLegacy { /** * Map of 6 bit nibbles to base64 characters. */ private static final Random rand = new Random(); private static final char[] iota64 = new char[64]; static { int i = 0; iota64[i++] = '.'; iota64[i++] = '/'; for (char c = 'A'; c <= 'Z'; c++) { iota64[i++] = c; } for (char c = 'a'; c <= 'z'; c++) { iota64[i++] = c; } for (char c = '0'; c <= '9'; c++) { iota64[i++] = c; } } /** * Hash the password for first time storage. * * @param password * The password to be hashed. * @return String of the hashed password. */ public static final String hashPassword(final String password) { final byte[] randomBytes = new byte[6]; rand.setSeed(System.currentTimeMillis()); rand.nextBytes(randomBytes); return myCrypt(password, genSalt(randomBytes)); } /** * Check a password against a hash. * * @param password * The password to validate. * @param hash * The hash to validate against. * @return <code>true</code> if the password is correct, <code>false</code> * otherwise. */ public static final boolean checkPassword(final String password, final String hash) { return myCrypt(password, hash).equals(hash); } public static final boolean isLegacyPassword(final String hash) { return hash.substring(0, 3).equals("$H$"); } /** * Encrypt a string with <code>Seed</code> as a seed code. * * @param password * Password to encrypt. * @param seed * Seed to use. * @return The salted SHA1 hash of password. * @throws RuntimeException */ private static final String myCrypt(final String password, String seed) throws RuntimeException { String out = null; int count = 8; MessageDigest digester; // Check for correct Seed if (!seed.substring(0, 3).equals("$H$")) { // Oh noes! Generate a seed and continue. final byte[] randomBytes = new byte[6]; rand.nextBytes(randomBytes); seed = genSalt(randomBytes); } final String salt = seed.substring(4, 12); if (salt.length() != 8) { throw new RuntimeException("Error hashing password - Invalid seed."); } byte[] sha1Hash = new byte[40]; try { digester = MessageDigest.getInstance("SHA-1"); digester.update((salt + password).getBytes("iso-8859-1"), 0, (salt + password).length()); sha1Hash = digester.digest(); do { final byte[] CombinedBytes = new byte[sha1Hash.length + password.length()]; System.arraycopy(sha1Hash, 0, CombinedBytes, 0, sha1Hash.length); System.arraycopy(password.getBytes("iso-8859-1"), 0, CombinedBytes, sha1Hash.length, password.getBytes("iso-8859-1").length); digester.update(CombinedBytes, 0, CombinedBytes.length); sha1Hash = digester.digest(); } while (--count > 0); out = seed.substring(0, 12); out += encode64(sha1Hash); } catch (final NoSuchAlgorithmException Ex) { System.err.println("Error hashing password." + Ex); } catch (final UnsupportedEncodingException Ex) { System.err.println("Error hashing password." + Ex); } if (out == null) { throw new RuntimeException("Error hashing password - out = null"); } return out; } /** * Generates a salt string from random bytes <code>Random</code> * * @param Random * Random bytes to get salt from. * @return Salt string. */ private static final String genSalt(final byte[] Random) { final StringBuilder Salt = new StringBuilder("$H$"); Salt.append(iota64[30]); Salt.append(encode64(Random)); return Salt.toString(); } private static final String convertToHex(final byte[] data) { final StringBuffer buf = new StringBuffer(); for (final byte element : data) { int halfbyte = element >>> 4 & 0x0F; int two_halfs = 0; do { if (0 <= halfbyte && halfbyte <= 9) { buf.append((char) ('0' + halfbyte)); } else { buf.append((char) ('a' + (halfbyte - 10))); } halfbyte = element & 0x0F; } while (two_halfs++ < 1); } return buf.toString(); } public static final String encodeSHA1(final String text) throws NoSuchAlgorithmException, UnsupportedEncodingException { final MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] sha1hash = new byte[40]; md.update(text.getBytes("iso-8859-1"), 0, text.length()); sha1hash = md.digest(); return convertToHex(sha1hash); } /** * Encodes a byte array into base64. * * @param Input * Array of bytes to put into base64. * @return String of base64. */ private static final String encode64(final byte[] Input) { final int iLen = Input.length; final int oDataLen = (iLen * 4 + 2) / 3; // output length without padding final int oLen = (iLen + 2) / 3 * 4; // output length including // padding final char[] out = new char[oLen]; int ip = 0; int op = 0; while (ip < iLen) { final int i0 = Input[ip++] & 0xff; final int i1 = ip < iLen ? Input[ip++] & 0xff : 0; final int i2 = ip < iLen ? Input[ip++] & 0xff : 0; final int o0 = i0 >>> 2; final int o1 = (i0 & 3) << 4 | i1 >>> 4; final int o2 = (i1 & 0xf) << 2 | i2 >>> 6; final int o3 = i2 & 0x3F; out[op++] = iota64[o0]; out[op++] = iota64[o1]; out[op] = op < oDataLen ? iota64[o2] : '='; op++; out[op] = op < oDataLen ? iota64[o3] : '='; op++; } return new String(out); } }