001package org.apache.commons.jcs3.utils.serialization; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.IOException; 023import java.nio.ByteBuffer; 024import java.security.InvalidAlgorithmParameterException; 025import java.security.InvalidKeyException; 026import java.security.NoSuchAlgorithmException; 027import java.security.SecureRandom; 028import java.security.spec.InvalidKeySpecException; 029 030import javax.crypto.BadPaddingException; 031import javax.crypto.Cipher; 032import javax.crypto.IllegalBlockSizeException; 033import javax.crypto.NoSuchPaddingException; 034import javax.crypto.SecretKey; 035import javax.crypto.SecretKeyFactory; 036import javax.crypto.spec.GCMParameterSpec; 037import javax.crypto.spec.PBEKeySpec; 038import javax.crypto.spec.SecretKeySpec; 039 040import org.apache.commons.jcs3.engine.behavior.IElementSerializer; 041 042/** 043 * Performs serialization and de-serialization. It encrypts and decrypts the 044 * value. 045 * 046 * @since 3.1 047 */ 048public class EncryptingSerializer extends StandardSerializer 049{ 050 private static final String DEFAULT_SECRET_KEY_ALGORITHM = "PBKDF2WithHmacSHA256"; 051 private static final String DEFAULT_CIPHER = "AES/ECB/PKCS5Padding"; 052 private static final int KEYHASH_ITERATION_COUNT = 1000; 053 private static final int KEY_LENGTH = 256; 054 private static final int TAG_LENGTH = 128; 055 private static final int IV_LENGTH = 12; 056 private static final int SALT_LENGTH = 16; 057 058 /** The pre-shared key */ 059 private String psk; 060 061 /** The cipher transformation */ 062 private String cipherTransformation = DEFAULT_CIPHER; 063 064 /** The random source */ 065 private final SecureRandom secureRandom; 066 067 /** The secret-key factory */ 068 private final SecretKeyFactory secretKeyFactory; 069 070 /** Wrapped serializer */ 071 private final IElementSerializer serializer; 072 073 /** 074 * Default constructor 075 */ 076 public EncryptingSerializer() 077 { 078 this(new StandardSerializer()); 079 } 080 081 /** 082 * Wrapper constructor 083 * 084 * @param serializer 085 * the wrapped serializer 086 */ 087 public EncryptingSerializer(IElementSerializer serializer) 088 { 089 this.serializer = serializer; 090 091 try 092 { 093 this.secureRandom = new SecureRandom(); 094 this.secretKeyFactory = SecretKeyFactory.getInstance(DEFAULT_SECRET_KEY_ALGORITHM); 095 } 096 catch (NoSuchAlgorithmException e) 097 { 098 throw new RuntimeException("Could not set up encryption tools", e); 099 } 100 } 101 102 /** 103 * Set the pre-shared key for encryption and decryption 104 * 105 * @param psk the key 106 */ 107 public void setPreSharedKey(String psk) 108 { 109 this.psk = psk; 110 } 111 112 /** 113 * Set the cipher transformation for encryption and decryption 114 * Default is AES/ECB/PKCS5Padding 115 * 116 * @param transformation the transformation 117 */ 118 public void setAesCipherTransformation(String transformation) 119 { 120 this.cipherTransformation = transformation; 121 } 122 123 private byte[] getRandomBytes(int length) 124 { 125 byte[] bytes = new byte[length]; 126 secureRandom.nextBytes(bytes); 127 128 return bytes; 129 } 130 131 private SecretKey createSecretKey(String password, byte[] salt) throws InvalidKeySpecException 132 { 133 /* Derive the key, given password and salt. */ 134 PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 135 KEYHASH_ITERATION_COUNT, KEY_LENGTH); 136 SecretKey tmp = secretKeyFactory.generateSecret(spec); 137 return new SecretKeySpec(tmp.getEncoded(), "AES"); 138 } 139 140 private byte[] encrypt(byte[] source) throws IOException 141 { 142 try 143 { 144 byte[] salt = getRandomBytes(SALT_LENGTH); 145 byte[] iv = getRandomBytes(IV_LENGTH); 146 147 SecretKey secretKey = createSecretKey(psk, salt); 148 Cipher cipher = Cipher.getInstance(cipherTransformation); 149 if (cipher.getAlgorithm().startsWith("AES/GCM")) 150 { 151 cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(TAG_LENGTH, iv)); 152 } 153 else 154 { 155 cipher.init(Cipher.ENCRYPT_MODE, secretKey); 156 } 157 158 byte[] encrypted = cipher.doFinal(source); 159 160 // join initial vector, salt and encrypted data for later decryption 161 return ByteBuffer.allocate(IV_LENGTH + SALT_LENGTH + encrypted.length) 162 .put(iv) 163 .put(salt) 164 .put(encrypted) 165 .array(); 166 } 167 catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | 168 IllegalBlockSizeException | InvalidKeyException | InvalidKeySpecException | 169 InvalidAlgorithmParameterException e) 170 { 171 throw new IOException("Error while encrypting", e); 172 } 173 } 174 175 private byte[] decrypt(byte[] source) throws IOException 176 { 177 try 178 { 179 // split data in initial vector, salt and encrypted data 180 ByteBuffer wrapped = ByteBuffer.wrap(source); 181 182 byte[] iv = new byte[IV_LENGTH]; 183 wrapped.get(iv); 184 185 byte[] salt = new byte[SALT_LENGTH]; 186 wrapped.get(salt); 187 188 byte[] encrypted = new byte[wrapped.remaining()]; 189 wrapped.get(encrypted); 190 191 SecretKey secretKey = createSecretKey(psk, salt); 192 Cipher cipher = Cipher.getInstance(cipherTransformation); 193 194 if (cipher.getAlgorithm().startsWith("AES/GCM")) 195 { 196 cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(TAG_LENGTH, iv)); 197 } 198 else 199 { 200 cipher.init(Cipher.DECRYPT_MODE, secretKey); 201 } 202 203 return cipher.doFinal(encrypted); 204 } 205 catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | 206 IllegalBlockSizeException | InvalidKeyException | InvalidKeySpecException | 207 InvalidAlgorithmParameterException e) 208 { 209 throw new IOException("Error while decrypting", e); 210 } 211 } 212 213 /** 214 * Serializes an object using default serialization. Encrypts the byte array. 215 * <p> 216 * @param obj object 217 * @return byte[] 218 * @throws IOException on i/o problem 219 */ 220 @Override 221 public <T> byte[] serialize( final T obj ) 222 throws IOException 223 { 224 final byte[] unencrypted = serializer.serialize(obj); 225 return encrypt(unencrypted); 226 } 227 228 /** 229 * Uses default de-serialization to turn a byte array into an object. Decrypts the value 230 * first. All exceptions are converted into IOExceptions. 231 * <p> 232 * @param data data bytes 233 * @param loader class loader to use 234 * @return Object 235 * @throws IOException on i/o problem 236 * @throws ClassNotFoundException if class is not found during deserialization 237 */ 238 @Override 239 public <T> T deSerialize( final byte[] data, final ClassLoader loader ) 240 throws IOException, ClassNotFoundException 241 { 242 if ( data == null ) 243 { 244 return null; 245 } 246 247 final byte[] deccrypted = decrypt(data); 248 return serializer.deSerialize(deccrypted, loader); 249 } 250}