All Downloads are FREE. Search and download functionalities are using the official Maven repository.

art.cutils.security.AES Maven / Gradle / Ivy

/*
 * _________  ____ ______________.___.____       _________
 * \_   ___ \|    |   \__    ___/|   |    |     /   _____/
 * /    \  \/|    |   / |    |   |   |    |     \_____  \
 * \     \___|    |  /  |    |   |   |    |___  /        \
 *  \______  /______/   |____|   |___|_______ \/_______  /
 *         \/                                \/        \/
 *
 * Copyright (C) 2018 — 2023 Bobai Kato. All Rights Reserved.
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package art.cutils.security;

import art.cutils.Serialization;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.Validate;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.validation.Valid;
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.function.Supplier;

import static art.cutils.security.AES.Algorithm.SHA256;
import static java.security.MessageDigest.getInstance;
import static java.util.Objects.hash;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;
import static org.apache.commons.lang3.Validate.isTrue;

/**
 * This is an implementation of Advanced Encryption Standard, to can encrypt and decrypt Objects of
 * any type.
 *
 * @param  Type of value
 * @author @author Bobai Kato
 * @since 1.0
 */
public final class AES implements Serializable {
  private static final long serialVersionUID = 977987773346721438L;
  /**
   * Default encryption Key.
   *
   * @since 1.0
   */
  private static final String DEFAULT_KEY = "Set yours with: `AES.init('fW&yNtP2peBndT5Hz&')`";
  /** GCM Length. */
  private static final int GCM_IV_LENGTH = 12;
  /**
   * Instance of {@link Cipher}.
   *
   * @since 1.0
   */
  private final transient Cipher cipher;

  /**
   * Instance of {@link SecretKeySpec}.
   *
   * @since 1.0
   */
  private final SecretKeySpec secretKey;
  /**
   * Additional Authentication Data for GCM.
   *
   * @since 1.0
   */
  private final byte[] aad;

  /**
   * Default Constructor.
   *
   * @param encryptionKey meta data you want to verify secret message
   * @throws NoSuchPaddingException when a bad/Wrong encryption key is supplied.
   * @throws NoSuchAlgorithmException This exception is thrown when a cryptographic algorithm not
   *     available in the environment.
   * @since 1.0
   */
  private AES(final AES.@NotNull Algorithm algorithm, final @NotNull String encryptionKey)
      throws NoSuchPaddingException, NoSuchAlgorithmException {
    this.cipher = Cipher.getInstance("AES/GCM/NoPadding");
    byte[] key = this.aad = encryptionKey.getBytes(StandardCharsets.UTF_8);
    final MessageDigest messageDigest = getInstance(algorithm.type);
    key = Arrays.copyOf(messageDigest.digest(key), 16);
    this.secretKey = new SecretKeySpec(key, "AES");
  }

  /**
   * This initiates encryption with default {@link MessageDigest} {@link Algorithm#type} and
   * encryption key.
   *
   * @param  Type of value
   * @return Instance of {@link AES}
   * @throws NoSuchAlgorithmException This exception is thrown when a particular cryptographic
   *     algorithm is requested but is not available in the environment.
   * @throws NoSuchPaddingException This exception is thrown when a particular padding mechanism is
   *     requested but is not available in the environment.
   * @since 1.0
   */
  @Contract(" -> new")
  public static  @NotNull AES init() throws NoSuchAlgorithmException, NoSuchPaddingException {
    return AES.init(AES.DEFAULT_KEY);
  }

  /**
   * This initiates encryption with the default {@link MessageDigest} {@link Algorithm#type}:{@code
   * SHA-256}.
   *
   * @param encryptionKey user encryption Key
   * @param  Type of value
   * @return Instance of {@link AES}
   * @throws NoSuchAlgorithmException This exception is thrown when a particular cryptographic
   *     algorithm is requested but is not available in the environment.
   * @throws NoSuchPaddingException This exception is thrown when a particular padding mechanism is
   *     requested but is not available in the environment.
   * @since 1.0
   */
  @Contract("_ -> new")
  public static  @NotNull AES init(final String encryptionKey)
      throws NoSuchAlgorithmException, NoSuchPaddingException {
    requireNonNull(encryptionKey, "encryption Key cannot be null");
    return new AES<>(SHA256, encryptionKey);
  }

  /**
   * This initiates encryption with the specified {@link MessageDigest} {@link Algorithm#type} type
   * and default Key.
   *
   * @param algorithm encryption algorithm of {@link Algorithm} instance. SHA256 is set if {@code
   *     null}.
   * @param  Type of value
   * @return Instance of {@link AES}
   * @throws NoSuchAlgorithmException This exception is thrown when a particular cryptographic
   *     algorithm is requested but is not available in the environment.
   * @throws NoSuchPaddingException This exception is thrown when a particular padding mechanism is
   *     requested but is not available in the environment.
   * @since 1.0
   */
  @Contract("_ -> new")
  public static  @NotNull AES init(final AES.Algorithm algorithm)
      throws NoSuchAlgorithmException, NoSuchPaddingException {
    return AES.init(algorithm, AES.DEFAULT_KEY);
  }

