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

io.github.mmm.crypto.crypt.CryptorImplCipher Maven / Gradle / Ivy

package io.github.mmm.crypto.crypt;

import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Objects;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;

import io.github.mmm.binary.Binary;
import io.github.mmm.crypto.algorithm.AbstractCryptoAlgorithmWithRandom;
import io.github.mmm.crypto.random.RandomFactory;

/**
 * Abstract implementation of {@link Cryptor} based on {@link Cipher}.
 *
 * @author Joerg Hohwiller (hohwille at users.sourceforge.net)
 * @since 1.0.0
 */
public abstract class CryptorImplCipher extends AbstractCryptoAlgorithmWithRandom implements Cryptor {

  private final CryptorConfig config;

  private final Key key;

  private final int nonceSize;

  private Cipher cipher;

  private byte[] nonce;

  /** Index in {@link #nonce} or {@code -1} if nonce has completely been processed. */
  private int nonceIndex;

  /**
   * The constructor.
   *
   * @param randomFactory the {@link RandomFactory} to use.
   * @param config the {@link CryptorConfig}.
   * @param key the {@link Key} to use.
   */
  public CryptorImplCipher(RandomFactory randomFactory, CryptorConfig config, Key key) {

    super(config.getProvider(), randomFactory);
    this.key = key;
    this.config = config;
    this.nonceSize = config.getNonceSize();
  }

  @Override
  public String getAlgorithm() {

    return this.config.getAlgorithm();
  }

  @Override
  public int getNonceSize() {

    return this.nonceSize;
  }

  /**
   * @return the underlying {@link Cipher}.
   */
  protected Cipher getCipher() {

    if (this.cipher == null) {
      try {
        this.cipher = getProvider().createCipher(getAlgorithm());
        int opmode;
        AlgorithmParameterSpec parameters = null;
        SecureRandom secureRandom = getRandomFactory().newSecureRandom();
        boolean encryptor = isEncryptor();
        if (encryptor) {
          opmode = Cipher.ENCRYPT_MODE;
          if ((this.nonceSize > 0) && this.config.isCreateRandomNonce()) {
            this.nonce = new byte[this.nonceSize];
            secureRandom.nextBytes(this.nonce);
            parameters = new IvParameterSpec(this.nonce);
          }
        } else {
          opmode = Cipher.DECRYPT_MODE;
          if (this.nonceSize > 0) {
            Objects.requireNonNull(this.nonce, "nonce");
            if (this.nonce.length != this.nonceSize) {
              throw new IllegalStateException(
                  "Required nonce size is " + this.nonceSize + " but " + this.nonce.length + " was found!");
            }
            parameters = new IvParameterSpec(this.nonce);
            this.nonce = null;
          }
          this.nonceIndex = -1;
        }
        if (parameters == null) {
          this.cipher.init(opmode, this.key, secureRandom);
        } else {
          this.cipher.init(opmode, this.key, parameters, secureRandom);
        }
        if (encryptor && (this.nonceSize > 0) && !this.config.isCreateRandomNonce()) {
          this.nonce = this.cipher.getIV();
          assert (this.nonceSize == this.nonce.length);
        }
      } catch (Exception e) {
        throw creationFailedException(e, Cipher.class);
      }
    }
    return this.cipher;
  }

  /**
   * @return {@code true} if {@link Encryptor}, {@code false} if {@link Decryptor}.
   */
  protected abstract boolean isEncryptor();

  /**
   * @return the {@link CryptorConfig}.
   */
  public final CryptorConfig getConfig() {

    return this.config;
  }

  /**
   * @return "decryption" for {@link Decryptor} and "encryption" for {@link Encryptor}.
   */
  protected String getMode() {

    if (isEncryptor()) {
      return "encryption";
    } else {
      return "decryption";
    }
  }

  private int readNonce(byte[] input, int off, int len) {

    assert (this.nonceIndex >= 0);
    if (this.nonce == null) {
      this.nonce = new byte[this.nonceSize];
      this.nonceIndex = 0;
    }
    int nonceBytes = this.nonce.length - this.nonceIndex;
    if (nonceBytes > 0) {
      if (nonceBytes > len) {
        nonceBytes = len;
      }
      System.arraycopy(input, off, this.nonce, this.nonceIndex, nonceBytes);
      this.nonceIndex += nonceBytes;
      return nonceBytes;
    } else {
      return 0;
    }
  }

  @Override
  public byte[] crypt(byte[] input, int offset, int length, boolean complete) {

    try {
      int off = offset;
      int len = length;
      if ((this.nonceSize > 0) && (this.nonceIndex >= 0) && !isEncryptor()) {
        int nonceBytes = readNonce(input, off, len);
        off += nonceBytes;
        len -= nonceBytes;
        if (len <= 0) {
          return Binary.EMPTY_BYTE_ARRAY;
        }
      }
      byte[] result;
      if (complete) {
        result = getCipher().doFinal(input, off, len);
      } else {
        result = getCipher().update(input, off, len);
      }
      if ((this.nonceSize > 0) && (this.nonceIndex == 0) && isEncryptor()) {
        byte[] data = new byte[result.length + this.nonceSize];
        System.arraycopy(this.nonce, 0, data, 0, this.nonceSize);
        this.nonceIndex = -1;
        System.arraycopy(result, 0, data, this.nonceSize, result.length);
        result = data;
      }
      return result;
    } catch (GeneralSecurityException e) {
      throw wrapSecurityException(e);
    }
  }

  /**
   * @param e the {@link Exception} to wrap.
   * @return the wrapped {@link RuntimeException}.
   */
  protected RuntimeException wrapSecurityException(Exception e) {

    return new IllegalStateException("The " + getMode() + " failed: " + e.getMessage(), e);
  }

  @Override
  public void reset() {

    if (this.cipher != null) {
      try {
        this.cipher.doFinal();
      } catch (Exception e) {
        // ignore...
      }
      this.cipher = null;
    }
    this.nonce = null;
    this.nonceIndex = 0;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy