package org.ovirt.engine.core.uutils.crypto; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.Key; import java.security.KeyException; import java.security.KeyStore; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.Certificate; import java.util.HashMap; import java.util.Map; import java.util.Random; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.spec.IvParameterSpec; import org.apache.commons.codec.binary.Base64; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.type.TypeFactory; public class EnvelopeEncryptDecrypt { private static final String ARTIFACT = "EnvelopeEncryptDecrypt"; private static final String VERSION = "1"; private static final String PUBKEY_DIGEST_ALGO = "SHA-1"; private static final String PKEY_MODE_PADDING = "ECB/PKCS1Padding"; private static final String CONTENT_KEY = "content"; private static final String RANDOM_KEY = "random"; private static final String ARTIFACT_KEY = "artifact"; private static final String VERSION_KEY = "version"; private static final String CIPHER_ALGO_KEY = "cipherAlgo"; private static final String ENCRYPTED_CONTENT_KEY = "encryptedContent"; private static final String IV_KEY = "iv"; private static final String WRAPPED_KEY_KEY = "wrappedKey"; private static final String WRAP_ALGO_KEY = "wrapAlgo"; private static final String WRAP_KEY_DIGEST_ALGO_KEY = "wrapKeyDigestAlgo"; private static final String WRAP_KEY_DIGEST_KEY = "wrapKeyDigest"; private static final Random random; static { try { random= SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } /** * Encrypt a content using envelope. * @param algo Cipher algorithm to use. * @param bits Size of cipher key. * @param cert Certificate to encrypt to (wrap key using public key). * @param blockSize Adjust the size of content to blockSize. * @param content Content to encrypt. * @return Base64 value of envelope. * * The blockSize is used in order to hide actual content size. */ public static String encrypt( String algo, int bits, Certificate cert, int blockSize, byte[] content ) throws GeneralSecurityException, IOException { final String wrapAlgo = cert.getPublicKey().getAlgorithm() + "/" + PKEY_MODE_PADDING; final Base64 base64 = new Base64(0); final Map<String, String> map = new HashMap<>(); final Map<String, String> env = new HashMap<>(); env.put(CONTENT_KEY, base64.encodeToString(content)); byte[] r = new byte[((content.length / blockSize) + 1) * blockSize - content.length]; random.nextBytes(r); env.put(RANDOM_KEY, base64.encodeToString(r)); KeyGenerator gen = KeyGenerator.getInstance(algo.split("/", 2)[0]); gen.init(bits); Key key = gen.generateKey(); Cipher cipher = Cipher.getInstance(algo); cipher.init(Cipher.ENCRYPT_MODE, key); Cipher wrap = Cipher.getInstance(wrapAlgo); wrap.init(Cipher.WRAP_MODE, cert); map.put(ARTIFACT_KEY, ARTIFACT); map.put(VERSION_KEY, VERSION); map.put(WRAP_ALGO_KEY, wrapAlgo); map.put(CIPHER_ALGO_KEY, algo); map.put(ENCRYPTED_CONTENT_KEY, base64.encodeToString(cipher.doFinal(new ObjectMapper().writeValueAsString(env).getBytes( StandardCharsets.UTF_8)))); map.put(IV_KEY, base64.encodeToString(cipher.getIV())); map.put(WRAPPED_KEY_KEY, base64.encodeToString(wrap.wrap(key))); map.put(WRAP_KEY_DIGEST_ALGO_KEY, PUBKEY_DIGEST_ALGO); map.put(WRAP_KEY_DIGEST_KEY, base64.encodeToString(MessageDigest.getInstance(PUBKEY_DIGEST_ALGO).digest(cert.getPublicKey().getEncoded()))); return base64.encodeToString(new ObjectMapper().writeValueAsString(map).getBytes(StandardCharsets.UTF_8)); } /** * Decrypt a content using envelope. * @param pkeyEntry A private key entry (key and certificate) to use for decryption. * @param blob value of envelope. * @return content. */ public static byte[] decrypt( KeyStore.PrivateKeyEntry pkeyEntry, String blob ) throws GeneralSecurityException, IOException { final Map<String, String> map = new ObjectMapper().readValue( Base64.decodeBase64(blob), TypeFactory.defaultInstance().constructMapType(HashMap.class, String.class, String.class) ); if (!ARTIFACT.equals(map.get(ARTIFACT_KEY))) { throw new IllegalArgumentException(String.format("Invalid artifact '%s'", map.get(ARTIFACT_KEY))); } if (!VERSION.equals(map.get(VERSION_KEY))) { throw new IllegalArgumentException(String.format("Invalid version '%s'", map.get(VERSION_KEY))); } if (pkeyEntry.getCertificate() != null) { if ( !MessageDigest.isEqual( Base64.decodeBase64(map.get(WRAP_KEY_DIGEST_KEY)), MessageDigest.getInstance(map.get(WRAP_KEY_DIGEST_ALGO_KEY)).digest(pkeyEntry.getCertificate().getPublicKey().getEncoded()) ) ) { throw new KeyException("Private key entry mismatch"); } } Cipher wrap = Cipher.getInstance(map.get(WRAP_ALGO_KEY)); wrap.init(Cipher.UNWRAP_MODE, pkeyEntry.getPrivateKey()); Cipher cipher = Cipher.getInstance(map.get(CIPHER_ALGO_KEY)); cipher.init( Cipher.DECRYPT_MODE, wrap.unwrap( Base64.decodeBase64(map.get(WRAPPED_KEY_KEY)), cipher.getAlgorithm().split("/", 2)[0], Cipher.SECRET_KEY ), new IvParameterSpec(Base64.decodeBase64(map.get(IV_KEY))) ); final Map<String, String> env = new ObjectMapper().readValue( cipher.doFinal(Base64.decodeBase64(map.get(ENCRYPTED_CONTENT_KEY))), TypeFactory.defaultInstance().constructMapType(HashMap.class, String.class, String.class) ); return Base64.decodeBase64(env.get(CONTENT_KEY)); } }