Java tutorial
/** * Copyright 2013 Stockholm County Council * * This file is part of APIGW * * APIGW is free software; you can redistribute it and/or modify * it under the terms of version 2.1 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * APIGW is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with APIGW; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA * */ package org.apigw.commons.crypto; import org.apache.commons.codec.binary.Base64; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; import javax.annotation.PostConstruct; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.security.*; import java.security.cert.CertificateException; /** * Created by martin on 26/12/14. */ public class ApigwCrypto { protected Logger log; protected static final int IV_LENGTH = 16; protected static final String TRANSFORMATION = "AES/CBC/PKCS7PADDING"; protected static final String KEY_ALGORITHM = "AES"; @Value("${crypto.useEncryption}") protected boolean useEncryption; @Value("${crypto.keyStore.file}") private String keyStoreFile; @Value("${crypto.keyStore.password}") private String keyStorePassword; @Value("${crypto.keyStore.type}") private String keyStoreType; @Value("${crypto.keyStore.alias}") private String alias; @Value("${crypto.saltKeyStore.file}") private String saltKeyKeyStoreFile; @Value("${crypto.saltKeyStore.password}") private String saltKeyKeyStorePassword; @Value("${crypto.saltKeyStore.type}") private String saltKeyKeyStoreType; @Value("${crypto.saltKey.password}") private String saltKeyPassword; @Value("${crypto.saltKey.alias}") private String saltKeyAlias; @Value("${crypto.encodedSalt}") private String encodedEncryptedSalt; private Provider securityProvider; private byte[] salt; private KeyStore keyStore; private KeyStore saltKeyStore; @PostConstruct public void init() throws Exception { log.debug("Initializing..."); if (useEncryption) { securityProvider = new BouncyCastleProvider(); Security.addProvider(securityProvider); keyStore = initKeyStore(keyStoreFile, keyStorePassword, keyStoreType); saltKeyStore = initKeyStore(saltKeyKeyStoreFile, saltKeyKeyStorePassword, saltKeyKeyStoreType); salt = initSalt(); Key key = keyStore.getKey(alias, keyStorePassword.toCharArray()); validateKey(key); String algorithm = key.getAlgorithm(); int size = key.getEncoded().length * 8; log.debug("operations will be performed using {} key with size {}", algorithm, size); } else { keyStore = null; log.warn("No keystore file specified, will not encrypt messages"); } log.debug("Finished initializing"); } /** * * @return The SecretKeySpec to be used for encryption/decryption of messages * @throws UnrecoverableKeyException * @throws NoSuchAlgorithmException * @throws KeyStoreException */ protected SecretKeySpec getSecretKeySpec() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException { Key key = keyStore.getKey(alias, keyStorePassword.toCharArray()); return new SecretKeySpec(key.getEncoded(), KEY_ALGORITHM); } /** * * @return IvParameterSpec of the global salt to be used for encryption/decryption */ protected IvParameterSpec getIvParameterSpec() { return new IvParameterSpec(salt); } /** * * @return a new instance of Cipher to be used for encryption/decryption * @throws NoSuchPaddingException * @throws NoSuchAlgorithmException */ protected Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException { return Cipher.getInstance(TRANSFORMATION, securityProvider); } protected void validateKey(Key key) throws InvalidKeyException, NoSuchAlgorithmException { String algorithm = key.getAlgorithm(); int size = key.getEncoded().length * 8; if (!KEY_ALGORITHM.equalsIgnoreCase(algorithm)) { String msg = "Expected key of type: " + KEY_ALGORITHM + ", instead it was: " + algorithm; log.error(msg); throw new InvalidKeyException(msg); } else if (size > Cipher.getMaxAllowedKeyLength(KEY_ALGORITHM)) { String msg = "Illegal key size, max platform support for " + KEY_ALGORITHM + " keys is " + Cipher.getMaxAllowedKeyLength(KEY_ALGORITHM); log.error(msg); throw new InvalidKeyException(msg); } } /** * Will init the global salt / IV, this salt should not be stored together with encrypted values. */ private byte[] initSalt() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { byte[] encryptedSalt = Base64.decodeBase64(encodedEncryptedSalt.getBytes()); Key saltKey = saltKeyStore.getKey(saltKeyAlias, saltKeyPassword.toCharArray()); validateKey(saltKey); String algorithm = saltKey.getAlgorithm(); int size = saltKey.getEncoded().length * 8; log.debug("initializing salt using {} key with size {}", algorithm, size); SecretKeySpec skeySpec = new SecretKeySpec(saltKey.getEncoded(), KEY_ALGORITHM); IvParameterSpec ivParameterSpec = new IvParameterSpec(getIV(encryptedSalt)); Cipher decryptCipher = Cipher.getInstance(TRANSFORMATION, securityProvider); decryptCipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParameterSpec); byte[] decryptedSalt = decryptCipher.doFinal(encryptedSalt); return removeIV(decryptedSalt); } /** * Will return a new byte[] containing all the bytes except for the first number of bytes corresponding to IV_LENGTH * @param ivPrependedEncryptedBytes * @return a new byte[] containing all the bytes except for the first number of bytes corresponding to IV_LENGTH */ private byte[] removeIV(byte[] ivPrependedEncryptedBytes) { byte[] encryptedWithoutIV = new byte[ivPrependedEncryptedBytes.length - IV_LENGTH]; System.arraycopy(ivPrependedEncryptedBytes, IV_LENGTH, encryptedWithoutIV, 0, ivPrependedEncryptedBytes.length - IV_LENGTH); return encryptedWithoutIV; } /** * Will extract the first number fo bytes from a byte[] which should represent an IV * * @param ivPrependedEncryptedBytes byte array where the first number of bytes (corresponding to IV_LENGTH) contains an IV * @return */ private byte[] getIV(byte[] ivPrependedEncryptedBytes) { byte[] iv = new byte[IV_LENGTH]; System.arraycopy(ivPrependedEncryptedBytes, 0, iv, 0, IV_LENGTH); return iv; } /** * * @param keyStoreFile * @param keyStorePassword * @param keyStoreType * @return a new KeyStore corresponding to provided values * @throws IOException * @throws KeyStoreException * @throws CertificateException * @throws NoSuchAlgorithmException */ private KeyStore initKeyStore(String keyStoreFile, String keyStorePassword, String keyStoreType) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { InputStream inputStream; KeyStore keyStore; try { inputStream = new ClassPathResource(keyStoreFile).getInputStream(); } catch (FileNotFoundException e) { log.warn("unable to load {} from classpath, will try filesystem", keyStoreFile); inputStream = new FileSystemResource(keyStoreFile).getInputStream(); } keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(inputStream, keyStorePassword.toCharArray()); inputStream.close(); return keyStore; } public void setKeyStoreFile(String keyStoreFile) { this.keyStoreFile = keyStoreFile; } public void setKeyStorePassword(String keyStorePassword) { this.keyStorePassword = keyStorePassword; } public void setKeyStoreType(String keyStoreType) { this.keyStoreType = keyStoreType; } public void setAlias(String alias) { this.alias = alias; } public void setUseEncryption(boolean useEncryption) { this.useEncryption = useEncryption; } public void setSaltKeyKeyStoreFile(String saltKeyKeyStoreFile) { this.saltKeyKeyStoreFile = saltKeyKeyStoreFile; } public void setSaltKeyKeyStorePassword(String saltKeyKeyStorePassword) { this.saltKeyKeyStorePassword = saltKeyKeyStorePassword; } public void setSaltKeyKeyStoreType(String saltKeyKeyStoreType) { this.saltKeyKeyStoreType = saltKeyKeyStoreType; } public void setSaltKeyAlias(String saltKeyAlias) { this.saltKeyAlias = saltKeyAlias; } public void setSaltKeyPassword(String saltKeyPassword) { this.saltKeyPassword = saltKeyPassword; } public void setEncodedEncryptedSalt(String encodedEncryptedSalt) { this.encodedEncryptedSalt = encodedEncryptedSalt; } }