/* * Copyright (c) 2008-2012, Hazel Bilisim Ltd. All Rights Reserved. * * 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 com.hazelcast.nio; import com.hazelcast.config.AsymmetricEncryptionConfig; import com.hazelcast.config.SymmetricEncryptionConfig; import com.hazelcast.logging.ILogger; import com.hazelcast.logging.Logger; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.*; import java.nio.ByteBuffer; import java.security.*; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.KeySpec; import java.util.logging.Level; final class CipherHelper { private static AsymmetricCipherBuilder asymmetricCipherBuilder = null; private static SymmetricCipherBuilder symmetricCipherBuilder = null; final static ILogger logger = Logger.getLogger(CipherHelper.class.getName()); static { try { if (Boolean.getBoolean("hazelcast.security.bouncy.enabled")) { String provider = "org.bouncycastle.jce.provider.BouncyCastleProvider"; Security.addProvider((Provider) Class.forName(provider).newInstance()); } } catch (Exception e) { logger.log(Level.WARNING, e.getMessage(), e); } } @SuppressWarnings("SynchronizedMethod") public static synchronized Cipher createAsymmetricReaderCipher(IOService ioService, String remoteAlias) throws Exception { if (asymmetricCipherBuilder == null) { asymmetricCipherBuilder = new AsymmetricCipherBuilder(ioService); } return asymmetricCipherBuilder.getReaderCipher(remoteAlias); } @SuppressWarnings("SynchronizedMethod") public static synchronized Cipher createAsymmetricWriterCipher(IOService ioService) throws Exception { if (asymmetricCipherBuilder == null) { asymmetricCipherBuilder = new AsymmetricCipherBuilder(ioService); } return asymmetricCipherBuilder.getWriterCipher(); } @SuppressWarnings("SynchronizedMethod") public static synchronized Cipher createSymmetricReaderCipher(IOService ioService) throws Exception { if (symmetricCipherBuilder == null) { symmetricCipherBuilder = new SymmetricCipherBuilder(ioService.getSymmetricEncryptionConfig()); } return symmetricCipherBuilder.getReaderCipher(null); } @SuppressWarnings("SynchronizedMethod") public static synchronized Cipher createSymmetricWriterCipher(IOService ioService) throws Exception { if (symmetricCipherBuilder == null) { symmetricCipherBuilder = new SymmetricCipherBuilder(ioService.getSymmetricEncryptionConfig()); } return symmetricCipherBuilder.getWriterCipher(); } public static boolean isAsymmetricEncryptionEnabled(IOService ioService) { AsymmetricEncryptionConfig aec = ioService.getAsymmetricEncryptionConfig(); return (aec != null && aec.isEnabled()); } public static boolean isSymmetricEncryptionEnabled(IOService ioService) { SymmetricEncryptionConfig sec = ioService.getSymmetricEncryptionConfig(); return (sec != null && sec.isEnabled()); } public static String getKeyAlias(IOService ioService) { AsymmetricEncryptionConfig aec = ioService.getAsymmetricEncryptionConfig(); return aec.getKeyAlias(); } interface CipherBuilder { Cipher getWriterCipher() throws Exception; Cipher getReaderCipher(String param) throws Exception; boolean isAsymmetric(); } static class AsymmetricCipherBuilder implements CipherBuilder { String algorithm = "RSA/NONE/PKCS1PADDING"; KeyStore keyStore; private final IOService ioService; AsymmetricCipherBuilder(IOService ioService) { this.ioService = ioService; try { AsymmetricEncryptionConfig aec = ioService.getAsymmetricEncryptionConfig(); algorithm = aec.getAlgorithm(); keyStore = KeyStore.getInstance(aec.getStoreType()); // get user password and file input stream char[] password = aec.getStorePassword().toCharArray(); java.io.FileInputStream fis = new java.io.FileInputStream(aec.getStorePath()); keyStore.load(fis, password); fis.close(); } catch (Exception e) { logger.log(Level.WARNING, e.getMessage(), e); } } public Cipher getReaderCipher(String remoteAlias) throws Exception { java.security.cert.Certificate certificate = keyStore.getCertificate(remoteAlias); PublicKey publicKey = certificate.getPublicKey(); Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.DECRYPT_MODE, publicKey); return cipher; } public Cipher getWriterCipher() throws Exception { AsymmetricEncryptionConfig aec = ioService.getAsymmetricEncryptionConfig(); Cipher cipher = Cipher.getInstance(algorithm); KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(aec.getKeyAlias(), new KeyStore.PasswordProtection(aec.getKeyPassword().toCharArray())); PrivateKey privateKey = pkEntry.getPrivateKey(); cipher.init(Cipher.ENCRYPT_MODE, privateKey); return cipher; } public boolean isAsymmetric() { return true; } } static class SymmetricCipherBuilder implements CipherBuilder { final String algorithm; // 8-byte Salt final byte[] salt; final String passPhrase; final int iterationCount; byte[] keyBytes; SymmetricCipherBuilder(SymmetricEncryptionConfig sec) { algorithm = sec.getAlgorithm(); passPhrase = sec.getPassword(); salt = createSalt(sec.getSalt()); iterationCount = sec.getIterationCount(); keyBytes = sec.getKey(); } byte[] createSalt(String saltStr) { long hash = 0; char chars[] = saltStr.toCharArray(); for (char c : chars) { hash = 31 * hash + c; } byte[] theSalt = new byte[8]; theSalt[0] = (byte) (hash >>> 56); theSalt[1] = (byte) (hash >>> 48); theSalt[2] = (byte) (hash >>> 40); theSalt[3] = (byte) (hash >>> 32); theSalt[4] = (byte) (hash >>> 24); theSalt[5] = (byte) (hash >>> 16); theSalt[6] = (byte) (hash >>> 8); theSalt[7] = (byte) (hash); return theSalt; } public Cipher create(boolean encryptMode) { try { int mode = (encryptMode) ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE; Cipher cipher = Cipher.getInstance(algorithm); String keyAlgorithm = algorithm; if (algorithm.indexOf('/') != -1) { keyAlgorithm = algorithm.substring(0, algorithm.indexOf('/')); } // 32-bit digest key=pass+salt ByteBuffer bbPass = ByteBuffer.allocate(32); MessageDigest md = MessageDigest.getInstance("MD5"); bbPass.put(md.digest(passPhrase.getBytes())); md.reset(); byte[] saltDigest = md.digest(salt); bbPass.put(saltDigest); boolean isCBC = algorithm.indexOf("/CBC/") != -1; SecretKey key = null; //CBC mode requires IvParameter with 8 byte input int ivLength = 8; AlgorithmParameterSpec paramSpec = null; if (keyBytes == null) { keyBytes = bbPass.array(); } if (algorithm.startsWith("AES")) { ivLength = 16; key = new SecretKeySpec(keyBytes, "AES"); } else if (algorithm.startsWith("Blowfish")) { key = new SecretKeySpec(keyBytes, "Blowfish"); } else if (algorithm.startsWith("DESede")) { //requires at least 192 bits (24 bytes) KeySpec keySpec = new DESedeKeySpec(keyBytes); key = SecretKeyFactory.getInstance("DESede").generateSecret(keySpec); } else if (algorithm.startsWith("DES")) { KeySpec keySpec = new DESKeySpec(keyBytes); key = SecretKeyFactory.getInstance("DES").generateSecret(keySpec); } else if (algorithm.startsWith("PBEWith")) { paramSpec = new PBEParameterSpec(salt, iterationCount); KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount); key = SecretKeyFactory.getInstance(keyAlgorithm).generateSecret(keySpec); } if (isCBC) { byte[] iv = (ivLength == 8) ? salt : saltDigest; paramSpec = new IvParameterSpec(iv); } cipher.init(mode, key, paramSpec); return cipher; } catch (Throwable e) { throw new RuntimeException("unable to create Cipher:" + e.getMessage(), e); } } public Cipher getWriterCipher() { return create(true); } public Cipher getReaderCipher(String ignored) { return create(false); } public boolean isAsymmetric() { return false; } } public static void handleCipherException(Exception e, Connection connection) { logger.log(Level.WARNING, e.getMessage(), e); connection.close(); } }