1 package org.apache.commons.jcs3.utils.serialization;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.IOException;
23 import java.nio.ByteBuffer;
24 import java.security.InvalidAlgorithmParameterException;
25 import java.security.InvalidKeyException;
26 import java.security.NoSuchAlgorithmException;
27 import java.security.SecureRandom;
28 import java.security.spec.InvalidKeySpecException;
29
30 import javax.crypto.BadPaddingException;
31 import javax.crypto.Cipher;
32 import javax.crypto.IllegalBlockSizeException;
33 import javax.crypto.NoSuchPaddingException;
34 import javax.crypto.SecretKey;
35 import javax.crypto.SecretKeyFactory;
36 import javax.crypto.spec.GCMParameterSpec;
37 import javax.crypto.spec.PBEKeySpec;
38 import javax.crypto.spec.SecretKeySpec;
39
40 import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
41
42
43
44
45
46
47
48 public class EncryptingSerializer extends StandardSerializer
49 {
50 private static final String DEFAULT_SECRET_KEY_ALGORITHM = "PBKDF2WithHmacSHA256";
51 private static final String DEFAULT_CIPHER = "AES/ECB/PKCS5Padding";
52 private static final int KEYHASH_ITERATION_COUNT = 1000;
53 private static final int KEY_LENGTH = 256;
54 private static final int TAG_LENGTH = 128;
55 private static final int IV_LENGTH = 12;
56 private static final int SALT_LENGTH = 16;
57
58
59 private String psk;
60
61
62 private String cipherTransformation = DEFAULT_CIPHER;
63
64
65 private final SecureRandom secureRandom;
66
67
68 private final SecretKeyFactory secretKeyFactory;
69
70
71 private final IElementSerializer serializer;
72
73
74
75
76 public EncryptingSerializer()
77 {
78 this(new StandardSerializer());
79 }
80
81
82
83
84
85
86
87 public EncryptingSerializer(IElementSerializer serializer)
88 {
89 this.serializer = serializer;
90
91 try
92 {
93 this.secureRandom = new SecureRandom();
94 this.secretKeyFactory = SecretKeyFactory.getInstance(DEFAULT_SECRET_KEY_ALGORITHM);
95 }
96 catch (NoSuchAlgorithmException e)
97 {
98 throw new RuntimeException("Could not set up encryption tools", e);
99 }
100 }
101
102
103
104
105
106
107 public void setPreSharedKey(String psk)
108 {
109 this.psk = psk;
110 }
111
112
113
114
115
116
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
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
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
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
215
216
217
218
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
230
231
232
233
234
235
236
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 }