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 }