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.CharPool;
020    import com.liferay.portal.kernel.util.GetterUtil;
021    import com.liferay.portal.kernel.util.Validator;
022    
023    import java.nio.ByteBuffer;
024    
025    import java.security.SecureRandom;
026    
027    import java.util.regex.Matcher;
028    import java.util.regex.Pattern;
029    
030    import javax.crypto.SecretKey;
031    import javax.crypto.SecretKeyFactory;
032    import javax.crypto.spec.PBEKeySpec;
033    
034    /**
035     * @author Michael C. Han
036     * @author Tomas Polesovsky
037     */
038    public class PBKDF2PasswordEncryptor
039            extends BasePasswordEncryptor implements PasswordEncryptor {
040    
041            public String[] getSupportedAlgorithmTypes() {
042                    return new String[] {PasswordEncryptorUtil.TYPE_PBKDF2};
043            }
044    
045            @Override
046            protected String doEncrypt(
047                            String algorithm, String plainTextPassword,
048                            String encryptedPassword)
049                    throws PwdEncryptorException {
050    
051                    try {
052                            PBKDF2EncryptionConfiguration pbkdf2EncryptionConfiguration =
053                                    new PBKDF2EncryptionConfiguration();
054    
055                            pbkdf2EncryptionConfiguration.configure(
056                                    algorithm, encryptedPassword);
057    
058                            byte[] saltBytes = pbkdf2EncryptionConfiguration.getSaltBytes();
059    
060                            PBEKeySpec pbeKeySpec = new PBEKeySpec(
061                                    plainTextPassword.toCharArray(), saltBytes,
062                                    pbkdf2EncryptionConfiguration.getRounds(),
063                                    pbkdf2EncryptionConfiguration.getKeySize());
064    
065                            String algorithmName = algorithm;
066    
067                            int index = algorithm.indexOf(CharPool.SLASH);
068    
069                            if (index > -1) {
070                                    algorithmName = algorithm.substring(0, index);
071                            }
072    
073                            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(
074                                    algorithmName);
075    
076                            SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
077    
078                            byte[] secretKeyBytes = secretKey.getEncoded();
079    
080                            ByteBuffer byteBuffer = ByteBuffer.allocate(
081                                    2 * 4 + saltBytes.length + secretKeyBytes.length);
082    
083                            byteBuffer.putInt(pbkdf2EncryptionConfiguration.getKeySize());
084                            byteBuffer.putInt(pbkdf2EncryptionConfiguration.getRounds());
085                            byteBuffer.put(saltBytes);
086                            byteBuffer.put(secretKeyBytes);
087    
088                            return Base64.encode(byteBuffer.array());
089                    }
090                    catch (Exception e) {
091                            throw new PwdEncryptorException(e.getMessage(), e);
092                    }
093            }
094    
095            private static final int _KEY_SIZE = 160;
096    
097            private static final int _ROUNDS = 128000;
098    
099            private static final int _SALT_BYTES_LENGTH = 8;
100    
101            private static Pattern _pattern = Pattern.compile(
102                    "^.*/?([0-9]+)?/([0-9]+)$");
103    
104            private class PBKDF2EncryptionConfiguration {
105    
106                    public void configure(String algorithm, String encryptedPassword)
107                            throws PwdEncryptorException {
108    
109                            if (Validator.isNull(encryptedPassword)) {
110                                    Matcher matcher = _pattern.matcher(algorithm);
111    
112                                    if (matcher.matches()) {
113                                            _keySize = GetterUtil.getInteger(
114                                                    matcher.group(1), _KEY_SIZE);
115    
116                                            _rounds = GetterUtil.getInteger(matcher.group(2), _ROUNDS);
117                                    }
118    
119                                    SecureRandom random = new SecureRandom();
120    
121                                    random.nextBytes(_saltBytes);
122                            }
123                            else {
124                                    byte[] bytes = new byte[16];
125    
126                                    try {
127                                            byte[] encryptedPasswordBytes = Base64.decode(
128                                                    encryptedPassword);
129    
130                                            System.arraycopy(
131                                                    encryptedPasswordBytes, 0, bytes, 0, bytes.length);
132                                    }
133                                    catch (Exception e) {
134                                            throw new PwdEncryptorException(
135                                                    "Unable to extract salt from encrypted password " +
136                                                            e.getMessage(),
137                                                    e);
138                                    }
139    
140                                    ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
141    
142                                    _keySize = byteBuffer.getInt();
143                                    _rounds = byteBuffer.getInt();
144    
145                                    byteBuffer.get(_saltBytes);
146                            }
147                    }
148    
149                    public int getKeySize() {
150                            return _keySize;
151                    }
152    
153                    public int getRounds() {
154                            return _rounds;
155                    }
156    
157                    public byte[] getSaltBytes() {
158                            return _saltBytes;
159                    }
160    
161                    private int _keySize = _KEY_SIZE;
162                    private int _rounds = _ROUNDS;
163                    private byte[] _saltBytes = new byte[_SALT_BYTES_LENGTH];
164    
165            }
166    
167    }