| // 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.subtle.EngineFactory; |
| import com.google.crypto.tink.subtle.Enums.HashType; |
| 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. */ |
| public class HkdfStreamingPrf implements StreamingPrf { |
| 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); |
| } |
| |
| private final HashType hashType; |
| private final byte[] ikm; |
| 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<i+1>) |
| 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<i+1>) |
| 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); |
| } |
| } |