  /**
   * This initiates encryption with the specified {@link MessageDigest} {@link Algorithm#type} and
   * encryption key.
   *
   * @param encryptionKey user encryption Key
   * @param algorithm encryption algorithm of {@link Algorithm} instance. SHA256 is set if {@code
   *     null}.
   * @param  Type of value
   * @return Instance of {@link AES}
   * @throws NoSuchAlgorithmException This exception is thrown when a particular cryptographic
   *     algorithm is requested but is not available in the environment.
   * @throws NoSuchPaddingException This exception is thrown when a particular padding mechanism is
   *     requested but is not available in the environment.
   * @since 1.0
   */
  @Contract("_, _ -> new")
  public static  @NotNull AES init(final AES.Algorithm algorithm, final String encryptionKey)
      throws NoSuchAlgorithmException, NoSuchPaddingException {
    requireNonNull(encryptionKey, "encryption Key cannot be null");
    return new AES<>(isNull(algorithm) ? SHA256 : algorithm, encryptionKey);
  }

  /**
   * This encrypt item of T type.
   *
   * @param itemToEncrypt item to encrypt.
   * @return encrypted string of {@code itemToEncrypt} of T type. Not {@literal null}
   * @throws InvalidAlgorithmParameterException This is the exception for invalid or inappropriate
   *     algorithm parameters.
   * @throws InvalidKeyException This is the exception for invalid Keys (invalid encoding, wrong *
   *     length, uninitialized, etc).
   * @throws BadPaddingException This exception is thrown when a particular padding mechanism is
   *     expected for the input data but the data is not padded properly.
   * @throws IllegalBlockSizeException This exception is thrown when the length of data provided to
   *     a block cipher is incorrect, i.e., does not match the block size of the cipher.
   * @throws IOException Signals that an I/O exception of some sort has occurred.
   */
  public String encrypt(@Valid final T itemToEncrypt)
      throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException,
          IllegalBlockSizeException, IOException {
    Validate.isTrue(isNotEmpty(itemToEncrypt), "Item to encrypt cannot be null.", itemToEncrypt);
    final Supplier ivSupplier =
        () -> {
          final SecureRandom secureRandom = new SecureRandom();
          final byte[] iv = new byte[GCM_IV_LENGTH]; // NEVER REUSE THIS IV WITH SAME KEY
          do {
            secureRandom.nextBytes(iv);
          } while (iv[0] == 0);
          return iv;
        };

    final byte[] iv = ivSupplier.get();

    final GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);
    this.cipher.init(Cipher.ENCRYPT_MODE, this.secretKey, parameterSpec);

    if (nonNull(this.aad)) {
      this.cipher.updateAAD(this.aad);
    }

    final byte[] serializeData = Serialization.serialize(itemToEncrypt);
    final byte[] cipherText = this.cipher.doFinal(serializeData);

    final ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
    byteBuffer.put(iv);
    byteBuffer.put(cipherText);

    return Base64.getUrlEncoder().withoutPadding().encodeToString(byteBuffer.array());
  }

  /**
   * This method will decrypt the {@code itemToDecrypt}.
   *
   * @param itemToDecrypt encrypted string to be decrypted. not {@literal null}
   * @return decrypted Object.
   * @throws InvalidAlgorithmParameterException exception for invalid or inappropriate algorithm
   *     parameters.
   * @throws InvalidKeyException exception for invalid Keys
   * @throws BadPaddingException This exception is thrown when a particular padding mechanism is *
   *     expected for the input data but the data is not padded properly.
   * @throws IllegalBlockSizeException This exception is thrown when the length of data provided to
   *     a block * cipher is incorrect, i.e., does not match the block size of the cipher.
   */
  public T decrypt(final @NotNull String itemToDecrypt)
      throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException,
          IllegalBlockSizeException {

    isTrue(isNotEmpty(itemToDecrypt), "Item to decrypt cannot be null.", itemToDecrypt);

    final byte[] cipherMessage = Base64.getUrlDecoder().decode(itemToDecrypt);
    final AlgorithmParameterSpec spec = new GCMParameterSpec(128, cipherMessage, 0, GCM_IV_LENGTH);

    this.cipher.init(Cipher.DECRYPT_MODE, this.secretKey, spec);

    if (nonNull(this.aad)) {
      this.cipher.updateAAD(this.aad);
    }

    final byte[] plainText =
        this.cipher.doFinal(cipherMessage, GCM_IV_LENGTH, cipherMessage.length - GCM_IV_LENGTH);

    return SerializationUtils.deserialize(plainText);
  }

  @Override
  public int hashCode() {
    int result = hash(this.cipher, this.secretKey);
    result = 31 * result + Arrays.hashCode(this.aad);
    return result;
  }

  @Override
  @Contract(value = "null -> false", pure = true)
  public boolean equals(final Object o) {
    if (this == o) {
      return true;
    }
    if (o instanceof AES) {
      final AES aes = (AES) o;
      if (this.cipher.equals(aes.cipher) && this.secretKey.equals(aes.secretKey)) {
        return Arrays.equals(this.aad, aes.aad);
      }
      return false;
    }
    return false;
  }

  /**
   * This holds valid {@link MessageDigest} algorithm types.
   *
   * @since 1.0
   */
  public enum Algorithm {
    /** MD2 algorithm type. */
    MD2("MD2"),

    /** MD5 algorithm type. */
    MD5("MD5"),

    /** SHA-1 algorithm type. */
    SHA1("SHA-1"),

    /** SHA-224 algorithm type. */
    SHA224("SHA-224"),

    /** SHA-256 algorithm type. */
    SHA256("SHA-256"),

    /** SHA-384 algorithm type. */
    SHA384("SHA-384"),

    /** SHA-512 algorithm type. */
    SHA512("SHA-512");

    private final String type;

    @Contract(pure = true)
    Algorithm(final String type) {
      this.type = type;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy