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

com.google.crypto.tink.subtle.PrfAesCmac Maven / Gradle / Ivy

// Copyright 2017 Google Inc.
//
// 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 com.google.crypto.tink.subtle;

import com.google.crypto.tink.AccessesPartialKey;
import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.config.internal.TinkFipsUtil;
import com.google.crypto.tink.mac.internal.AesUtil;
import com.google.crypto.tink.prf.AesCmacPrfKey;
import com.google.crypto.tink.prf.Prf;
import com.google.errorprone.annotations.Immutable;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/**
 * An implementation of CMAC following RFC 4493.
 */
@Immutable
@AccessesPartialKey
public final class PrfAesCmac implements Prf {
  public static final TinkFipsUtil.AlgorithmFipsCompatibility FIPS =
      TinkFipsUtil.AlgorithmFipsCompatibility.ALGORITHM_NOT_FIPS;

  @SuppressWarnings("Immutable")
  private final SecretKey keySpec;

  @SuppressWarnings("Immutable")
  private byte[] subKey1;

  @SuppressWarnings("Immutable")
  private byte[] subKey2;

  private static final ThreadLocal localAesCipher =
      new ThreadLocal() {
        @Override
        protected Cipher initialValue() {
          try {
            return EngineFactory.CIPHER.getInstance("AES/ECB/NoPadding");
          } catch (GeneralSecurityException ex) {
            throw new IllegalStateException(ex);
          }
        }
      };

  private static Cipher instance() throws GeneralSecurityException {
    if (!FIPS.isCompatible()) {
      throw new GeneralSecurityException("Can not use AES-CMAC in FIPS-mode.");
    }
    return localAesCipher.get();
  }

  public PrfAesCmac(final byte[] key) throws GeneralSecurityException {
    Validators.validateAesKeySize(key.length);

    keySpec = new SecretKeySpec(key, "AES");
    generateSubKeys();
  }

  public static Prf create(AesCmacPrfKey key) throws GeneralSecurityException {
    return new PrfAesCmac(key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get()));
  }

  // Only visible for testing.
  static int calcN(int dataLength) {
    if (dataLength == 0) {
      return 1;
    }
    return (dataLength - 1) / AesUtil.BLOCK_SIZE + 1;
  }

  private static void xorBlock(final byte[] x, final byte[] y, int offsetY, byte[] output) {
    for (int i = 0; i < AesUtil.BLOCK_SIZE; i++) {
      output[i] = (byte) (x[i] ^ y[i + offsetY]);
    }
  }

  // https://tools.ietf.org/html/rfc4493#section-2.4
  @Override
  public byte[] compute(final byte[] data, int outputLength) throws GeneralSecurityException {
    if (outputLength > AesUtil.BLOCK_SIZE) {
      throw new InvalidAlgorithmParameterException(
          "outputLength too large, max is " + AesUtil.BLOCK_SIZE + " bytes");
    }
    Cipher aes = instance();
    aes.init(Cipher.ENCRYPT_MODE, keySpec);

    // n is the number of blocks (including partial blocks) into which the data
    // is divided. Empty data is divided into 1 empty block.
    // Step 2: n = ceil(length / blocksize)
    // TODO(b/68969256): Adding a test that computes a CMAC of length 2**31-1.
    int n = calcN(data.length);

    // Step 3
    boolean flag = (n * AesUtil.BLOCK_SIZE == data.length);

    // Step 4
    byte[] mLast;
    if (flag) {
      mLast = Bytes.xor(data, (n - 1) * AesUtil.BLOCK_SIZE, subKey1, 0, AesUtil.BLOCK_SIZE);
    } else {
      mLast =
          Bytes.xor(
              AesUtil.cmacPad(Arrays.copyOfRange(data, (n - 1) * AesUtil.BLOCK_SIZE, data.length)),
              subKey2);
    }

    // Step 5
    byte[] x = new byte[AesUtil.BLOCK_SIZE];

    // Step 6
    byte[] y = new byte[AesUtil.BLOCK_SIZE];
    for (int i = 0; i < n - 1; i++) {
      xorBlock(x, data, i * AesUtil.BLOCK_SIZE, /* output= */ y);
      int written = aes.doFinal(y, 0, AesUtil.BLOCK_SIZE, /* output= */ x);
      if (written != AesUtil.BLOCK_SIZE) {
        throw new IllegalStateException("Cipher didn't write full block");
      }
    }
    xorBlock(x, mLast, 0, /* output= */ y);

    // Step 7
    int written = aes.doFinal(y, 0, AesUtil.BLOCK_SIZE, /* output= */ x);
    if (written != AesUtil.BLOCK_SIZE) {
      throw new IllegalStateException("Cipher didn't write full block");
    }
    if (x.length == outputLength) {
      return x;
    }
    return Arrays.copyOf(x, outputLength);
  }

  // https://tools.ietf.org/html/rfc4493#section-2.3
  private void generateSubKeys() throws GeneralSecurityException {
    Cipher aes = instance();
    aes.init(Cipher.ENCRYPT_MODE, keySpec);
    byte[] zeroes = new byte[AesUtil.BLOCK_SIZE];
    byte[] l = aes.doFinal(zeroes);
    subKey1 = AesUtil.dbl(l);
    subKey2 = AesUtil.dbl(subKey1);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy