001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.security.pwd;
016    
017    import com.liferay.portal.PwdEncryptorException;
018    import com.liferay.portal.kernel.util.Base64;
019    import com.liferay.portal.kernel.util.Digester;
020    import com.liferay.portal.kernel.util.DigesterUtil;
021    import com.liferay.portal.kernel.util.GetterUtil;
022    import com.liferay.portal.kernel.util.PropsKeys;
023    import com.liferay.portal.kernel.util.StringPool;
024    import com.liferay.portal.kernel.util.Validator;
025    import com.liferay.portal.util.PropsUtil;
026    
027    import java.io.UnsupportedEncodingException;
028    
029    import java.security.MessageDigest;
030    import java.security.NoSuchAlgorithmException;
031    import java.security.SecureRandom;
032    
033    import java.util.Random;
034    
035    import jodd.util.BCrypt;
036    
037    import org.vps.crypt.Crypt;
038    
039    /**
040     * @author Brian Wing Shun Chan
041     * @author Scott Lee
042     */
043    public class PwdEncryptor {
044    
045            public static final String PASSWORDS_ENCRYPTION_ALGORITHM =
046                    GetterUtil.getString(PropsUtil.get(
047                            PropsKeys.PASSWORDS_ENCRYPTION_ALGORITHM)).toUpperCase();
048    
049            public static final char[] SALT_CHARS =
050                    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
051                            .toCharArray();
052    
053            public static final String TYPE_BCRYPT = "BCRYPT";
054    
055            /**
056             * @deprecated {@link #TYPE_UFC_CRYPT}
057             */
058            public static final String TYPE_CRYPT = "CRYPT";
059    
060            public static final String TYPE_MD2 = "MD2";
061    
062            public static final String TYPE_MD5 = "MD5";
063    
064            public static final String TYPE_NONE = "NONE";
065    
066            public static final String TYPE_SHA = "SHA";
067    
068            public static final String TYPE_SHA_256 = "SHA-256";
069    
070            public static final String TYPE_SHA_384 = "SHA-384";
071    
072            public static final String TYPE_SSHA = "SSHA";
073    
074            public static final String TYPE_UFC_CRYPT = "UFC-CRYPT";
075    
076            public static String encrypt(String clearTextPassword)
077                    throws PwdEncryptorException {
078    
079                    return encrypt(PASSWORDS_ENCRYPTION_ALGORITHM, clearTextPassword, null);
080            }
081    
082            public static String encrypt(
083                            String clearTextPassword, String currentEncryptedPassword)
084                    throws PwdEncryptorException {
085    
086                    return encrypt(
087                            PASSWORDS_ENCRYPTION_ALGORITHM, clearTextPassword,
088                            currentEncryptedPassword);
089            }
090    
091            public static String encrypt(
092                            String algorithm, String clearTextPassword,
093                            String currentEncryptedPassword)
094                    throws PwdEncryptorException {
095    
096                    if (algorithm.equals(TYPE_BCRYPT)) {
097                            byte[] saltBytes = _getSaltFromBCrypt(currentEncryptedPassword);
098    
099                            return encodePassword(algorithm, clearTextPassword, saltBytes);
100                    }
101                    else if (algorithm.equals(TYPE_CRYPT) ||
102                                     algorithm.equals(TYPE_UFC_CRYPT)) {
103    
104                            byte[] saltBytes = _getSaltFromCrypt(currentEncryptedPassword);
105    
106                            return encodePassword(algorithm, clearTextPassword, saltBytes);
107                    }
108                    else if (algorithm.equals(TYPE_NONE)) {
109                            return clearTextPassword;
110                    }
111                    else if (algorithm.equals(TYPE_SSHA)) {
112                            byte[] saltBytes = _getSaltFromSSHA(currentEncryptedPassword);
113    
114                            return encodePassword(algorithm, clearTextPassword, saltBytes);
115                    }
116                    else {
117                            return encodePassword(algorithm, clearTextPassword, null);
118                    }
119            }
120    
121            protected static String encodePassword(
122                            String algorithm, String clearTextPassword, byte[] saltBytes)
123                    throws PwdEncryptorException {
124    
125                    try {
126                            if (algorithm.equals(TYPE_BCRYPT)) {
127                                    String salt = new String(saltBytes);
128    
129                                    return BCrypt.hashpw(clearTextPassword, salt);
130                            }
131                            else if (algorithm.equals(TYPE_CRYPT) ||
132                                             algorithm.equals(TYPE_UFC_CRYPT)) {
133    
134                                    return Crypt.crypt(
135                                            saltBytes, clearTextPassword.getBytes(Digester.ENCODING));
136                            }
137                            else if (algorithm.equals(TYPE_SSHA)) {
138                                    byte[] clearTextPasswordBytes = clearTextPassword.getBytes(
139                                            Digester.ENCODING);
140    
141                                    // Create a byte array of salt bytes appended to password bytes
142    
143                                    byte[] pwdPlusSalt =
144                                            new byte[clearTextPasswordBytes.length + saltBytes.length];
145    
146                                    System.arraycopy(
147                                            clearTextPasswordBytes, 0, pwdPlusSalt, 0,
148                                            clearTextPasswordBytes.length);
149    
150                                    System.arraycopy(
151                                            saltBytes, 0, pwdPlusSalt, clearTextPasswordBytes.length,
152                                            saltBytes.length);
153    
154                                    // Digest byte array
155    
156                                    MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1");
157    
158                                    byte[] pwdPlusSaltHash = sha1Digest.digest(pwdPlusSalt);
159    
160                                    // Appends salt bytes to the SHA-1 digest.
161    
162                                    byte[] digestPlusSalt =
163                                            new byte[pwdPlusSaltHash.length + saltBytes.length];
164    
165                                    System.arraycopy(
166                                            pwdPlusSaltHash, 0, digestPlusSalt, 0,
167                                            pwdPlusSaltHash.length);
168    
169                                    System.arraycopy(
170                                            saltBytes, 0, digestPlusSalt, pwdPlusSaltHash.length,
171                                            saltBytes.length);
172    
173                                    // Base64 encode and format string
174    
175                                    return Base64.encode(digestPlusSalt);
176                            }
177                            else {
178                                    return DigesterUtil.digest(algorithm, clearTextPassword);
179                            }
180                    }
181                    catch (NoSuchAlgorithmException nsae) {
182                            throw new PwdEncryptorException(nsae.getMessage());
183                    }
184                    catch (UnsupportedEncodingException uee) {
185                            throw new PwdEncryptorException(uee.getMessage());
186                    }
187            }
188    
189            private static byte[] _getSaltFromBCrypt(String bcryptString)
190                    throws PwdEncryptorException {
191    
192                    byte[] saltBytes = null;
193    
194                    try {
195                            if (Validator.isNull(bcryptString)) {
196                                    String salt = BCrypt.gensalt();
197    
198                                    saltBytes = salt.getBytes(StringPool.UTF8);
199                            }
200                            else {
201                                    String salt = bcryptString.substring(0, 29);
202    
203                                    saltBytes = salt.getBytes(StringPool.UTF8);
204                            }
205                    }
206                    catch (UnsupportedEncodingException uee) {
207                            throw new PwdEncryptorException(
208                                    "Unable to extract salt from encrypted password: " +
209                                            uee.getMessage());
210                    }
211    
212                    return saltBytes;
213            }
214    
215            private static byte[] _getSaltFromCrypt(String cryptString)
216                    throws PwdEncryptorException {
217    
218                    byte[] saltBytes = null;
219    
220                    try {
221                            if (Validator.isNull(cryptString)) {
222    
223                                    // Generate random salt
224    
225                                    Random random = new Random();
226    
227                                    int numSaltChars = SALT_CHARS.length;
228    
229                                    StringBuilder sb = new StringBuilder();
230    
231                                    int x = random.nextInt(Integer.MAX_VALUE) % numSaltChars;
232                                    int y = random.nextInt(Integer.MAX_VALUE) % numSaltChars;
233    
234                                    sb.append(SALT_CHARS[x]);
235                                    sb.append(SALT_CHARS[y]);
236    
237                                    String salt = sb.toString();
238    
239                                    saltBytes = salt.getBytes(Digester.ENCODING);
240                            }
241                            else {
242    
243                                    // Extract salt from encrypted password
244    
245                                    String salt = cryptString.substring(0, 2);
246    
247                                    saltBytes = salt.getBytes(Digester.ENCODING);
248                            }
249                    }
250                    catch (UnsupportedEncodingException uee) {
251                            throw new PwdEncryptorException(
252                                    "Unable to extract salt from encrypted password: " +
253                                            uee.getMessage());
254                    }
255    
256                    return saltBytes;
257            }
258    
259            private static byte[] _getSaltFromSSHA(String sshaString)
260                    throws PwdEncryptorException {
261    
262                    byte[] saltBytes = new byte[8];
263    
264                    if (Validator.isNull(sshaString)) {
265    
266                            // Generate random salt
267    
268                            Random random = new SecureRandom();
269    
270                            random.nextBytes(saltBytes);
271                    }
272                    else {
273    
274                            // Extract salt from encrypted password
275    
276                            try {
277                                    byte[] digestPlusSalt = Base64.decode(sshaString);
278                                    byte[] digestBytes = new byte[digestPlusSalt.length - 8];
279    
280                                    System.arraycopy(
281                                            digestPlusSalt, 0, digestBytes, 0, digestBytes.length);
282    
283                                    System.arraycopy(
284                                            digestPlusSalt, digestBytes.length, saltBytes, 0,
285                                            saltBytes.length);
286                            }
287                            catch (Exception e) {
288                                    throw new PwdEncryptorException(
289                                            "Unable to extract salt from encrypted password: " +
290                                                    e.getMessage());
291                            }
292                    }
293    
294                    return saltBytes;
295            }
296    
297    }