001    /**
002     * Copyright (c) 2000-2013 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(
047                            PropsUtil.get(
048                                    PropsKeys.PASSWORDS_ENCRYPTION_ALGORITHM)).toUpperCase();
049    
050            public static final char[] SALT_CHARS =
051                    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./".
052                            toCharArray();
053    
054            public static final String TYPE_BCRYPT = "BCRYPT";
055    
056            /**
057             * @deprecated {@link #TYPE_UFC_CRYPT}
058             */
059            public static final String TYPE_CRYPT = "CRYPT";
060    
061            public static final String TYPE_MD2 = "MD2";
062    
063            public static final String TYPE_MD5 = "MD5";
064    
065            public static final String TYPE_NONE = "NONE";
066    
067            public static final String TYPE_SHA = "SHA";
068    
069            public static final String TYPE_SHA_256 = "SHA-256";
070    
071            public static final String TYPE_SHA_384 = "SHA-384";
072    
073            public static final String TYPE_SSHA = "SSHA";
074    
075            public static final String TYPE_UFC_CRYPT = "UFC-CRYPT";
076    
077            public static String encrypt(String clearTextPassword)
078                    throws PwdEncryptorException {
079    
080                    return encrypt(PASSWORDS_ENCRYPTION_ALGORITHM, clearTextPassword, null);
081            }
082    
083            public static String encrypt(
084                            String clearTextPassword, String currentEncryptedPassword)
085                    throws PwdEncryptorException {
086    
087                    return encrypt(
088                            PASSWORDS_ENCRYPTION_ALGORITHM, clearTextPassword,
089                            currentEncryptedPassword);
090            }
091    
092            public static String encrypt(
093                            String algorithm, String clearTextPassword,
094                            String currentEncryptedPassword)
095                    throws PwdEncryptorException {
096    
097                    if (algorithm.equals(TYPE_BCRYPT)) {
098                            byte[] saltBytes = _getSaltFromBCrypt(currentEncryptedPassword);
099    
100                            return encodePassword(algorithm, clearTextPassword, saltBytes);
101                    }
102                    else if (algorithm.equals(TYPE_CRYPT) ||
103                                     algorithm.equals(TYPE_UFC_CRYPT)) {
104    
105                            byte[] saltBytes = _getSaltFromCrypt(currentEncryptedPassword);
106    
107                            return encodePassword(algorithm, clearTextPassword, saltBytes);
108                    }
109                    else if (algorithm.equals(TYPE_NONE)) {
110                            return clearTextPassword;
111                    }
112                    else if (algorithm.equals(TYPE_SSHA)) {
113                            byte[] saltBytes = _getSaltFromSSHA(currentEncryptedPassword);
114    
115                            return encodePassword(algorithm, clearTextPassword, saltBytes);
116                    }
117                    else {
118                            return encodePassword(algorithm, clearTextPassword, null);
119                    }
120            }
121    
122            protected static String encodePassword(
123                            String algorithm, String clearTextPassword, byte[] saltBytes)
124                    throws PwdEncryptorException {
125    
126                    try {
127                            if (algorithm.equals(TYPE_BCRYPT)) {
128                                    String salt = new String(saltBytes);
129    
130                                    return BCrypt.hashpw(clearTextPassword, salt);
131                            }
132                            else if (algorithm.equals(TYPE_CRYPT) ||
133                                             algorithm.equals(TYPE_UFC_CRYPT)) {
134    
135                                    return Crypt.crypt(
136                                            saltBytes, clearTextPassword.getBytes(Digester.ENCODING));
137                            }
138                            else if (algorithm.equals(TYPE_SSHA)) {
139                                    byte[] clearTextPasswordBytes = clearTextPassword.getBytes(
140                                            Digester.ENCODING);
141    
142                                    // Create a byte array of salt bytes appended to password bytes
143    
144                                    byte[] pwdPlusSalt =
145                                            new byte[clearTextPasswordBytes.length + saltBytes.length];
146    
147                                    System.arraycopy(
148                                            clearTextPasswordBytes, 0, pwdPlusSalt, 0,
149                                            clearTextPasswordBytes.length);
150    
151                                    System.arraycopy(
152                                            saltBytes, 0, pwdPlusSalt, clearTextPasswordBytes.length,
153                                            saltBytes.length);
154    
155                                    // Digest byte array
156    
157                                    MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1");
158    
159                                    byte[] pwdPlusSaltHash = sha1Digest.digest(pwdPlusSalt);
160    
161                                    // Appends salt bytes to the SHA-1 digest.
162    
163                                    byte[] digestPlusSalt =
164                                            new byte[pwdPlusSaltHash.length + saltBytes.length];
165    
166                                    System.arraycopy(
167                                            pwdPlusSaltHash, 0, digestPlusSalt, 0,
168                                            pwdPlusSaltHash.length);
169    
170                                    System.arraycopy(
171                                            saltBytes, 0, digestPlusSalt, pwdPlusSaltHash.length,
172                                            saltBytes.length);
173    
174                                    // Base64 encode and format string
175    
176                                    return Base64.encode(digestPlusSalt);
177                            }
178                            else {
179                                    return DigesterUtil.digest(algorithm, clearTextPassword);
180                            }
181                    }
182                    catch (NoSuchAlgorithmException nsae) {
183                            throw new PwdEncryptorException(nsae.getMessage());
184                    }
185                    catch (UnsupportedEncodingException uee) {
186                            throw new PwdEncryptorException(uee.getMessage());
187                    }
188            }
189    
190            private static byte[] _getSaltFromBCrypt(String bcryptString)
191                    throws PwdEncryptorException {
192    
193                    byte[] saltBytes = null;
194    
195                    try {
196                            if (Validator.isNull(bcryptString)) {
197                                    String salt = BCrypt.gensalt();
198    
199                                    saltBytes = salt.getBytes(StringPool.UTF8);
200                            }
201                            else {
202                                    String salt = bcryptString.substring(0, 29);
203    
204                                    saltBytes = salt.getBytes(StringPool.UTF8);
205                            }
206                    }
207                    catch (UnsupportedEncodingException uee) {
208                            throw new PwdEncryptorException(
209                                    "Unable to extract salt from encrypted password: " +
210                                            uee.getMessage());
211                    }
212    
213                    return saltBytes;
214            }
215    
216            private static byte[] _getSaltFromCrypt(String cryptString)
217                    throws PwdEncryptorException {
218    
219                    byte[] saltBytes = null;
220    
221                    try {
222                            if (Validator.isNull(cryptString)) {
223    
224                                    // Generate random salt
225    
226                                    Random random = new Random();
227    
228                                    int numSaltChars = SALT_CHARS.length;
229    
230                                    StringBuilder sb = new StringBuilder();
231    
232                                    int x = random.nextInt(Integer.MAX_VALUE) % numSaltChars;
233                                    int y = random.nextInt(Integer.MAX_VALUE) % numSaltChars;
234    
235                                    sb.append(SALT_CHARS[x]);
236                                    sb.append(SALT_CHARS[y]);
237    
238                                    String salt = sb.toString();
239    
240                                    saltBytes = salt.getBytes(Digester.ENCODING);
241                            }
242                            else {
243    
244                                    // Extract salt from encrypted password
245    
246                                    String salt = cryptString.substring(0, 2);
247    
248                                    saltBytes = salt.getBytes(Digester.ENCODING);
249                            }
250                    }
251                    catch (UnsupportedEncodingException uee) {
252                            throw new PwdEncryptorException(
253                                    "Unable to extract salt from encrypted password: " +
254                                            uee.getMessage());
255                    }
256    
257                    return saltBytes;
258            }
259    
260            private static byte[] _getSaltFromSSHA(String sshaString)
261                    throws PwdEncryptorException {
262    
263                    byte[] saltBytes = new byte[8];
264    
265                    if (Validator.isNull(sshaString)) {
266    
267                            // Generate random salt
268    
269                            Random random = new SecureRandom();
270    
271                            random.nextBytes(saltBytes);
272                    }
273                    else {
274    
275                            // Extract salt from encrypted password
276    
277                            try {
278                                    byte[] digestPlusSalt = Base64.decode(sshaString);
279                                    byte[] digestBytes = new byte[digestPlusSalt.length - 8];
280    
281                                    System.arraycopy(
282                                            digestPlusSalt, 0, digestBytes, 0, digestBytes.length);
283    
284                                    System.arraycopy(
285                                            digestPlusSalt, digestBytes.length, saltBytes, 0,
286                                            saltBytes.length);
287                            }
288                            catch (Exception e) {
289                                    throw new PwdEncryptorException(
290                                            "Unable to extract salt from encrypted password: " +
291                                                    e.getMessage());
292                            }
293                    }
294    
295                    return saltBytes;
296            }
297    
298    }