001 /* 002 * Copyright 2010 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package com.hs.mail.security.login; 017 018 import java.security.MessageDigest; 019 import java.security.NoSuchAlgorithmException; 020 021 /** 022 * Implementation of the password encoder that returns the MD5 hash of any 023 * plaintext password passed into the encoder. The specification is available 024 * from RFC 1321. 025 * 026 */ 027 public class MD5PasswordEncoder implements PasswordEncoder { 028 029 private static final String MAGIC = "$1$"; 030 031 private static final char A64[] = { 032 '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', 033 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 034 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 035 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 036 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 037 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 038 'w', 'x', 'y', 'z' 039 }; 040 041 public MD5PasswordEncoder() { 042 } 043 044 public static String encodePassword(char password[]) { 045 long time = System.currentTimeMillis(); 046 return crypt(Integer.toString((int) (time & 0xffffffffL), 16), password); 047 } 048 049 public String encode(char[] password) { 050 return encodePassword(password); 051 } 052 053 public boolean compare(String encoded, char[] plain) { 054 String enc = encoded; 055 if (!enc.startsWith(MAGIC)) 056 throw new IllegalArgumentException( 057 "Invalid password format (no magic)"); 058 enc = enc.substring(3); 059 int index = enc.indexOf('$'); 060 if (index == -1) { 061 throw new IllegalArgumentException( 062 "Invalid password format (no salt)"); 063 } else { 064 String salt = enc.substring(0, index); 065 enc = enc.substring(index + 1); 066 return encoded.equals(crypt(salt, plain)); 067 } 068 } 069 070 public static String crypt(String salt, char password[]) { 071 if (salt.length() > 8) 072 salt = salt.substring(0, 8); 073 byte slt[] = salt.getBytes(); 074 byte pwd[] = new byte[password.length]; 075 for (int i = 0; i < pwd.length; i++) { 076 pwd[i] = (byte) password[i]; 077 password[i] = '\0'; 078 } 079 080 MessageDigest md1 = getMD5(); 081 md1.update(pwd); 082 md1.update(MAGIC.getBytes()); 083 md1.update(slt); 084 MessageDigest md2 = getMD5(); 085 md2.update(pwd); 086 md2.update(slt); 087 md2.update(pwd); 088 byte digest[] = md2.digest(); 089 for (int i = pwd.length; i > 0; i -= 16) { 090 md1.update(digest, 0, i <= 16 ? i : 16); 091 } 092 093 for (int i = pwd.length; i > 0; i >>>= 1) { 094 if ((i & 1) != 0) 095 md1.update((byte) 0); 096 else 097 md1.update(pwd[0]); 098 } 099 100 digest = md1.digest(); 101 for (int i = 0; i < 1000; i++) { 102 md1.reset(); 103 if ((i & 1) != 0) 104 md1.update(pwd); 105 else 106 md1.update(digest); 107 if (i % 3 != 0) 108 md1.update(slt); 109 if (i % 7 != 0) 110 md1.update(pwd); 111 if ((i & 1) != 0) 112 md1.update(digest); 113 else 114 md1.update(pwd); 115 digest = md1.digest(); 116 } 117 118 for (int i = 0; i < pwd.length; i++) { 119 pwd[i] = 0; 120 } 121 122 StringBuffer sb = new StringBuffer(); 123 sb.append(MAGIC).append(salt).append('$'); 124 long l = ui(digest[0]) << 16 | ui(digest[6]) << 8 | ui(digest[12]); 125 sb.append(a64(l, 4)); 126 l = ui(digest[1]) << 16 | ui(digest[7]) << 8 | ui(digest[13]); 127 sb.append(a64(l, 4)); 128 l = ui(digest[2]) << 16 | ui(digest[8]) << 8 | ui(digest[14]); 129 sb.append(a64(l, 4)); 130 l = ui(digest[3]) << 16 | ui(digest[9]) << 8 | ui(digest[15]); 131 sb.append(a64(l, 4)); 132 l = ui(digest[4]) << 16 | ui(digest[10]) << 8 | ui(digest[5]); 133 sb.append(a64(l, 4)); 134 l = ui(digest[11]); 135 sb.append(a64(l, 2)); 136 137 return sb.toString(); 138 } 139 140 private static MessageDigest getMD5() { 141 try { 142 return MessageDigest.getInstance("MD5"); 143 } catch (NoSuchAlgorithmException e) { 144 throw new RuntimeException("MD5 digest algorithm not available"); 145 } 146 } 147 148 private static String a64(long l, int size) { 149 StringBuffer sb = new StringBuffer(); 150 char a64[] = A64; 151 for (int i = 0; i < size; i++) { 152 sb.append(a64[(int) (l & 63L)]); 153 l >>>= 6; 154 } 155 return sb.toString(); 156 } 157 158 private static int ui(byte b) { 159 return b & 0xff; 160 } 161 162 public static void main(String args[]) { 163 for (int i = 0; i < args.length; i++) { 164 System.out.println(args[i] + ": " 165 + encodePassword(args[i].toCharArray())); 166 } 167 } 168 169 }