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.io.BigEndianCodec;
019    import com.liferay.portal.kernel.security.SecureRandomUtil;
020    import com.liferay.portal.kernel.util.Base64;
021    import com.liferay.portal.kernel.util.CharPool;
022    import com.liferay.portal.kernel.util.GetterUtil;
023    import com.liferay.portal.kernel.util.Validator;
024    
025    import java.nio.ByteBuffer;
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            @Override
042            public String[] getSupportedAlgorithmTypes() {
043                    return new String[] {PasswordEncryptorUtil.TYPE_PBKDF2};
044            }
045    
046            @Override
047            protected String doEncrypt(
048                            String algorithm, String plainTextPassword,
049                            String encryptedPassword)
050                    throws PwdEncryptorException {
051    
052                    try {
053                            PBKDF2EncryptionConfiguration pbkdf2EncryptionConfiguration =
054                                    new PBKDF2EncryptionConfiguration();
055    
056                            pbkdf2EncryptionConfiguration.configure(
057                                    algorithm, encryptedPassword);
058    
059                            byte[] saltBytes = pbkdf2EncryptionConfiguration.getSaltBytes();
060    
061                            PBEKeySpec pbeKeySpec = new PBEKeySpec(
062                                    plainTextPassword.toCharArray(), saltBytes,
063                                    pbkdf2EncryptionConfiguration.getRounds(),
064                                    pbkdf2EncryptionConfiguration.getKeySize());
065    
066                            String algorithmName = algorithm;
067    
068                            int index = algorithm.indexOf(CharPool.SLASH);
069    
070                            if (index > -1) {
071                                    algorithmName = algorithm.substring(0, index);
072                            }
073    
074                            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(
075                                    algorithmName);
076    
077                            SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
078    
079                            byte[] secretKeyBytes = secretKey.getEncoded();
080    
081                            ByteBuffer byteBuffer = ByteBuffer.allocate(
082                                    2 * 4 + saltBytes.length + secretKeyBytes.length);
083    
084                            byteBuffer.putInt(pbkdf2EncryptionConfiguration.getKeySize());
085                            byteBuffer.putInt(pbkdf2EncryptionConfiguration.getRounds());
086                            byteBuffer.put(saltBytes);
087                            byteBuffer.put(secretKeyBytes);
088    
089                            return Base64.encode(byteBuffer.array());
090                    }
091                    catch (Exception e) {
092                            throw new PwdEncryptorException(e.getMessage(), e);
093                    }
094            }
095    
096            private static final int _KEY_SIZE = 160;
097    
098            private static final int _ROUNDS = 128000;
099    
100            private static final int _SALT_BYTES_LENGTH = 8;
101    
102            private static Pattern _pattern = Pattern.compile(
103                    "^.*/?([0-9]+)?/([0-9]+)$");
104    
105            private class PBKDF2EncryptionConfiguration {
106    
107                    public void configure(String algorithm, String encryptedPassword)
108                            throws PwdEncryptorException {
109    
110                            if (Validator.isNull(encryptedPassword)) {
111                                    Matcher matcher = _pattern.matcher(algorithm);
112    
113                                    if (matcher.matches()) {
114                                            _keySize = GetterUtil.getInteger(
115                                                    matcher.group(1), _KEY_SIZE);
116    
117                                            _rounds = GetterUtil.getInteger(matcher.group(2), _ROUNDS);
118                                    }
119    
120                                    BigEndianCodec.putLong(
121                                            _saltBytes, 0, SecureRandomUtil.nextLong());
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    }