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    }