001/** 002 * Copyright 2005-2014 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016/* 017 * To change this template, choose Tools | Templates 018 * and open the template in the editor. 019 */ 020package org.kuali.rice.krad.devtools.maintainablexml; 021 022import java.io.UnsupportedEncodingException; 023import java.security.GeneralSecurityException; 024import java.security.MessageDigest; 025 026import javax.crypto.Cipher; 027import javax.crypto.KeyGenerator; 028import javax.crypto.SecretKey; 029import javax.crypto.SecretKeyFactory; 030import javax.crypto.spec.DESKeySpec; 031 032import org.apache.commons.codec.binary.Base64; 033import org.apache.commons.lang.StringUtils; 034 035/** 036 * Implementation of encryption service for demonstration. 037 * This class has been copied from the base rice code but has added an old secret key that allows for data encrypted 038 * with the commons-codec 1.3 api to be decrypted with newer versions of the api. The Base64.decodeBase64 method did not 039 * decode the last two bytes correctly in 1.3 when the encoded key did not end with '=', it always defaulted those 040 * bytes to '1' 041 * 042 * @author Kuali Rice Team (rice.collab@kuali.org) 043 */ 044public class EncryptionService { 045 046 public final static String ALGORITHM = "DES/ECB/PKCS5Padding"; 047 public final static String HASH_ALGORITHM = "SHA"; 048 private final static String CHARSET = "UTF-8"; 049 private transient SecretKey desKey; 050 private transient SecretKey desKeyOld; 051 private boolean isEnabled = false; 052 053 public EncryptionService(String key) throws Exception { 054 if (desKey != null) { 055 throw new RuntimeException("The secret key must be kept secret. Storing it in the Java source code is a really bad idea."); 056 } 057 058 if (!StringUtils.isEmpty(key)) { 059 setSecretKey(key); 060 } 061 } 062 063 public boolean isEnabled() { 064 return isEnabled; 065 } 066 067 public String encrypt(Object valueToHide) throws GeneralSecurityException { 068 checkEnabled(); 069 070 if (valueToHide == null) { 071 return ""; 072 } 073 074 // Initialize the cipher for encryption 075 Cipher cipher = Cipher.getInstance(ALGORITHM); 076 cipher.init(Cipher.ENCRYPT_MODE, getDesKey()); 077 078 try { 079 // Our cleartext 080 byte[] cleartext = valueToHide.toString().getBytes(CHARSET); 081 082 // Encrypt the cleartext 083 byte[] ciphertext = cipher.doFinal(cleartext); 084 085 return new String(Base64.encodeBase64(ciphertext), CHARSET); 086 } catch (Exception e) { 087 throw new RuntimeException(e); 088 } 089 090 } 091 092 public String decrypt(String ciphertext) throws GeneralSecurityException { 093 checkEnabled(); 094 095 if (StringUtils.isBlank(ciphertext)) { 096 return ""; 097 } 098 099 // 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}