001 /*
002 * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org
003 * Copyright (C) 2011 NightLabs Consulting GmbH
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program. If not, see <http://www.gnu.org/licenses/>.
017 */
018 package org.cumulus4j.store;
019
020 import java.io.UnsupportedEncodingException;
021 import java.util.HashMap;
022 import java.util.Map;
023
024 import javax.crypto.BadPaddingException;
025 import javax.crypto.Cipher;
026 import javax.crypto.IllegalBlockSizeException;
027 import javax.crypto.spec.IvParameterSpec;
028 import javax.crypto.spec.SecretKeySpec;
029
030 import org.cumulus4j.store.crypto.AbstractCryptoManager;
031 import org.cumulus4j.store.crypto.AbstractCryptoSession;
032 import org.cumulus4j.store.crypto.Ciphertext;
033 import org.cumulus4j.store.crypto.CryptoContext;
034 import org.cumulus4j.store.crypto.CryptoSession;
035 import org.cumulus4j.store.crypto.Plaintext;
036
037 /**
038 * Dummy crypto-manager for debugging and testing.
039 *
040 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
041 */
042 public class DummyCryptoManager extends AbstractCryptoManager
043 {
044 public static final String KEY_STORE_ID_COMPATIBILITY_TEST = "COMPATIBILITYTEST"; // no special characters!
045
046 @Override
047 protected CryptoSession createCryptoSession() {
048 return new DummySession();
049 }
050
051 private static final class DummySession
052 extends AbstractCryptoSession
053 {
054 // // key length: 128 bits
055 // private static final byte[] dummyKey = { 'D', 'e', 'r', ' ', 'F', 'e', 'r', 'd', ' ', 'h', 'a', 't', ' ', 'v', 'i', 'e' };
056 // initialization vector length: 128 bits
057 private static final IvParameterSpec iv = new IvParameterSpec(new byte[] {'b', 'l', 'a', 't', 'r', 'u', 'l', 'l', 'a', 'l', 'a', 't', 'r', 'a', 'r', 'a'});
058
059 private static final String ALGORITHM = "AES";
060 private static final String ALGORITHM_WITH_PARAMS = ALGORITHM + "/CBC/PKCS5Padding";
061
062 // private Cipher encrypter;
063 // private Cipher decrypter;
064 // {
065 // try {
066 // SecretKeySpec key = new SecretKeySpec(dummyKey, ALGORITHM);
067 // encrypter = Cipher.getInstance(ALGORITHM_WITH_PARAMS);
068 // encrypter.init(Cipher.ENCRYPT_MODE, key, iv);
069 // decrypter = Cipher.getInstance(ALGORITHM_WITH_PARAMS);
070 // decrypter.init(Cipher.DECRYPT_MODE, key, iv);
071 // } catch (Exception ex) {
072 // throw new RuntimeException(ex);
073 // }
074 // }
075
076 private Map<Integer, Map<String, Cipher>> mode2KeyStoreID2Cipher = new HashMap<Integer, Map<String,Cipher>>();
077
078 protected Cipher getEncrypter() {
079 return getCipher(Cipher.ENCRYPT_MODE);
080 }
081
082 protected Cipher getDecrypter() {
083 return getCipher(Cipher.DECRYPT_MODE);
084 }
085
086 protected Cipher getCipher(int mode) {
087 String keyStoreID = getKeyStoreID();
088
089 synchronized (mode2KeyStoreID2Cipher) {
090 Map<String, Cipher> keyStoreID2Cipher = mode2KeyStoreID2Cipher.get(mode);
091 if (keyStoreID2Cipher == null) {
092 keyStoreID2Cipher = new HashMap<String, Cipher>();
093 mode2KeyStoreID2Cipher.put(mode, keyStoreID2Cipher);
094 }
095
096 Cipher cipher = keyStoreID2Cipher.get(keyStoreID);
097 if (cipher == null) {
098 // key length: 128 bits
099 byte[] dummyKey = { 'D', 'e', 'r', ' ', 'F', 'e', 'r', 'd', ' ', 'h', 'a', 't', ' ', 'v', 'i', 'e' };
100
101 if (!KEY_STORE_ID_COMPATIBILITY_TEST.equals(keyStoreID)) {
102 try {
103 byte[] keyStoreIDBytes = keyStoreID.getBytes("UTF-8");
104 int keyIdx = -1;
105 for (int i = 0; i < keyStoreIDBytes.length; ++i) {
106 if (++keyIdx >= dummyKey.length)
107 keyIdx = 0;
108
109 dummyKey[keyIdx] ^= keyStoreIDBytes[i];
110 }
111 } catch (UnsupportedEncodingException e) {
112 throw new RuntimeException(e);
113 }
114 }
115
116 try {
117 SecretKeySpec key = new SecretKeySpec(dummyKey, ALGORITHM);
118 cipher = Cipher.getInstance(ALGORITHM_WITH_PARAMS);
119 cipher.init(mode, key, iv);
120 } catch (Exception ex) {
121 throw new RuntimeException(ex);
122 }
123 keyStoreID2Cipher.put(keyStoreID, cipher);
124 }
125 return cipher;
126 }
127 }
128
129 @Override
130 public Ciphertext encrypt(CryptoContext cryptoContext, Plaintext plaintext)
131 {
132 // First get the required resources (that are cleared in close()).
133 Cipher c = getEncrypter();
134
135 // Then assert that we are not yet closed. This makes sure that we definitely can continue
136 // even if close() is called right now simultaneously.
137 assertNotClosed();
138
139 Ciphertext result = new Ciphertext();
140 result.setKeyID(12345);
141
142 synchronized (c) {
143 try {
144 result.setData(
145 c.doFinal(plaintext.getData())
146 );
147 } catch (IllegalBlockSizeException e) {
148 throw new RuntimeException(e);
149 } catch (BadPaddingException e) {
150 throw new RuntimeException(e);
151 }
152 }
153
154 return result;
155 }
156
157 @Override
158 public Plaintext decrypt(CryptoContext cryptoContext, Ciphertext ciphertext)
159 {
160 if (ciphertext.getKeyID() != 12345)
161 throw new IllegalArgumentException("No key with this keyID: " + ciphertext.getKeyID());
162
163 // First get the required resources (that are cleared in close()).
164 Cipher c = getDecrypter();
165
166 // Then assert that we are not yet closed. This makes sure that we definitely can continue
167 // even if close() is called right now simultaneously.
168 assertNotClosed();
169
170 Plaintext result = new Plaintext();
171
172 synchronized (c) {
173 try {
174 result.setData(
175 c.doFinal(ciphertext.getData())
176 );
177 } catch (IllegalBlockSizeException e) {
178 throw new RuntimeException(e);
179 } catch (BadPaddingException e) {
180 throw new RuntimeException(e);
181 }
182 }
183
184 return result;
185 }
186
187 @Override
188 public void close() {
189 super.close();
190 synchronized (mode2KeyStoreID2Cipher) {
191 mode2KeyStoreID2Cipher.clear();
192 }
193 }
194 }
195
196 }