View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  /*
17   * To change this template, choose Tools | Templates
18   * and open the template in the editor.
19   */
20  package org.kuali.rice.krad.devtools.maintainablexml;
21  
22  import java.io.UnsupportedEncodingException;
23  import java.security.GeneralSecurityException;
24  import java.security.MessageDigest;
25  
26  import javax.crypto.Cipher;
27  import javax.crypto.KeyGenerator;
28  import javax.crypto.SecretKey;
29  import javax.crypto.SecretKeyFactory;
30  import javax.crypto.spec.DESKeySpec;
31  
32  import org.apache.commons.codec.binary.Base64;
33  import org.apache.commons.lang.StringUtils;
34  
35  /**
36   * Implementation of encryption service for demonstration.
37   * This class has been copied from the base rice code but has added an old secret key that allows for data encrypted
38   * with the commons-codec 1.3 api to be decrypted with newer versions of the api. The Base64.decodeBase64 method did not
39   * decode the last two bytes correctly in 1.3 when the encoded key did not end with '=', it always defaulted those
40   * bytes to '1'
41   * 
42   * @author Kuali Rice Team (rice.collab@kuali.org)
43   */
44  public class EncryptionService {
45  
46      public final static String ALGORITHM = "DES/ECB/PKCS5Padding";
47      public final static String HASH_ALGORITHM = "SHA";
48      private final static String CHARSET = "UTF-8";
49      private transient SecretKey desKey;
50      private transient SecretKey desKeyOld;
51      private boolean isEnabled = false;
52  
53      public EncryptionService(String key) throws Exception {
54          if (desKey != null) {
55              throw new RuntimeException("The secret key must be kept secret. Storing it in the Java source code is a really bad idea.");
56          }
57  
58          if (!StringUtils.isEmpty(key)) {
59              setSecretKey(key);
60          }
61      }
62  
63      public boolean isEnabled() {
64          return isEnabled;
65      }
66  
67      public String encrypt(Object valueToHide) throws GeneralSecurityException {
68          checkEnabled();
69  
70          if (valueToHide == null) {
71              return "";
72          }
73  
74          // Initialize the cipher for encryption
75          Cipher cipher = Cipher.getInstance(ALGORITHM);
76          cipher.init(Cipher.ENCRYPT_MODE, getDesKey());
77  
78          try {
79              // Our cleartext
80              byte[] cleartext = valueToHide.toString().getBytes(CHARSET);
81  
82              // Encrypt the cleartext
83              byte[] ciphertext = cipher.doFinal(cleartext);
84  
85              return new String(Base64.encodeBase64(ciphertext), CHARSET);
86          } catch (Exception e) {
87              throw new RuntimeException(e);
88          }
89  
90      }
91  
92      public String decrypt(String ciphertext) throws GeneralSecurityException {
93          checkEnabled();
94  
95          if (StringUtils.isBlank(ciphertext)) {
96              return "";
97          }
98  
99          // Initialize the same cipher for decryption
100         Cipher cipher = Cipher.getInstance(ALGORITHM);
101         cipher.init(Cipher.DECRYPT_MODE, getDesKey());
102 
103         try {
104             // un-Base64 encode the encrypted data
105             byte[] encryptedData = Base64.decodeBase64(ciphertext.getBytes(CHARSET));
106 
107             // Decrypt the ciphertext
108             byte[] cleartext1 = cipher.doFinal(encryptedData);
109             return new String(cleartext1, CHARSET);
110         } catch (Exception e) {
111             Cipher cipher2 = Cipher.getInstance(ALGORITHM);
112             cipher2.init(Cipher.DECRYPT_MODE, getDesKeyOld());
113 
114             try {
115                 // un-Base64 encode the encrypted data
116                 byte[] encryptedData = Base64.decodeBase64(ciphertext.getBytes(CHARSET));
117 
118                 // Decrypt the ciphertext
119                 byte[] cleartext1 = cipher2.doFinal(encryptedData);
120                 return new String(cleartext1, CHARSET);
121             } catch (UnsupportedEncodingException ex) {
122                 throw new RuntimeException(e);
123             }            
124         }
125     }
126 
127     public byte[] encryptBytes(byte[] valueToHide) throws GeneralSecurityException {
128         checkEnabled();
129 
130         if (valueToHide == null) {
131             return new byte[0];
132         }
133 
134         // Initialize the cipher for encryption
135         Cipher cipher = Cipher.getInstance(ALGORITHM);
136         cipher.init(Cipher.ENCRYPT_MODE, getDesKey());
137 
138         // Our cleartext
139         byte[] cleartext = valueToHide;
140 
141         // Encrypt the cleartext
142         byte[] ciphertext = cipher.doFinal(cleartext);
143 
144         return ciphertext;
145     }
146 
147     public byte[] decryptBytes(byte[] ciphertext) throws GeneralSecurityException {
148         checkEnabled();
149 
150         if (ciphertext == null) {
151             return new byte[0];
152         }
153 
154         // Initialize the same cipher for decryption
155         Cipher cipher = Cipher.getInstance(ALGORITHM);
156         cipher.init(Cipher.DECRYPT_MODE, getDesKey());
157 
158         // un-Base64 encode the encrypted data
159         byte[] encryptedData = ciphertext;
160 
161         // Decrypt the ciphertext
162         byte[] cleartext1 = cipher.doFinal(encryptedData);
163         return cleartext1;
164     }
165 
166     /**
167      * 
168      * This method generates keys. This method is implementation specific and should not be present in any general purpose interface
169      * extracted from this class.
170      * 
171      * @return
172      * @throws Exception
173      */
174     public static String generateEncodedKey() throws Exception {
175         KeyGenerator keygen = KeyGenerator.getInstance("DES");
176         SecretKey desKey = keygen.generateKey();
177 
178         // Create the cipher
179         Cipher cipher = Cipher.getInstance(ALGORITHM);
180         cipher.init((Cipher.WRAP_MODE), desKey);
181 
182         SecretKeyFactory desFactory = SecretKeyFactory.getInstance("DES");
183         DESKeySpec desSpec = (DESKeySpec) desFactory.getKeySpec(desKey, javax.crypto.spec.DESKeySpec.class);
184         byte[] rawDesKey = desSpec.getKey();
185 
186         return new String(Base64.encodeBase64(rawDesKey));
187     }
188 
189     private SecretKey unwrapEncodedKey(String key) throws Exception {
190         KeyGenerator keygen = KeyGenerator.getInstance("DES");
191         SecretKey desKey = keygen.generateKey();
192 
193         // Create the cipher
194         Cipher cipher = Cipher.getInstance(ALGORITHM);
195         cipher.init((Cipher.UNWRAP_MODE), desKey);
196 
197         byte[] bytes = Base64.decodeBase64(key.getBytes());
198 
199         SecretKeyFactory desFactory = SecretKeyFactory.getInstance("DES");
200 
201         DESKeySpec keyspec = new DESKeySpec(bytes);
202         SecretKey k = desFactory.generateSecret(keyspec);
203 
204         return k;
205 
206     }
207 
208     private SecretKey unwrapEncodedKeyOld(String key) throws Exception {
209         KeyGenerator keygen = KeyGenerator.getInstance("DES");
210         SecretKey desKey = keygen.generateKey();
211 
212         // Create the cipher
213         Cipher cipher = Cipher.getInstance(ALGORITHM);
214         cipher.init((Cipher.UNWRAP_MODE), desKey);
215 
216         byte[] bytes = Base64.decodeBase64(key.getBytes());
217 
218 
219         // If decoding was done with commons-codec 1.3 and the key not ended with '='
220         bytes[6] = 1;
221         bytes[7] = 1;
222 
223         SecretKeyFactory desFactory = SecretKeyFactory.getInstance("DES");
224 
225         DESKeySpec keyspec = new DESKeySpec(bytes);
226         SecretKey k = desFactory.generateSecret(keyspec);
227 
228         return k;
229 
230     }
231 
232     /**
233      * Sets the secretKey attribute value.
234      * 
235      * @param secretKey The secretKey to set.
236      * @throws Exception
237      */
238     public void setSecretKey(String secretKey) throws Exception {
239         if (!StringUtils.isEmpty(secretKey)) {
240             desKey = this.unwrapEncodedKey(secretKey);
241             setDesKeyOld(this.unwrapEncodedKeyOld(secretKey));
242             isEnabled = true;
243             // Create the cipher
244             Cipher cipher = Cipher.getInstance(ALGORITHM);
245             cipher.init((Cipher.WRAP_MODE), getDesKey());
246         }
247     }
248 
249     /** Hash the value by converting to a string, running the hash algorithm, and then base64'ng the results.
250      * Returns a blank string if any problems occur or the input value is null or empty.
251      *
252      */
253     public String hash(Object valueToHide) throws GeneralSecurityException {
254         if (valueToHide == null || StringUtils.isEmpty(valueToHide.toString())) {
255             return "";
256         }
257         try {
258             MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM);
259             return new String(Base64.encodeBase64(md.digest(valueToHide.toString().getBytes(CHARSET))), CHARSET);
260         } catch (UnsupportedEncodingException ex) {
261             // should never happen
262         }
263         return "";
264     }
265 
266     /**
267      * Performs a check to see if the encryption service is enabled.  If it is not then an
268      * IllegalStateException will be thrown.
269      */
270     protected void checkEnabled() {
271         if (!isEnabled()) {
272             throw new IllegalStateException("Illegal use of encryption service.  Encryption service is disabled, to enable please configure 'encryption.key'.");
273         }
274     }
275 
276     /**
277      * @return the desKey
278      */
279     public SecretKey getDesKey() {
280         return desKey;
281     }
282 
283     /**
284      * @return the desKeyOld
285      */
286     public SecretKey getDesKeyOld() {
287         return desKeyOld;
288     }
289 
290     /**
291      * @param desKeyOld the desKeyOld to set
292      */
293     public void setDesKeyOld(SecretKey desKeyOld) {
294         this.desKeyOld = desKeyOld;
295     }
296 }