001/** 002 * Copyright 2005-2015 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 */ 044@SuppressWarnings({"InsecureCipherMode", "InsecureCryptoUsage"}) 045public class EncryptionService { 046 047 public final static String ALGORITHM = "DES/ECB/PKCS5Padding"; 048 public final static String HASH_ALGORITHM = "SHA"; 049 private final static String CHARSET = "UTF-8"; 050 private transient SecretKey desKey; 051 private transient SecretKey desKeyOld; 052 private boolean isEnabled = false; 053 054 public EncryptionService(String key) throws Exception { 055 if (desKey != null) { 056 throw new RuntimeException("The secret key must be kept secret. Storing it in the Java source code is a really bad idea."); 057 } 058 059 if (!StringUtils.isEmpty(key)) { 060 setSecretKey(key); 061 } 062 } 063 064 public boolean isEnabled() { 065 return isEnabled; 066 } 067 068 public String encrypt(Object valueToHide) throws GeneralSecurityException { 069 checkEnabled(); 070 071 if (valueToHide == null) { 072 return ""; 073 } 074 075 // Initialize the cipher for encryption 076 Cipher cipher = Cipher.getInstance(ALGORITHM); 077 cipher.init(Cipher.ENCRYPT_MODE, getDesKey()); 078 079 try { 080 // Our cleartext 081 byte[] cleartext = valueToHide.toString().getBytes(CHARSET); 082 083 // Encrypt the cleartext 084 byte[] ciphertext = cipher.doFinal(cleartext); 085 086 return new String(Base64.encodeBase64(ciphertext), CHARSET); 087 } catch (Exception e) { 088 throw new RuntimeException(e); 089 } 090 091 } 092 093 public String decrypt(String ciphertext) throws GeneralSecurityException { 094 checkEnabled(); 095 096 if (StringUtils.isBlank(ciphertext)) { 097 return ""; 098 } 099 100 // Initialize the same cipher for decryption 101 Cipher cipher = Cipher.getInstance(ALGORITHM); 102 cipher.init(Cipher.DECRYPT_MODE, getDesKey()); 103 104 try { 105 // un-Base64 encode the encrypted data 106 byte[] encryptedData = Base64.decodeBase64(ciphertext.getBytes(CHARSET)); 107 108 // Decrypt the ciphertext 109 byte[] cleartext1 = cipher.doFinal(encryptedData); 110 return new String(cleartext1, CHARSET); 111 } catch (Exception e) { 112 Cipher cipher2 = Cipher.getInstance(ALGORITHM); 113 cipher2.init(Cipher.DECRYPT_MODE, getDesKeyOld()); 114 115 try { 116 // un-Base64 encode the encrypted data 117 byte[] encryptedData = Base64.decodeBase64(ciphertext.getBytes(CHARSET)); 118 119 // Decrypt the ciphertext 120 byte[] cleartext1 = cipher2.doFinal(encryptedData); 121 return new String(cleartext1, CHARSET); 122 } catch (UnsupportedEncodingException ex) { 123 throw new RuntimeException(e); 124 } 125 } 126 } 127 128 public byte[] encryptBytes(byte[] valueToHide) throws GeneralSecurityException { 129 checkEnabled(); 130 131 if (valueToHide == null) { 132 return new byte[0]; 133 } 134 135 // Initialize the cipher for encryption 136 Cipher cipher = Cipher.getInstance(ALGORITHM); 137 cipher.init(Cipher.ENCRYPT_MODE, getDesKey()); 138 139 // Our cleartext 140 byte[] cleartext = valueToHide; 141 142 // Encrypt the cleartext 143 byte[] ciphertext = cipher.doFinal(cleartext); 144 145 return ciphertext; 146 } 147 148 public byte[] decryptBytes(byte[] ciphertext) throws GeneralSecurityException { 149 checkEnabled(); 150 151 if (ciphertext == null) { 152 return new byte[0]; 153 } 154 155 // Initialize the same cipher for decryption 156 Cipher cipher = Cipher.getInstance(ALGORITHM); 157 cipher.init(Cipher.DECRYPT_MODE, getDesKey()); 158 159 // un-Base64 encode the encrypted data 160 byte[] encryptedData = ciphertext; 161 162 // Decrypt the ciphertext 163 byte[] cleartext1 = cipher.doFinal(encryptedData); 164 return cleartext1; 165 } 166 167 /** 168 * 169 * This method generates keys. This method is implementation specific and should not be present in any general purpose interface 170 * extracted from this class. 171 * 172 * @return 173 * @throws Exception 174 */ 175 public static String generateEncodedKey() throws Exception { 176 KeyGenerator keygen = KeyGenerator.getInstance("DES"); 177 SecretKey desKey = keygen.generateKey(); 178 179 // Create the cipher 180 Cipher cipher = Cipher.getInstance(ALGORITHM); 181 cipher.init((Cipher.WRAP_MODE), desKey); 182 183 SecretKeyFactory desFactory = SecretKeyFactory.getInstance("DES"); 184 DESKeySpec desSpec = (DESKeySpec) desFactory.getKeySpec(desKey, javax.crypto.spec.DESKeySpec.class); 185 byte[] rawDesKey = desSpec.getKey(); 186 187 return new String(Base64.encodeBase64(rawDesKey)); 188 } 189 190 private SecretKey unwrapEncodedKey(String key) throws Exception { 191 KeyGenerator keygen = KeyGenerator.getInstance("DES"); 192 SecretKey desKey = keygen.generateKey(); 193 194 // Create the cipher 195 Cipher cipher = Cipher.getInstance(ALGORITHM); 196 cipher.init((Cipher.UNWRAP_MODE), desKey); 197 198 byte[] bytes = Base64.decodeBase64(key.getBytes()); 199 200 SecretKeyFactory desFactory = SecretKeyFactory.getInstance("DES"); 201 202 DESKeySpec keyspec = new DESKeySpec(bytes); 203 SecretKey k = desFactory.generateSecret(keyspec); 204 205 return k; 206 207 } 208 209 private SecretKey unwrapEncodedKeyOld(String key) throws Exception { 210 KeyGenerator keygen = KeyGenerator.getInstance("DES"); 211 SecretKey desKey = keygen.generateKey(); 212 213 // Create the cipher 214 Cipher cipher = Cipher.getInstance(ALGORITHM); 215 cipher.init((Cipher.UNWRAP_MODE), desKey); 216 217 byte[] bytes = Base64.decodeBase64(key.getBytes()); 218 219 220 // If decoding was done with commons-codec 1.3 and the key not ended with '=' 221 bytes[6] = 1; 222 bytes[7] = 1; 223 224 SecretKeyFactory desFactory = SecretKeyFactory.getInstance("DES"); 225 226 DESKeySpec keyspec = new DESKeySpec(bytes); 227 SecretKey k = desFactory.generateSecret(keyspec); 228 229 return k; 230 231 } 232 233 /** 234 * Sets the secretKey attribute value. 235 * 236 * @param secretKey The secretKey to set. 237 * @throws Exception 238 */ 239 public void setSecretKey(String secretKey) throws Exception { 240 if (!StringUtils.isEmpty(secretKey)) { 241 desKey = this.unwrapEncodedKey(secretKey); 242 setDesKeyOld(this.unwrapEncodedKeyOld(secretKey)); 243 isEnabled = true; 244 // Create the cipher 245 Cipher cipher = Cipher.getInstance(ALGORITHM); 246 cipher.init((Cipher.WRAP_MODE), getDesKey()); 247 } 248 } 249 250 /** Hash the value by converting to a string, running the hash algorithm, and then base64'ng the results. 251 * Returns a blank string if any problems occur or the input value is null or empty. 252 * 253 */ 254 public String hash(Object valueToHide) throws GeneralSecurityException { 255 if (valueToHide == null || StringUtils.isEmpty(valueToHide.toString())) { 256 return ""; 257 } 258 try { 259 MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM); 260 return new String(Base64.encodeBase64(md.digest(valueToHide.toString().getBytes(CHARSET))), CHARSET); 261 } catch (UnsupportedEncodingException ex) { 262 // should never happen 263 } 264 return ""; 265 } 266 267 /** 268 * Performs a check to see if the encryption service is enabled. If it is not then an 269 * IllegalStateException will be thrown. 270 */ 271 protected void checkEnabled() { 272 if (!isEnabled()) { 273 throw new IllegalStateException("Illegal use of encryption service. Encryption service is disabled, to enable please configure 'encryption.key'."); 274 } 275 } 276 277 /** 278 * @return the desKey 279 */ 280 public SecretKey getDesKey() { 281 return desKey; 282 } 283 284 /** 285 * @return the desKeyOld 286 */ 287 public SecretKey getDesKeyOld() { 288 return desKeyOld; 289 } 290 291 /** 292 * @param desKeyOld the desKeyOld to set 293 */ 294 public void setDesKeyOld(SecretKey desKeyOld) { 295 this.desKeyOld = desKeyOld; 296 } 297}