View Javadoc
1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.commons.crypto.cipher;
19  
20  import java.security.GeneralSecurityException;
21  import java.util.List;
22  import java.util.Properties;
23  
24  import org.apache.commons.crypto.Crypto;
25  import org.apache.commons.crypto.utils.ReflectionUtils;
26  import org.apache.commons.crypto.utils.Utils;
27  
28  /**
29   * This is the factory class used for creating {@link CryptoCipher} instances.
30   */
31  public class CryptoCipherFactory {
32  
33      /**
34       * The configuration key of the provider class for JCE cipher.
35       */
36      public static final String JCE_PROVIDER_KEY = Crypto.CONF_PREFIX
37              + "cipher.jce.provider";
38      /**
39       * The configuration key of the CryptoCipher implementation class.
40       * <p>
41       * The value of CLASSES_KEY needs to be the full name of a
42       * class that implements the
43       * {@link org.apache.commons.crypto.cipher.CryptoCipher CryptoCipher} interface
44       * The internal classes are listed in the enum
45       * {@link CipherProvider CipherProvider}
46       * which can be used to obtain the full class name.
47       * <p>
48       * The value can also be a comma-separated list of class names in
49       * order of descending priority.
50       */
51  
52      public static final String CLASSES_KEY = Crypto.CONF_PREFIX
53              + "cipher.classes";
54  
55      /**
56       * Defines the internal CryptoCipher implementations.
57       * <p>
58       * Usage:
59       * <blockquote><pre>
60       * props.setProperty(CryptoCipherFactory.CLASSES_KEY, CipherProvider.OPENSSL.getClassName());
61       * props.setProperty(...); // if required by the implementation
62       * cipher = CryptoCipherFactory.getInstance(transformation, props);
63       * </pre></blockquote>
64       */
65      public enum CipherProvider {
66  
67          /**
68           * The OpenSSL cipher implementation (using JNI)
69           * <p>
70           * This implementation does not use any properties
71           */
72          // Please ensure the property description agrees with the implementation
73          OPENSSL(OpenSslCipher.class),
74  
75          /**
76           * The JCE cipher implementation from the JVM
77           * <p>
78           * uses the property {@link #JCE_PROVIDER_KEY}
79           * to define the provider name, if present.
80           */
81          // Please ensure the property description agrees with the implementation
82          JCE(JceCipher.class);
83  
84          private final Class<? extends CryptoCipher> klass;
85  
86          private final String className;
87  
88          /**
89           * The private constructor.
90           * @param klass the Class of CryptoCipher
91           */
92          private CipherProvider(Class<? extends CryptoCipher> klass) {
93              this.klass = klass;
94              this.className = klass.getName();
95          }
96  
97          /**
98           * Gets the class name of the provider.
99           *
100          * @return the fully qualified name of the provider class
101          */
102         public String getClassName() {
103             return className;
104         }
105 
106         /**
107          * Gets the implementation class of the provider.
108          *
109          * @return the implementation class of the provider
110          */
111         public Class<? extends CryptoCipher> getImplClass() {
112             return klass;
113         }
114     }
115 
116     /**
117      * For AES, the algorithm block is fixed size of 128 bits.
118      *
119      * @see <a href="http://en.wikipedia.org/wiki/Advanced_Encryption_Standard">
120      *      http://en.wikipedia.org/wiki/Advanced_Encryption_Standard</a>;
121      */
122     public static final int AES_BLOCK_SIZE = 16;
123 
124     /**
125      * The default value (OPENSSL,JCE) for crypto cipher.
126      */
127     private static final String CLASSES_DEFAULT =
128             CipherProvider.OPENSSL.getClassName()
129             .concat(",")
130             .concat(CipherProvider.JCE.getClassName());
131     /**
132      * The private Constructor of {@link CryptoCipherFactory}.
133      */
134     private CryptoCipherFactory() {
135     }
136 
137     /**
138      * Gets a cipher instance for specified algorithm/mode/padding.
139      *
140      * @param props  the configuration properties - uses {@link #CLASSES_KEY}
141      * @param transformation  algorithm/mode/padding
142      * @return CryptoCipher  the cipher  (defaults to OpenSslCipher)
143      * @throws GeneralSecurityException if cipher initialize failed
144      * @throws IllegalArgumentException if no classname(s) were provided
145      */
146     public static CryptoCipher getCryptoCipher(String transformation,
147                                            Properties props) throws GeneralSecurityException {
148 
149         final List<String> names = Utils.splitClassNames(getCipherClassString(props), ",");
150         if (names.size() == 0) {
151             throw new IllegalArgumentException("No classname(s) provided");            
152         }
153         CryptoCipher cipher = null;
154         Exception lastException = null;
155 
156         StringBuilder errorMessage = new StringBuilder("CryptoCipher ");
157         for (String klass : names) {
158             try {
159                 Class<?> cls = ReflectionUtils.getClassByName(klass);
160                 cipher = ReflectionUtils.newInstance(cls.asSubclass
161                         (CryptoCipher.class), props, transformation);
162                 if (cipher != null) {
163                     break;
164                 }
165             } catch (Exception e) {
166                 lastException = e;
167                 errorMessage.append("{" + klass + "}");
168             }
169         }
170 
171         if (cipher != null) {
172             return cipher;
173         }
174         errorMessage.append(" is not available or transformation " +
175                 transformation + " is not supported.");
176         throw new GeneralSecurityException(errorMessage.toString(), lastException);
177     }
178 
179     /**
180      * Gets a cipher for algorithm/mode/padding in config value
181      * commons.crypto.cipher.transformation
182      *
183      * @param transformation the name of the transformation, e.g.,
184      * <i>AES/CBC/PKCS5Padding</i>.
185      * See the Java Cryptography Architecture Standard Algorithm Name Documentation
186      * for information about standard transformation names.
187      * @return CryptoCipher the cipher object (defaults to OpenSslCipher if available, else JceCipher)
188      * @throws GeneralSecurityException if JCE cipher initialize failed
189      */
190     public static CryptoCipher getCryptoCipher(String transformation)
191             throws GeneralSecurityException {
192         return getCryptoCipher(transformation, new Properties());
193     }
194 
195     /**
196      * Gets the cipher class.
197      *
198      * @param props The <code>Properties</code> class represents a set of
199      *        properties.
200      * @return the cipher class based on the props.
201      */
202     private static String getCipherClassString(Properties props) {
203         String cipherClassString = props.getProperty(CryptoCipherFactory.CLASSES_KEY, CLASSES_DEFAULT);
204         if (cipherClassString.isEmpty()) { // TODO does it make sense to treat the empty string as the default?
205             cipherClassString = CLASSES_DEFAULT;
206         }
207         return cipherClassString;
208     }
209 
210 }