content: /*****************************************************************
   *   Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you 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
   *
   *    https://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 org.apache.cayenne.crypto.transformer.bytes;
  
  import java.security.InvalidAlgorithmParameterException;
  import java.security.InvalidKeyException;
  import java.security.Key;
  import java.util.Arrays;
  
  import javax.crypto.BadPaddingException;
  import javax.crypto.Cipher;
  import javax.crypto.IllegalBlockSizeException;
  import javax.crypto.ShortBufferException;
  import javax.crypto.spec.IvParameterSpec;
  
  import org.apache.cayenne.crypto.CayenneCryptoException;
  
  /**
   * A {@link BytesEncryptor} that encrypts the provided bytes. The first block in
   * the encrypted bytes is the value of IV used to seed the CBC transformation.
   * It will be needed for decryption. The object is stateful and is not
   * thread-safe.
   * 
   * @since 4.0
   */
  class CbcEncryptor implements BytesEncryptor {
  
      private Cipher cipher;
      private byte[] iv;
      private Key key;
      private int blockSize;
  
      public CbcEncryptor(Cipher cipher, Key key, byte[] seedIv) {
          this.key = key;
          this.cipher = cipher;
          this.blockSize = cipher.getBlockSize();
  
          if (seedIv.length != blockSize) {
  
              // TODO: perhaps we should truncate/expand it if there's a mismatch
              throw new CayenneCryptoException("IV size is expected to be the same as block size. Was " + seedIv.length
                      + "; block size was: " + blockSize);
          }
  
          // making a copy - we are modifying this array, something that should
          // not be visible oustide this object.
          this.iv = Arrays.copyOf(seedIv, blockSize);
  
      }
  
      @Override
      public byte[] encrypt(byte[] input, int outputOffset, byte[] flags) {
  
          try {
              return doEncrypt(input, outputOffset);
          } catch (Exception e) {
              throw new CayenneCryptoException("Error on encryption", e);
          }
      }
  
      private byte[] doEncrypt(byte[] plain, int outputOffset) throws InvalidKeyException,
              InvalidAlgorithmParameterException, ShortBufferException, IllegalBlockSizeException, BadPaddingException {
  
          cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
          byte[] encrypted = new byte[outputOffset + blockSize + cipher.getOutputSize(plain.length)];
  
          // copy IV in the first block
          System.arraycopy(iv, 0, encrypted, outputOffset, blockSize);
  
          int encBytes = cipher.doFinal(plain, 0, plain.length, encrypted, outputOffset + blockSize);
  
          // store the last block of ciphertext to use as an IV for the next round
          // of encryption...
          System.arraycopy(encrypted, outputOffset + encBytes, iv, 0, blockSize);
  
          return encrypted;
      }
  
  }
  