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