/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.tools.cipher;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Key;
import java.util.logging.Level;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import com.rapidminer.RapidMiner;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.LogService;
/**
* This class can be used to generate a new key and store it in the user directory. Please note that
* by default existing keys will be overwritten by objects of this class. That means that passwords
* stored with "old" keys can no longer be decrypted.
*
* Note that the class provides methods to override the default key storage location. Furthermore,
* it is possible to suppress the storage of keys completely. These methods are implemented thread
* safe to allow for scenarios in which the encryption key has to be exchanged (in separate
* threads).
*
* @author Ingo Mierswa, Michael Knopf, Nils Woehler
*/
public class KeyGeneratorTool {
/** Length of the triple DES key. */
private static final int KEY_LENGTH = 168;
/** Use Java's build in triple DES implementation. */
private static final String GENERATOR_TYPE = "DESede";
/** The cipher key provider */
private static CipherKeyProvider cipherKeyProvider = new FileCipherKeyProvider();
/** The encryption key */
private static ThreadLocal<Key> localKey = new ThreadLocal<Key>() {
@Override
public Key initialValue() {
return null;
}
};
/**
* Controls whether a newly created key should be save to disk or not. This allow for the
* encryption with key other than the user key by calling the {@link KeyGeneratorTool} from
* another thread.
*/
private static ThreadLocal<Boolean> saveKeyToDisk = new ThreadLocal<Boolean>() {
@Override
public Boolean initialValue() {
return new Boolean(true);
}
};
/**
* Generates a new random key.
*
* @return The new random key.
* @throws KeyGenerationException
*/
public static SecretKey createSecretKey() throws KeyGenerationException {
return cipherKeyProvider.createKey(KEY_LENGTH, GENERATOR_TYPE);
}
/**
* Generates and stores a new random key. Key storage can be suppressed using
* {@link KeyGeneratorTool#setSaveKeyToDisk(boolean)}.
*
* @throws KeyGenerationException
*/
public static void createAndStoreKey() throws KeyGenerationException {
if (!RapidMiner.getExecutionMode().canAccessFilesystem()) {
LogService.getRoot().log(Level.CONFIG, "com.rapidminer.tools.cipher.KeyGeneratorTool.skip_key_generation",
RapidMiner.getExecutionMode());
return;
}
// actual generation
SecretKey key = cipherKeyProvider.createKey(KEY_LENGTH, GENERATOR_TYPE);
if (key != null) {
localKey.set(key);
}
// save key to disk
if (saveKeyToDisk.get()) {
Exception writeException = null;
try {
cipherKeyProvider.storeKey(key);
} catch (Exception e) {
LogService.getRoot().log(
Level.WARNING,
I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.tools.cipher.KeyGeneratorTool.generating_key_error", e), e);
writeException = e;
}
if (writeException != null) {
throw new KeyGenerationException("Cannot store key: " + writeException.getMessage());
}
}
}
/**
* Stores the specified {@link Key} bytes[] in the given file.
*
* @param rawKey
* @param keyPath
* @throws KeyGenerationException
*/
public static void storeKey(byte[] rawKey, Path keyPath) throws IOException {
Files.deleteIfExists(keyPath);
try (FileOutputStream fos = new FileOutputStream(keyPath.toFile());
ObjectOutputStream out = new ObjectOutputStream(fos)) {
out.writeInt(rawKey.length);
out.write(rawKey);
}
}
/**
* Return the user encryption key stored on disk. The key is only loaded once per session.
*
* @return The user encryption key.
* @throws IOException
*/
public static Key getUserKey() throws IOException {
if (localKey.get() == null) {
try {
localKey.set(cipherKeyProvider.loadKey());
} catch (KeyLoadingException e) {
throw new IOException(e.getLocalizedMessage());
}
}
return localKey.get();
}
/**
* Sets the new user encryption key for the <strong>current thread</strong>.
* <p>
* <strong>THIS KEY IS NEVER STORED ON THE FILESYSTEM!</strong>
* </p>
*
* @param newKey
* the new key
*/
public static void setUserKey(Key newKey) {
localKey.set(newKey);
}
/**
* Converts a raw key into a {@link SecretKeySpec}.
*
* @param rawKey
* The raw key.
* @return
*/
public static SecretKeySpec makeKey(final byte[] rawKey) {
return new SecretKeySpec(rawKey, GENERATOR_TYPE);
}
/**
* Controls whether newly generated keys should be stored permanently.
*
* @param saveKey
* whether the new key should be stored to disk
*/
public static void setSaveKeyToDisk(boolean saveKey) {
saveKeyToDisk.set(saveKey);
}
/**
* Sets the {@link CipherKeyProvider} for the {@link KeyGeneratorTool} utility class
*
* @param keyProvider
* the {@link CipherKeyProvider} to be used
*/
public static void setCipherKeyProvider(CipherKeyProvider keyProvider) {
cipherKeyProvider = keyProvider;
}
}