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        /**
046         * Iteration count used in combination with the salt to create the encryption key.
047         */
048        private final static int DEFAULT_ITERATION_COUNT = 17;
049
050        /** Name of the default encryption method */
051        public static final String DEFAULT_CRYPT_METHOD = "PBEWithMD5AndDES";
052
053        /**
054         * Default salt.
055         * 
056         * @deprecated TODO remove in Wicket 10
057         */
058        @Deprecated
059        public final static byte[] SALT = { (byte)0x15, (byte)0x8c, (byte)0xa3, (byte)0x4a,
060                        (byte)0x66, (byte)0x51, (byte)0x2a, (byte)0xbc };
061
062        /** The name of encryption method (cipher) */
063        private final String cryptMethod;
064        
065        private final int iterationCount;
066        
067        private final byte[] salt;
068 
069        /**
070         * Constructor
071         * 
072         * @deprecated TODO remove in Wicket 10
073         */
074        @Deprecated(forRemoval = true)
075        public SunJceCrypt()
076        {
077                this(DEFAULT_CRYPT_METHOD);
078        }
079
080        /**
081         * Constructor.
082         * 
083         * @param salt
084         *              salt for encryption
085         * @param iterationCount
086         *                              iteration count
087         */
088        public SunJceCrypt(byte[] salt, int iterationCount)
089        {
090                this(DEFAULT_CRYPT_METHOD, salt, iterationCount);
091        }
092
093        /**
094         * Constructor
095         *
096         * @deprecated TODO remove in Wicket 10
097         */
098        @Deprecated(forRemoval = true)
099        public SunJceCrypt(String cryptMethod)
100        {
101                this(cryptMethod, SALT, DEFAULT_ITERATION_COUNT);
102        }
103
104        /**
105         * Constructor that uses a custom encryption method (cipher).
106         * You may need to override {@link #createKeySpec()} and/or
107         * {@link #createParameterSpec()} for the custom cipher.
108         *
109         * @param cryptMethod
110         *              the name of encryption method (the cipher)
111         * @param salt
112         *              salt for encryption
113         * @param iterationCount
114         *                              iteration count
115         */
116        public SunJceCrypt(String cryptMethod, byte[] salt, int iterationCount)
117        {
118                this.cryptMethod = Args.notNull(cryptMethod, "Crypt method");
119                this.salt = Args.notNull(salt, "salt");
120                this.iterationCount = Args.withinRange(1, Integer.MAX_VALUE,  iterationCount, "iterationCount");
121        }
122
123        /**
124         * Crypts the given byte array
125         * 
126         * @param input
127         *            byte array to be encrypted
128         * @param mode
129         *            crypt mode
130         * @return the input crypted. Null in case of an error
131         * @throws GeneralSecurityException
132         */
133        @Override
134        protected byte[] crypt(final byte[] input, final int mode)
135                throws GeneralSecurityException
136        {
137                SecretKey key = generateSecretKey();
138                AlgorithmParameterSpec spec = createParameterSpec();
139                Cipher ciph = createCipher(key, spec, mode);
140                return ciph.doFinal(input);
141        }
142
143        /**
144         * Creates the {@link javax.crypto.Cipher} that will do the de-/encryption.
145         *
146         * @param key
147         *              the secret key to use
148         * @param spec
149         *              the parameters spec to use
150         * @param mode
151         *              the mode ({@link javax.crypto.Cipher#ENCRYPT_MODE} or {@link javax.crypto.Cipher#DECRYPT_MODE})
152         * @return the cipher that will do the de-/encryption
153         * @throws GeneralSecurityException
154         */
155        protected Cipher createCipher(SecretKey key, AlgorithmParameterSpec spec, int mode) throws GeneralSecurityException
156        {
157                Cipher cipher = Cipher.getInstance(cryptMethod);
158                cipher.init(mode, key, spec);
159                return cipher;
160        }
161
162        /**
163         * Generate the de-/encryption key.
164         * <p>
165         * Note: if you don't provide your own encryption key, the implementation will use a default. Be
166         * aware that this is potential security risk. Thus make sure you always provide your own one.
167         *
168         * @return secretKey the security key generated
169         * @throws NoSuchAlgorithmException
170         *             unable to find encryption algorithm specified
171         * @throws InvalidKeySpecException
172         *             invalid encryption key
173         */
174        protected SecretKey generateSecretKey() throws NoSuchAlgorithmException,
175                InvalidKeySpecException
176        {
177                SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(cryptMethod);
178                KeySpec spec = createKeySpec();
179                return keyFactory.generateSecret(spec);
180        }
181        
182        /**
183         * @return the parameter spec to be used for the configured crypt method
184         */
185        protected AlgorithmParameterSpec createParameterSpec()
186        {
187                return new PBEParameterSpec(salt, iterationCount);
188        }
189
190        /**
191         * @return the key spec to be used for the configured crypt method
192         */
193        protected KeySpec createKeySpec()
194        {
195                return new PBEKeySpec(getKey().toCharArray());
196        }
197
198        /**
199         * Create a random salt to be used for this crypt. 
200         * 
201         * @return salt, always 8 bytes long
202         */
203        public static byte[] randomSalt()
204        {
205                // must be 8 bytes - for anything else PBES1Core throws
206                // InvalidAlgorithmParameterException: Salt must be 8 bytes long  
207                byte[] salt = new byte[8];
208                new Random().nextBytes(salt);
209                return salt;
210        }
211}