001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.util.crypt;
018
019import java.security.GeneralSecurityException;
020import java.security.NoSuchAlgorithmException;
021import java.security.spec.AlgorithmParameterSpec;
022import java.security.spec.InvalidKeySpecException;
023import java.security.spec.KeySpec;
024import java.util.Random;
025
026import javax.crypto.Cipher;
027import javax.crypto.SecretKey;
028import javax.crypto.SecretKeyFactory;
029import javax.crypto.spec.PBEKeySpec;
030import javax.crypto.spec.PBEParameterSpec;
031
032import org.apache.wicket.util.lang.Args;
033
034
035/**
036 * Provide some simple means to encrypt and decrypt strings such as passwords. The whole
037 * implementation is based around Sun's security providers and uses the <a
038 * href="http://www.ietf.org/rfc/rfc2898.txt">PBEWithMD5AndDES</a> method to encrypt and decrypt the
039 * data.
040 * 
041 * @author Juergen Donnerstag
042 */
043public class SunJceCrypt extends AbstractCrypt
044{
045        /** Name of the default encryption method */
046        public static final String DEFAULT_CRYPT_METHOD = "PBEWithMD5AndDES";
047
048        /** The name of encryption method (cipher) */
049        private final String cryptMethod;
050        
051        private final int iterationCount;
052        
053        private final byte[] salt;
054 
055        /**
056         * Constructor.
057         * 
058         * @param salt
059         *              salt for encryption
060         * @param iterationCount
061         *                              iteration count
062         */
063        public SunJceCrypt(byte[] salt, int iterationCount)
064        {
065                this(DEFAULT_CRYPT_METHOD, salt, iterationCount);
066        }
067
068        /**
069         * Constructor that uses a custom encryption method (cipher).
070         * You may need to override {@link #createKeySpec()} and/or
071         * {@link #createParameterSpec()} for the custom cipher.
072         *
073         * @param cryptMethod
074         *              the name of encryption method (the cipher)
075         * @param salt
076         *              salt for encryption
077         * @param iterationCount
078         *                              iteration count
079         */
080        public SunJceCrypt(String cryptMethod, byte[] salt, int iterationCount)
081        {
082                this.cryptMethod = Args.notNull(cryptMethod, "Crypt method");
083                this.salt = Args.notNull(salt, "salt");
084                this.iterationCount = Args.withinRange(1, Integer.MAX_VALUE,  iterationCount, "iterationCount");
085        }
086
087        /**
088         * Crypts the given byte array
089         * 
090         * @param input
091         *            byte array to be encrypted
092         * @param mode
093         *            crypt mode
094         * @return the input crypted. Null in case of an error
095         * @throws GeneralSecurityException
096         */
097        @Override
098        protected byte[] crypt(final byte[] input, final int mode)
099                throws GeneralSecurityException
100        {
101                SecretKey key = generateSecretKey();
102                AlgorithmParameterSpec spec = createParameterSpec();
103                Cipher ciph = createCipher(key, spec, mode);
104                return ciph.doFinal(input);
105        }
106
107        /**
108         * Creates the {@link javax.crypto.Cipher} that will do the de-/encryption.
109         *
110         * @param key
111         *              the secret key to use
112         * @param spec
113         *              the parameters spec to use
114         * @param mode
115         *              the mode ({@link javax.crypto.Cipher#ENCRYPT_MODE} or {@link javax.crypto.Cipher#DECRYPT_MODE})
116         * @return the cipher that will do the de-/encryption
117         * @throws GeneralSecurityException
118         */
119        protected Cipher createCipher(SecretKey key, AlgorithmParameterSpec spec, int mode) throws GeneralSecurityException
120        {
121                Cipher cipher = Cipher.getInstance(cryptMethod);
122                cipher.init(mode, key, spec);
123                return cipher;
124        }
125
126        /**
127         * Generate the de-/encryption key.
128         * <p>
129         * Note: if you don't provide your own encryption key, the implementation will use a default. Be
130         * aware that this is potential security risk. Thus make sure you always provide your own one.
131         *
132         * @return secretKey the security key generated
133         * @throws NoSuchAlgorithmException
134         *             unable to find encryption algorithm specified
135         * @throws InvalidKeySpecException
136         *             invalid encryption key
137         */
138        protected SecretKey generateSecretKey() throws NoSuchAlgorithmException,
139                InvalidKeySpecException
140        {
141                SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(cryptMethod);
142                KeySpec spec = createKeySpec();
143                return keyFactory.generateSecret(spec);
144        }
145        
146        /**
147         * @return the parameter spec to be used for the configured crypt method
148         */
149        protected AlgorithmParameterSpec createParameterSpec()
150        {
151                return new PBEParameterSpec(salt, iterationCount);
152        }
153
154        /**
155         * @return the key spec to be used for the configured crypt method
156         */
157        protected KeySpec createKeySpec()
158        {
159                return new PBEKeySpec(getKey().toCharArray());
160        }
161
162        /**
163         * Create a random salt to be used for this crypt. 
164         * 
165         * @return salt, always 8 bytes long
166         */
167        public static byte[] randomSalt()
168        {
169                // must be 8 bytes - for anything else PBES1Core throws
170                // InvalidAlgorithmParameterException: Salt must be 8 bytes long  
171                byte[] salt = new byte[8];
172                new Random().nextBytes(salt);
173                return salt;
174        }
175}