edu.internet2.middleware.morphString.Crypto Maven / Gradle / Ivy
Show all versions of grouperClient Show documentation
/**
* Copyright 2014 Internet2
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package edu.internet2.middleware.morphString;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import edu.internet2.middleware.grouperClient.util.GrouperClientUtils;
import edu.internet2.middleware.grouperClientExt.org.apache.commons.codec.binary.Base64;
/**
* The purpose of this class is to provide encryption
* and decryption using standard Java libraries, for potentially
* large amounts of data.
*
* This class provides default encryption using AES with a constant
* 128 bit key. If you want something more secure feel free to
* override the defaults however you please.
*
* This class works in one of two ways, (1) in memory using Strings, or (2) via
* I/O streams (preferred for large amounts of data).
*
* Crypo objects, or more specifically the default ciphers they create, are not
* threadsafe and are not computationally cheap, so a threadlocal factory
* method is provided for convenience. This is the preferred means of usage,
* but feel free to create these objects however you please.
*
* Note that you can encrypt BLOB fields by specifying encryption in the
* configurator (Crypto is the default encryption mechanism for that).
*
*/
public class Crypto {
/** threadlocal provided for conveniency */
private static final ThreadLocal threadLocalCrypto = new ThreadLocal();
/** @return a non-null thread-safe crypto object from a ThreadLocal */
public static Crypto getThreadLocalCrypto() {
Crypto crypto = threadLocalCrypto.get();
if (crypto == null) {
crypto = new Crypto();
threadLocalCrypto.set(crypto);
}
return crypto;
}
/**
* Generate a key.
* @param cipherName the name of the cipher, if null will default to "AES"
* @param keybits the number of bits in the key, if null will default to 128
* @return the bytes comprising the key
*/
public static byte[] generateKeyBytes(String cipherName, Integer keybits) {
KeyGenerator keyGenerator = null;
cipherName = cipherName == null ? "AES" : cipherName;
try {
keyGenerator = KeyGenerator.getInstance(cipherName);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to create KeyGenerator for cipherName="+cipherName, e);
}
keyGenerator.init(keybits == null ? 128 : keybits); // 192 and 256 bits may not be available
// Generate the secret key specs.
SecretKey secretKey = keyGenerator.generateKey();
byte[] keyBytes = secretKey.getEncoded();
return keyBytes;
}
/** SecretKeySpec */
private SecretKeySpec key;
/** lazily constructed cipher */
private Cipher cipher = null;
/**
* Create the default cipher
* @return the default cipher
*/
public Cipher createDefaultCipher() {
try {
return Cipher.getInstance("AES");
} catch(Exception x) {
throw new RuntimeException("Failed to create the default cipher!", x);
}
}
/** Default crypto object */
public Crypto() {
this(Morph.key());
}
/** Default crypto object
* @param theKey used to encrypt/decrypt
*/
public Crypto(String theKey) {
super();
init(theKey);
}
/** only warn once */
private static boolean warned = false;
/** initialize the key and cipher
* @param secret
*/
protected void init(String secret) {
if (GrouperClientUtils.isBlank(secret)) {
throw new NullPointerException("Must supply a non blank encrypt.key");
}
StringBuilder secretBuilder = new StringBuilder(secret);
if (!warned && secret.length() < 8) {
//note we dont have a logger
System.out.println("morphString warning: secret.key in morphString.properties should be at least 8 chars");
warned = true;
}
//secret must be length 16 or 32. pad if not
while (secretBuilder.length() < 16) {
secretBuilder.append("x");
}
if (secretBuilder.length() > 16) {
while (secretBuilder.length() < 32) {
secretBuilder.append("x");
}
}
if (secretBuilder.length() > 32) {
secretBuilder.delete(32, secretBuilder.length());
}
secret = secretBuilder.toString();
this.key = new SecretKeySpec(secret.getBytes(), "AES");
this.cipher = this.createDefaultCipher();
}
/**
* Encrypt the string
* @param clearText
* @return the encrypted String
*/
public String encrypt(String clearText) {
byte[] input = clearText.getBytes();
try {
this.initCipher(true);
byte[] output = this.cipher.doFinal(input);
byte[] encoded = Base64.encodeBase64(output);
return new String(encoded);
} catch(Exception x) {
throw new RuntimeException("Failed to encrypt string", x);
}
}
/**
* Decrypt the string
* @param cipherText
* @return the decrypted string
*/
public String decrypt(String cipherText) {
try {
byte[] cipherBytes = cipherText.getBytes();
byte[] decodedBytes = Base64.decodeBase64(cipherBytes);
this.initCipher(false);
byte[] clearBytes = this.cipher.doFinal(decodedBytes);
return new String(clearBytes);
} catch(Exception x) {
throw new RuntimeException("Failed to decrypt the cipherText", x);
}
}
/**
* Initialize the cipher for encryption or decryption
* @param encrypt true to encrypt, false to decrypt
*/
private void initCipher(boolean encrypt) {
try {
if ( encrypt ) {
this.cipher.init(Cipher.ENCRYPT_MODE, this.key);
} else {
this.cipher.init(Cipher.DECRYPT_MODE, this.key);
}
} catch(Exception x) {
throw new RuntimeException("Failed to init cipher for "+(encrypt ? "encrypt" : "decrypt"), x);
}
}
/**
* Get the encrypted input stream
* @param in
* @return the encrypted input stream
*/
public InputStream encrypt(InputStream in) {
this.initCipher(true);
return new CipherInputStream(in, this.cipher);
}
/**
* the decrypted input stream
* @param in
* @return the decrypted input stream
*/
public InputStream decrypt(InputStream in) {
this.initCipher(false);
return new CipherInputStream(in, this.cipher);
}
/**
* the encrypted output stream
* @param out
* @return the encrypted output stream
*/
public OutputStream encrypt(OutputStream out) {
this.initCipher(true);
return new CipherOutputStream(out, this.cipher);
}
/**
* the decrypted output stream
* @param out
* @return the decrypted output stream
*/
public OutputStream decrypt(OutputStream out) {
this.initCipher(false);
return new CipherOutputStream(out, this.cipher);
}
}