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

com.google.crypto.tink.subtle.prf.HkdfStreamingPrf Maven / Gradle / Ivy

// Copyright 2020 Google LLC
//
// 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.prf;

import static java.lang.Math.min;

import com.google.crypto.tink.AccessesPartialKey;
import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.internal.EnumTypeProtoConverter;
import com.google.crypto.tink.prf.HkdfPrfKey;
import com.google.crypto.tink.prf.HkdfPrfParameters;
import com.google.crypto.tink.subtle.EngineFactory;
import com.google.crypto.tink.subtle.Enums;
import com.google.crypto.tink.subtle.Enums.HashType;
import com.google.crypto.tink.util.Bytes;
import com.google.errorprone.annotations.Immutable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.crypto.spec.SecretKeySpec;

/** An implementation of the HKDF pseudorandom function, as given by RFC 5869. */
@Immutable
@AccessesPartialKey
public class HkdfStreamingPrf implements StreamingPrf {
  // This converter is not used with a proto but rather with an ordinary enum type.
  private static final EnumTypeProtoConverter
      HASH_TYPE_CONVERTER =
          EnumTypeProtoConverter.builder()
              .add(Enums.HashType.SHA1, HkdfPrfParameters.HashType.SHA1)
              .add(Enums.HashType.SHA224, HkdfPrfParameters.HashType.SHA224)
              .add(Enums.HashType.SHA256, HkdfPrfParameters.HashType.SHA256)
              .add(Enums.HashType.SHA384, HkdfPrfParameters.HashType.SHA384)
              .add(Enums.HashType.SHA512, HkdfPrfParameters.HashType.SHA512)
              .build();

  private static String getJavaxHmacName(HashType hashType) throws GeneralSecurityException {
    switch (hashType) {
      case SHA1:
        return "HmacSha1";
      case SHA256:
        return "HmacSha256";
      case SHA384:
        return "HmacSha384";
      case SHA512:
        return "HmacSha512";
      default:
        throw new GeneralSecurityException(
            "No getJavaxHmacName for given hash " + hashType + " known");
    }
  }

  public HkdfStreamingPrf(final HashType hashType, final byte[] ikm, final byte[] salt) {
    this.hashType = hashType;
    this.ikm = Arrays.copyOf(ikm, ikm.length);
    this.salt = Arrays.copyOf(salt, salt.length);
  }

  public static StreamingPrf create(HkdfPrfKey key) throws GeneralSecurityException {
    Bytes saltFromKey = key.getParameters().getSalt();
    return new HkdfStreamingPrf(
        HASH_TYPE_CONVERTER.toProtoEnum(key.getParameters().getHashType()),
        key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get()),
        saltFromKey == null ? new byte[] {} : saltFromKey.toByteArray());
  }

  private final HashType hashType;

  // Manual inspection shows that this is never mutated (and copied on construction)
  @SuppressWarnings("Immutable")
  private final byte[] ikm;

  // Manual inspection shows that this is never mutated (and copied on construction)
  @SuppressWarnings("Immutable")
  private final byte[] salt;

  private class HkdfInputStream extends InputStream {
    public HkdfInputStream(final byte[] input) {
      ctr = -1;
      this.input = Arrays.copyOf(input, input.length);
    }

    // We create the HMac lazily, so we don't have to throw an exception in computePrf.
    private void initialize() throws GeneralSecurityException, IOException {
      try {
        mac = EngineFactory.MAC.getInstance(getJavaxHmacName(hashType));
      } catch (GeneralSecurityException e) {
        throw new IOException("Creating HMac failed", e);
      }
      if (salt == null || salt.length == 0) {
        // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
        // then HKDF uses a salt that is an array of zeros of the same length as the hash digest.
        mac.init(new SecretKeySpec(new byte[mac.getMacLength()], getJavaxHmacName(hashType)));
      } else {
        mac.init(new SecretKeySpec(salt, getJavaxHmacName(hashType)));
      }
      mac.update(ikm);
      prk = mac.doFinal();
      buffer = ByteBuffer.allocateDirect(0);
      buffer.mark();
      ctr = 0;
    }

    // Updates ti to ti+1 as in RFC 5869, section 2.3:
    // T(i+1) = HMAC-Hash(PRK, T(i) | info | 0x)
    private void updateBuffer() throws GeneralSecurityException, IOException {
      mac.init(new SecretKeySpec(prk, getJavaxHmacName(hashType)));
      buffer.reset();
      mac.update(buffer);
      mac.update(input);
      ctr = ctr + 1;
      mac.update((byte) ctr);
      buffer = ByteBuffer.wrap(mac.doFinal());
      buffer.mark();
    }

    @Override
    public int read() throws IOException {
      byte[] oneByte = new byte[1];
      int ret = read(oneByte, 0, 1);
      if (ret == 1) {
        return oneByte[0] & 0xff;
      } else if (ret == -1) {
        return ret;
      } else {
        throw new IOException("Reading failed");
      }
    }

    @Override
    public int read(byte[] dst) throws IOException {
      return read(dst, 0, dst.length);
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
      int totalRead = 0;
      try {
        if (ctr == -1) {
          initialize();
        }

        while (totalRead < len) {

          if (!buffer.hasRemaining()) {
            if (ctr == 255) {
              // End of stream.
              return totalRead;
            }
            updateBuffer();
          }

          int toRead = min(len - totalRead, buffer.remaining());
          buffer.get(b, off, toRead);
          off += toRead;
          totalRead += toRead;
        }
      } catch (GeneralSecurityException e) {
        mac = null;
        throw new IOException("HkdfInputStream failed", e);
      }
      return totalRead;
    }

    // The input to the PRF; called "info" in RFC 5869.
    private final byte[] input;

    private javax.crypto.Mac mac;
    // The pseudorandom key. By RFC 5869: PRK = HMAC-Hash(salt, IKM)
    private byte[] prk;
    // The last T(i) computed. By RFC 5869:
    //   T(0) = empty string
    //   T(i+1) = HMAC-Hash(PRK, T(i) | info | 0x)
    private ByteBuffer buffer;
    // The current value of i which for which we store T(i) in buffer, or -1 if we are not
    // initialized.
    private int ctr;
  }

  @Override
  public InputStream computePrf(final byte[] input) {
    return new HkdfInputStream(input);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy