package com.airlock.iam.security.util.crypto;

import com.airlock.iam.security.util.crypto.iv.IvGenerator;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public final class AesGcmIvIntegratedCipher extends IvIntegratedCipher {
   private static final int IV_LENGTH = 12;
   private static final Integer TAG_LENGTH_ENCRYPT = 16;
   public static final String AES_GCM_NO_PADDING = "AES/GCM/NoPadding";

   @Override
   public byte[] encrypt(byte[] dataToEncrypt, byte[] key, IvGenerator ivGenerator) throws EncryptionException {
      try {
         javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding");
         byte[] iv = ivGenerator.generateIv(12);
         GCMParameterSpec gcmSpec = new GCMParameterSpec(TAG_LENGTH_ENCRYPT * 8, iv);
         cipher.init(1, new SecretKeySpec(key, "AES"), gcmSpec);
         byte[] encryptedBytes = cipher.doFinal(dataToEncrypt);
         byte[] combinedIvAndCipherText = new byte[iv.length + encryptedBytes.length];
         System.arraycopy(iv, 0, combinedIvAndCipherText, 0, iv.length);
         System.arraycopy(encryptedBytes, 0, combinedIvAndCipherText, iv.length, encryptedBytes.length);
         return combinedIvAndCipherText;
      } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | BadPaddingException | InvalidKeyException | IllegalBlockSizeException e) {
         throw new EncryptionException("failed to encrypt", e);
      }
   }

   @Override
   public byte[] decrypt(byte[] dataToDecrypt, byte[] key) throws DecryptionException {
      byte[] iv = new byte[12];
      System.arraycopy(dataToDecrypt, 0, iv, 0, iv.length);
      byte[] encryptedText = new byte[dataToDecrypt.length - 12];
      System.arraycopy(dataToDecrypt, 12, encryptedText, 0, encryptedText.length);
      GCMParameterSpec gcmSpec = new GCMParameterSpec(TAG_LENGTH_ENCRYPT * 8, iv);

      try {
         javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding");
         cipher.init(2, new SecretKeySpec(key, "AES"), gcmSpec);
         return cipher.doFinal(encryptedText);
      } catch (NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | BadPaddingException | InvalidKeyException e) {
         throw new DecryptionException("failed to decrypt", e);
      }
   }
}
