Java tutorial
/** * Copyright 2016 Smart Society Services B.V. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 */ package com.alliander.osgp.shared.security; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.Arrays; import javax.annotation.PostConstruct; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import com.alliander.osgp.shared.exceptionhandling.EncrypterException; /** * Encryption service class that offers encrypt and decrypt methods to encrypt * or decrypt data. You can use this service as a bean, for example by including * its package in component scan. This service uses a property * encryption.key.path that should point to a File containing the secret key. * When encrypting apart from this service always use {@link #getIvbytes()}. * */ @Component public class EncryptionService { /** * the algorithm used to load the secret key */ public static final String SECRET_KEY_SPEC = "AES"; private static final String UNEXPECTED_EXCEPTION_DURING_ENCRYPTION = "Unexpected exception during encryption"; private static final String UNEXPECTED_EXCEPTION_DURING_DECRYPTION = "Unexpected exception during decryption"; private static final String UNEXPECTED_EXCEPTION_WHEN_READING_KEY = "Unexpected exception when reading key"; private static final Logger LOGGER = LoggerFactory.getLogger(EncryptionService.class); /** * the algorithm used for reading the secret key */ public static final String ALGORITHM = "AES/CBC/PKCS7PADDING"; /** * the id of the provider used */ public static final String PROVIDER = "BC"; @Value("${encryption.key.path}") private String keyPath; private SecretKey key; private static final byte[] IVBYTES = new byte[16]; /** * for testability * * @param key * A SecretKeySpec instance */ protected EncryptionService(final SecretKeySpec key) { this.key = key; } public EncryptionService() { // Default constructor. } /* * initialization of the IVBYTES used for encryption and decryption clients * (encryptors) have to use these ivBytes when encrypting, for example: * openssl enc -e -aes-128-cbc ..... -iv 000102030405060708090a0b0c0d0e0f */ static { for (short s = 0; s < IVBYTES.length; s++) { IVBYTES[s] = (byte) s; } } @PostConstruct private void initEncryption() { try { this.key = new SecretKeySpec(Files.readAllBytes(new File(this.keyPath).toPath()), SECRET_KEY_SPEC); } catch (final IOException e) { LOGGER.error(UNEXPECTED_EXCEPTION_WHEN_READING_KEY, e); throw new EncrypterException(UNEXPECTED_EXCEPTION_WHEN_READING_KEY, e); } } /** * Decrypts the data using the key, Strips off iv bytes when they are there * (first 16 0 bytes). */ public byte[] decrypt(final byte[] inputData) { try { final Cipher cipher = Cipher.getInstance(ALGORITHM, PROVIDER); cipher.init(Cipher.DECRYPT_MODE, this.key, new IvParameterSpec(IVBYTES)); final byte[] decryptedData = cipher.doFinal(inputData); if (this.checkNullBytesPrepended(decryptedData)) { return Arrays.copyOfRange(decryptedData, IVBYTES.length, decryptedData.length); } else { return decryptedData; } } catch (final NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException | InvalidAlgorithmParameterException ex) { LOGGER.error(UNEXPECTED_EXCEPTION_DURING_DECRYPTION, ex); throw new EncrypterException("Unexpected exception during decryption!", ex); } } /** * <pre> * - When aes decrypting data (both Java / bouncy castle and openssl) sometimes 16 0 bytes are prepended. * - Possibly this has to do with padding during encryption * - openssl as well as Java / bouncy castle don't prefix iv bytes when aes encrypting data (seen in junit test and commandline) * - makeSimulatorKey.sh (device simulator) assumes decrypted data are prepended with 0 bytes, at present this is correct * </pre> * * @param bytes * @return */ private boolean checkNullBytesPrepended(final byte[] bytes) { if (bytes.length > IVBYTES.length) { boolean nullBytesPrepended = false; for (short s = 0; s < IVBYTES.length; s++) { if (bytes[s] == 0) { nullBytesPrepended = true; } else { return false; } } return nullBytesPrepended; } else { return false; } } /** * Encrypts the data using the key */ public byte[] encrypt(final byte[] inputData) { try { final Cipher cipher = Cipher.getInstance(ALGORITHM, PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, this.key, new IvParameterSpec(IVBYTES)); return cipher.doFinal(inputData); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException | InvalidAlgorithmParameterException e) { LOGGER.error(UNEXPECTED_EXCEPTION_DURING_ENCRYPTION, e); throw new EncrypterException("Unexpected exception during encryption!", e); } } public static byte[] getIvbytes() { return Arrays.copyOf(IVBYTES, IVBYTES.length); } }