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.keymanager.back.shared;
019
020 import java.io.IOException;
021 import java.security.GeneralSecurityException;
022 import java.security.NoSuchAlgorithmException;
023
024 import org.bouncycastle.crypto.CipherParameters;
025 import org.bouncycastle.crypto.CryptoException;
026 import org.bouncycastle.crypto.params.KeyParameter;
027 import org.bouncycastle.crypto.params.ParametersWithIV;
028 import org.cumulus4j.crypto.Cipher;
029 import org.cumulus4j.crypto.CipherOperationMode;
030 import org.cumulus4j.crypto.CryptoRegistry;
031 import org.cumulus4j.crypto.MACCalculator;
032 import org.slf4j.Logger;
033 import org.slf4j.LoggerFactory;
034
035 /**
036 * <p>
037 * Utility class to en- & decrypt symmetric secret keys using asymmetric encryption.
038 * </p>
039 * <p>
040 * TODO the MAC algorithm should be communicated between key-manager and app-server (maybe
041 * the app-server specifies it, but with the possibility that the key-manager can override, i.e. use another one?!
042 * thus requiring the GetKeyResponse to tell the app-server, which one was actually used - or maybe encode this into the
043 * binary result here? Or maybe only specify it here on the key-manager-side (and encode in the binary)?
044 * less work and probably sufficient).
045 * </p>
046 *
047 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
048 */
049 public final class KeyEncryptionUtil
050 {
051 private static final Logger logger = LoggerFactory.getLogger(KeyEncryptionUtil.class);
052
053 private KeyEncryptionUtil() { }
054
055 private static final String MAC_ALGORITHM = "HMAC-SHA1";
056
057 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
058
059 /**
060 * Encrypt the given symmetric secret <code>key</code> with the given {@link Cipher}.
061 * The key will be protected against manipulation/corruption by a MAC.
062 *
063 * @param key the symmetric secret key to be encrypted.
064 * @param encrypter the cipher used for encryption.
065 * @return the key together with the MAC's key + IV - all encrypted.
066 * @throws CryptoException in case the encryption fails.
067 * @throws NoSuchAlgorithmException in case a crypto algorithm's name (e.g. for the MAC) does not exist in the {@link CryptoRegistry}.
068 * @see #encryptKey(byte[], String, byte[])
069 */
070 public static byte[] encryptKey(byte[] key, Cipher encrypter) throws CryptoException, NoSuchAlgorithmException
071 {
072 byte[] mac = EMPTY_BYTE_ARRAY;
073 byte[] macKey = EMPTY_BYTE_ARRAY;
074 byte[] macIV = EMPTY_BYTE_ARRAY;
075
076 MACCalculator macCalculator = CryptoRegistry.sharedInstance().createMACCalculator(MAC_ALGORITHM, true);
077 mac = macCalculator.doFinal(key);
078 if (macCalculator.getParameters() instanceof ParametersWithIV) {
079 ParametersWithIV pwiv = (ParametersWithIV) macCalculator.getParameters();
080 macIV = pwiv.getIV();
081 macKey = ((KeyParameter)pwiv.getParameters()).getKey();
082 }
083 else if (macCalculator.getParameters() instanceof KeyParameter) {
084 macKey = ((KeyParameter)macCalculator.getParameters()).getKey();
085 }
086 else
087 throw new IllegalStateException("macCalculator.getParameters() returned an instance of an unknown type: " + (macCalculator.getParameters() == null ? null : macCalculator.getParameters().getClass().getName()));
088
089 int resultSize = (
090 1 // version
091 + 3 // macKeySize, macIVSize, macSize
092 + encrypter.getOutputSize(macKey.length + macIV.length + key.length + mac.length)
093 );
094
095 byte[] out = new byte[resultSize];
096
097 if (macKey.length > 255)
098 throw new IllegalStateException("MAC key length too long!");
099
100 if (macIV.length > 255)
101 throw new IllegalStateException("MAC IV length too long!");
102
103 if (mac.length > 255)
104 throw new IllegalStateException("MAC length too long!");
105
106 int outOff = 0;
107 out[outOff++] = (byte)1; // version
108 out[outOff++] = (byte)macKey.length;
109 out[outOff++] = (byte)macIV.length;
110 out[outOff++] = (byte)mac.length;
111
112 outOff += encrypter.update(macKey, 0, macKey.length, out, outOff);
113 outOff += encrypter.update(macIV, 0, macIV.length, out, outOff);
114 outOff += encrypter.update(key, 0, key.length, out, outOff);
115 outOff += encrypter.update(mac, 0, mac.length, out, outOff);
116 outOff += encrypter.doFinal(out, outOff);
117
118 if (out.length == outOff)
119 return out;
120
121 logger.warn("Precalculated size ({}) does not match the actually written size ({})! Truncating byte array.", out.length, outOff);
122
123 byte[] result = new byte[outOff];
124 System.arraycopy(out, 0, result, 0, result.length);
125 return result;
126 }
127
128 /**
129 * Encrypt the given symmetric secret <code>key</code>.
130 * The key will be protected against manipulation/corruption by a MAC (the algorithm is currently hard-coded, but this might be changed, soon).
131 *
132 * @param key the symmetric secret key to be encrypted.
133 * @param keyEncryptionTransformation the transformation to be used to encrypt (see {@link CryptoRegistry#createCipher(String)}).
134 * @param keyEncryptionPublicKey the public key to be used to encrypt the given <code>key</code>.
135 * @return the key together with the MAC's key + IV - all encrypted.
136 * @throws GeneralSecurityException if there's a problem {@link CryptoRegistry#createCipher(String) obtaining the cipher from the CryptoRegistry}.
137 * @throws IOException if decoding the public key from its binary representation fails.
138 * @throws CryptoException in case the encryption fails.
139 * @see #encryptKey(byte[], Cipher)
140 * @see #decryptKey(Cipher, byte[])
141 */
142 public static byte[] encryptKey(byte[] key, String keyEncryptionTransformation, byte[] keyEncryptionPublicKey)
143 throws GeneralSecurityException, IOException, CryptoException
144 {
145 Cipher keyEncrypter = CryptoRegistry.sharedInstance().createCipher(keyEncryptionTransformation);
146 CipherParameters publicKey = CryptoRegistry.sharedInstance().decodePublicKey(keyEncryptionPublicKey);
147 keyEncrypter.init(CipherOperationMode.ENCRYPT, publicKey);
148 byte[] keyEncodedEncrypted = KeyEncryptionUtil.encryptKey(key, keyEncrypter);
149 return keyEncodedEncrypted;
150 }
151
152 /**
153 * Decrypt a previously {@link #encryptKey(byte[], String, byte[]) encrypted} secret key and verify its integrity
154 * via a MAC.
155 *
156 * @param decrypter the cipher to be used for decryption (already initialised with key + IV).
157 * @param keyEncodedEncrypted the encrypted key as produced by {@link #encryptKey(byte[], Cipher)}
158 * @return the decrypted secret key (as originally passed to {@link #encryptKey(byte[], Cipher)}.
159 * @throws CryptoException if decryption failed.
160 * @throws IOException if data cannot be read or is corrupted - e.g. if MAC verification failed.
161 * @throws NoSuchAlgorithmException if the {@link CryptoRegistry} does not know the (MAC) algorithm.
162 * @see #encryptKey(byte[], Cipher)
163 * @see #encryptKey(byte[], String, byte[])
164 */
165 public static byte[] decryptKey(Cipher decrypter, byte[] keyEncodedEncrypted) throws CryptoException, IOException, NoSuchAlgorithmException
166 {
167 int encryptedOff = 0;
168 int version = keyEncodedEncrypted[encryptedOff++] & 0xff;
169 if (version != 1)
170 throw new IllegalArgumentException("keyEncodedEncrypted is of version " + version + " which is not supported!");
171
172 int macKeyLength = keyEncodedEncrypted[encryptedOff++] & 0xff;
173 int macIVLength = keyEncodedEncrypted[encryptedOff++] & 0xff;
174 int macLength = keyEncodedEncrypted[encryptedOff++] & 0xff;
175
176 int outputSize = decrypter.getOutputSize(keyEncodedEncrypted.length - encryptedOff);
177 byte[] out = new byte[outputSize];
178
179 int outOff = 0;
180 outOff += decrypter.update(keyEncodedEncrypted, encryptedOff, keyEncodedEncrypted.length - encryptedOff, out, outOff);
181 outOff += decrypter.doFinal(out, outOff);
182
183 int dataOff = 0;
184 MACCalculator macCalculator = CryptoRegistry.sharedInstance().createMACCalculator(MAC_ALGORITHM, false);
185
186 CipherParameters macKeyParam = new KeyParameter(out, 0, macKeyLength);
187 dataOff += macKeyLength;
188
189 CipherParameters macParams;
190 if (macIVLength == 0)
191 macParams = macKeyParam;
192 else {
193 macParams = new ParametersWithIV(macKeyParam, out, dataOff, macIVLength);
194 dataOff += macIVLength;
195 }
196
197 macCalculator.init(macParams);
198
199 int dataLength = outOff - dataOff - macLength;
200 int macOff = dataOff + dataLength;
201
202 if (macCalculator != null) {
203 byte[] newMAC = new byte[macCalculator.getMacSize()];
204 macCalculator.update(out, dataOff, dataLength);
205 macCalculator.doFinal(newMAC, 0);
206
207 if (newMAC.length != macLength)
208 throw new IOException("MACs have different length! Expected MAC has " + macLength + " bytes and newly calculated MAC has " + newMAC.length + " bytes!");
209
210 for (int i = 0; i < macLength; ++i) {
211 byte expected = out[macOff + i];
212 if (expected != newMAC[i])
213 throw new IOException("MAC mismatch! mac[" + i + "] was expected to be " + expected + " but was " + newMAC[i]);
214 }
215 }
216
217 byte[] decrypted = new byte[dataLength];
218 System.arraycopy(out, dataOff, decrypted, 0, decrypted.length);
219
220 return decrypted;
221 }
222 }