com.emc.codec.encryption.EncryptionCodec Maven / Gradle / Ivy
/*
* Copyright (c) 2015-2016, EMC Corporation.
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* + Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* + Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* + The name of EMC Corporation may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package com.emc.codec.encryption;
import com.emc.codec.*;
import com.emc.codec.util.CodecUtil;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.util.Map;
public class EncryptionCodec extends AbstractCodec {
private static final Logger log = LoggerFactory.getLogger(EncryptionCodec.class);
public static final int PRIORITY = 1000;
public static final String SECURE_RANDOM_INSTANCE = "SHA1PRNG";
public static final String AES_CBC_PKCS5_CIPHER = "AES/CBC/PKCS5Padding";
public static final String PROP_KEY_SIZE = "com.emc.codec.encryption.EncryptionCodec.keySize";
public static final String PROP_KEY_PROVIDER = "com.emc.codec.encryption.EncryptionCodec.keyProvider";
public static final String PROP_SECURITY_PROVIDER = "com.emc.codec.encryption.EncryptionCodec.securityProvider";
public static final int DEFAULT_KEY_SIZE = 128;
public static String encodeSpec(String cipherSpec) {
return CodecUtil.getEncodeSpec(EncryptionConstants.ENCRYPTION_TYPE, cipherSpec);
}
public static int getKeySize(Map codecProperties) {
return CodecUtil.getCodecProperty(PROP_KEY_SIZE, codecProperties, DEFAULT_KEY_SIZE);
}
public static void setKeySize(Map codecProperties, int keySize) {
codecProperties.put(PROP_KEY_SIZE, keySize);
}
public static KeyProvider getKeyProvider(Map codecProperties) {
return CodecUtil.getCodecProperty(PROP_KEY_PROVIDER, codecProperties, null);
}
public static void setKeyProvider(Map codecProperties, KeyProvider keyProvider) {
codecProperties.put(PROP_KEY_PROVIDER, keyProvider);
}
public static Provider getSecurityProvider(Map codecProperties) {
return CodecUtil.getCodecProperty(PROP_SECURITY_PROVIDER, codecProperties, null);
}
public static void setPropSecurityProvider(Map codecProperties, Provider securityProvider) {
codecProperties.put(PROP_SECURITY_PROVIDER, securityProvider);
}
@Override
public boolean canProcess(String encodeSpec) {
if (!EncryptionConstants.ENCRYPTION_TYPE.equals(CodecUtil.getEncodeType(encodeSpec))) return false;
String cipherSpec = EncryptionUtil.getCipherSpec(encodeSpec);
try {
Cipher.getInstance(cipherSpec);
return true;
} catch (Exception e) {
log.warn("cannot process cipher " + cipherSpec, e);
}
return false;
}
@Override
public String getDefaultEncodeSpec() {
return encodeSpec(AES_CBC_PKCS5_CIPHER);
}
@Override
public int getPriority() {
return PRIORITY;
}
@Override
public EncryptionMetadata createEncodeMetadata(String encodeSpec, Map metaMap) {
return new EncryptionMetadata(encodeSpec, metaMap);
}
@Override
public long getDecodedSize(EncryptionMetadata encodeInfo) {
return encodeInfo.getOriginalSize();
}
@Override
public OutputStream getDecodingStream(OutputStream originalStream, EncryptionMetadata metadata,
Map codecProperties) {
KeyProvider keyProvider = _getKeyProvider(codecProperties);
Provider provider = getSecurityProvider(codecProperties);
return new CipherOutputStream(originalStream, initDecryptCipher(metadata, keyProvider, provider));
}
@Override
public InputStream getDecodingStream(InputStream originalStream, EncryptionMetadata metadata,
Map codecProperties) {
KeyProvider keyProvider = _getKeyProvider(codecProperties);
Provider provider = getSecurityProvider(codecProperties);
return new CipherInputStream(originalStream, initDecryptCipher(metadata, keyProvider, provider));
}
@Override
public boolean isSizePredictable() {
return true;
}
@Override
public long getEncodedSize(long originalSize, String encodeSpec, Map codecProperties) {
String cipherSpec = EncryptionUtil.getCipherSpec(encodeSpec);
Provider provider = getSecurityProvider(codecProperties);
SecretKey key = generateKey(cipherSpec, getKeySize(codecProperties), provider);
Cipher cipher = initEncryptCipher(cipherSpec, key, provider);
long trailer = originalSize % cipher.getBlockSize();
long trunc = originalSize - trailer;
try {
return trunc + cipher.doFinal(new byte[(int) trailer]).length;
} catch (Exception e) {
throw new UnsupportedOperationException("Cipher error", e);
}
}
@Override
public EncodeOutputStream getEncodingStream(OutputStream originalStream, String encodeSpec,
Map codecProperties) {
String cipherSpec = EncryptionUtil.getCipherSpec(encodeSpec);
Provider provider = getSecurityProvider(codecProperties);
KeyProvider keyProvider = _getKeyProvider(codecProperties);
SecretKey key = generateKey(cipherSpec, getKeySize(codecProperties), provider);
Cipher cipher = initEncryptCipher(cipherSpec, key, provider);
String encryptedKey = encryptKey(key, keyProvider.getMasterKey(), provider);
EncryptionOutputStream eos = new EncryptionOutputStream(originalStream, encodeSpec, cipher, encryptedKey);
eos.addListener(new SigningEncodeMetadataListener(keyProvider, provider));
return eos;
}
@Override
public EncodeInputStream getEncodingStream(InputStream originalStream, String encodeSpec,
Map codecProperties) {
String cipherSpec = EncryptionUtil.getCipherSpec(encodeSpec);
Provider provider = getSecurityProvider(codecProperties);
KeyProvider keyProvider = _getKeyProvider(codecProperties);
SecretKey key = generateKey(cipherSpec, getKeySize(codecProperties), provider);
Cipher cipher = initEncryptCipher(cipherSpec, key, provider);
String encryptedKey = encryptKey(key, keyProvider.getMasterKey(), provider);
EncryptionInputStream eis = new EncryptionInputStream(originalStream, encodeSpec, cipher, encryptedKey);
eis.addListener(new SigningEncodeMetadataListener(keyProvider, provider));
return eis;
}
public void rekey(Map metaMap, Map codecProperties) {
// find the encryption spec in the metadata
String encryptSpec = null;
for (String spec : CodecChain.getEncodeSpecs(metaMap)) {
if (canDecode(spec)) {
encryptSpec = spec;
break;
}
}
if (encryptSpec == null) throw new IllegalArgumentException("object is not encrypted");
EncryptionMetadata metadata = new EncryptionMetadata(encryptSpec, metaMap);
rekey(metadata, codecProperties);
metaMap.putAll(metadata.toMap());
}
public void rekey(EncryptionMetadata metadata, Map codecProperties) {
KeyProvider keyProvider = getKeyProvider(codecProperties);
Provider provider = getSecurityProvider(codecProperties);
if (metadata.getMasterKeyFingerprint().equals(keyProvider.getMasterKeyFingerprint()))
throw new DoesNotNeedRekeyException("Object is already using the current master key");
KeyPair oldKey = keyProvider.getKey(metadata.getMasterKeyFingerprint());
// make sure we have the old key
if (oldKey == null)
throw new EncryptionException(String.format("Master key with fingerprint %s not found",
metadata.getMasterKeyFingerprint()));
// decrypt object key
SecretKey objectKey = metadata.getSecretKey((RSAPrivateKey) oldKey.getPrivate(), provider);
// re-encrypt object key with the current master key
metadata.setSecretKey(objectKey, keyProvider.getMasterKey().getPublic(), provider);
metadata.setMasterKeyFingerprint(keyProvider.getMasterKeyFingerprint());
// re-sign metadata
metadata.sign((RSAPrivateKey) keyProvider.getMasterKey().getPrivate(), provider);
}
protected Cipher initEncryptCipher(String cipherSpec, SecretKey key, Provider provider) {
try {
Cipher cipher = createCipher(cipherSpec, provider);
cipher.init(Cipher.ENCRYPT_MODE, key, getSecureRandom(provider));
return cipher;
} catch (GeneralSecurityException e) {
throw new EncryptionException("Error initializing cipher", e);
}
}
protected Cipher initDecryptCipher(EncryptionMetadata metadata, KeyProvider keyProvider, Provider provider) {
try {
String cipherSpec = EncryptionUtil.getCipherSpec(metadata.getEncodeSpec());
Cipher cipher = createCipher(cipherSpec, provider);
KeyPair masterKey = keyProvider.getKey(metadata.getMasterKeyFingerprint());
if (masterKey == null)
throw new EncryptionException(String.format("Could not decrypt object. no master key with ID %s found",
metadata.getMasterKeyFingerprint()));
SecretKey key = metadata.getSecretKey((RSAPrivateKey) masterKey.getPrivate(), provider);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(metadata.getInitVector()));
return cipher;
} catch (GeneralSecurityException e) {
throw new EncryptionException("Error initializing cipher", e);
}
}
protected Cipher createCipher(String cipherSpec, Provider provider) {
try {
if (provider != null) {
return Cipher.getInstance(cipherSpec, provider);
} else {
return Cipher.getInstance(cipherSpec);
}
} catch (GeneralSecurityException e) {
throw new UnsupportedOperationException("Could not get cipher instance for algorithm " + cipherSpec, e);
}
}
protected SecretKey generateKey(String cipherSpec, int keySize, Provider provider) {
String baseAlgorithm = EncryptionUtil.getBaseAlgorithm(cipherSpec);
try {
if (keySize > Cipher.getMaxAllowedKeyLength(cipherSpec))
throw new InvalidKeyException(String.format("Key size of %d bits is larger than the maximum allowed of %d",
keySize, Cipher.getMaxAllowedKeyLength(cipherSpec)));
KeyGenerator keygen;
if (provider != null) {
keygen = KeyGenerator.getInstance(baseAlgorithm, provider);
} else {
keygen = KeyGenerator.getInstance(baseAlgorithm);
}
keygen.init(keySize, getSecureRandom(provider));
return keygen.generateKey();
} catch (GeneralSecurityException e) {
throw new UnsupportedOperationException("Could not generate key for algorithm " + baseAlgorithm, e);
}
}
public String encryptKey(SecretKey key, KeyPair masterKey, Provider provider) {
return EncryptionUtil.encryptKey(key, provider, masterKey.getPublic());
}
protected KeyProvider _getKeyProvider(Map codecProperties) {
KeyProvider keyProvider = getKeyProvider(codecProperties);
if (keyProvider == null) throw new EncryptionException("no key provider specified");
return keyProvider;
}
protected SecureRandom getSecureRandom(Provider provider) {
try {
// Per FIPS bulletin 2013-09, make sure we don't use Dual_EC_DRBG
if (provider != null)
return SecureRandom.getInstance(SECURE_RANDOM_INSTANCE, provider);
else return SecureRandom.getInstance(SECURE_RANDOM_INSTANCE);
} catch (GeneralSecurityException e) {
throw new UnsupportedOperationException("Could not get secure random instance for " + SECURE_RANDOM_INSTANCE, e);
}
}
protected class SigningEncodeMetadataListener implements EncodeListener {
private KeyProvider keyProvider;
private Provider provider;
public SigningEncodeMetadataListener(KeyProvider keyProvider, Provider provider) {
this.keyProvider = keyProvider;
this.provider = provider;
}
@Override
public void encodeComplete(EncodeStream encodeStream) {
encodeStream.getEncodeMetadata().setMasterKeyFingerprint(keyProvider.getMasterKeyFingerprint());
encodeStream.getEncodeMetadata().sign((RSAPrivateKey) keyProvider.getMasterKey().getPrivate(), provider);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy