blob: 154ea952742fb89a793d5a16e596a9564345d5cc [file] [log] [blame]
/*
* Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.security;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.helper.Helper;
/**
* EclipseLink reference implementation for password encryption.
*
* @author Guy Pelletier
*/
public class JCEEncryptor implements Securable {
// Legacy DES ECB cipher used for backwards compatibility decryption only.
private static final String DES_ECB = "DES/ECB/PKCS5Padding";
private final Cipher decryptCipherDES_ECB;
// Legacy AES ECB cipher used for backwards compatibility decryption only.
private static final String AES_ECB = "AES/ECB/PKCS5Padding";
private final Cipher decryptCipherAES_ECB;
// All encryption is done through the AES CBC cipher.
private static final String AES_CBC = "AES/CBC/PKCS5Padding";
private final Cipher encryptCipherAES_CBC;
private final Cipher decryptCipherAES_CBC;
public JCEEncryptor() throws Exception {
/**
* We want to force the initialization of the cipher here. This is a fix
* for bug #2696486.
* JDev with JDK 1.3 in some cases will allow a JCE object to be created
* when it shouldn't. That is, JDev includes an incompletely configured JCE
* library for JDK 1.3, meaning JCE will not run properly in the VM. So, JDev
* allows you to create a JCEEncryptor object, but eventually throw's
* errors when trying to make JCE library calls from encryptPassword.
*
* Confusing??? Well, don't move this code before talking to Guy first!
*/
decryptCipherDES_ECB = Cipher.getInstance(DES_ECB);
decryptCipherDES_ECB.init(Cipher.DECRYPT_MODE, Synergizer.getDESMultitasker());
decryptCipherAES_ECB = Cipher.getInstance(AES_ECB);
decryptCipherAES_ECB.init(Cipher.DECRYPT_MODE, Synergizer.getAESMultitasker());
SecretKey sk = Synergizer.getAESCBCMultitasker();
IvParameterSpec iv = Synergizer.getIvSpec();
encryptCipherAES_CBC = Cipher.getInstance(AES_CBC);
encryptCipherAES_CBC.init(Cipher.ENCRYPT_MODE, sk, iv);
decryptCipherAES_CBC = Cipher.getInstance(AES_CBC);
decryptCipherAES_CBC.init(Cipher.DECRYPT_MODE, sk, iv);
}
/**
* Encrypts a string. Will throw a validation exception.
*/
@Override
public synchronized String encryptPassword(String password) {
try {
return Helper.buildHexStringFromBytes(encryptCipherAES_CBC.doFinal(password.getBytes("UTF-8")));
} catch (Exception e) {
throw ValidationException.errorEncryptingPassword(e);
}
}
/**
* Decrypts a string. Will throw a validation exception.
* Handles backwards compatibility for older encrypted strings.
*/
@Override
public synchronized String decryptPassword(String encryptedPswd) {
if (encryptedPswd == null) {
return null;
}
String password = null;
byte[] bytePassword = new byte[0];
try {
bytePassword = Helper.buildBytesFromHexString(encryptedPswd);
// try AES/CBC first
password = new String(decryptCipherAES_CBC.doFinal(bytePassword), "UTF-8");
} catch (ConversionException | IllegalBlockSizeException ce) {
// buildBytesFromHexString failed, assume clear text
password = encryptedPswd;
} catch (Exception e) {
ObjectInputStream oisAes = null;
try {
// try AES/ECB second
oisAes = new ObjectInputStream(new CipherInputStream(new ByteArrayInputStream(bytePassword), decryptCipherAES_ECB));
password = (String)oisAes.readObject();
} catch (Exception f) {
ObjectInputStream oisDes = null;
try {
// try DES/ECB third
oisDes = new ObjectInputStream(new CipherInputStream(new ByteArrayInputStream(bytePassword), decryptCipherDES_ECB));
password = (String)oisDes.readObject();
} catch (ArrayIndexOutOfBoundsException g) {
// JCE 1.2.1 couldn't decrypt it, assume clear text
password = encryptedPswd;
} catch (Exception h) {
if (h.getCause() instanceof IllegalBlockSizeException) {
// JCE couldn't decrypt it, assume clear text
password = encryptedPswd;
} else {
throw ValidationException.errorDecryptingPassword(h);
}
} finally {
if (oisDes != null) {
try {
oisDes.close();
} catch (IOException e2) {}
}
}
} finally {
if (oisAes != null) {
try {
oisAes.close();
} catch (IOException e1) {}
}
}
}
return password;
}
/**
* Returns multitaskers for the ciphers. :-)
*/
private static class Synergizer {
private static SecretKey getDESMultitasker() throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("DES");
return factory.generateSecret(new DESKeySpec(Helper.buildBytesFromHexString("E60B80C7AEC78038")));
}
private static SecretKey getAESMultitasker() throws Exception {
return new SecretKeySpec(Helper.buildBytesFromHexString("3E7CFEF156E712906E1F603B59463C67"), "AES");
}
private static SecretKey getAESCBCMultitasker() throws Exception {
return new SecretKeySpec(Helper.buildBytesFromHexString("2DB7354A48F1CA7B48ACA247540FC923"), "AES");
}
private static IvParameterSpec getIvSpec() {
byte[] b = new byte[] {
(byte) -26, (byte) 124, (byte) -99, (byte) 32,
(byte) -37, (byte) -58, (byte) -93, (byte) 100,
(byte) 126, (byte) -55, (byte) -21, (byte) 48,
(byte) -86, (byte) 97, (byte) 12, (byte) 113};
return new IvParameterSpec(b);
}
}
}