com.google.crypto.tink.hybrid.internal.HpkeDecrypt Maven / Gradle / Ivy
Show all versions of tink-android Show documentation
// Copyright 2021 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.hybrid.internal;
import static com.google.crypto.tink.internal.Util.isPrefix;
import com.google.crypto.tink.AccessesPartialKey;
import com.google.crypto.tink.HybridDecrypt;
import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.hybrid.HpkeParameters;
import com.google.crypto.tink.hybrid.HpkePrivateKey;
import com.google.crypto.tink.util.Bytes;
import com.google.errorprone.annotations.Immutable;
import java.security.GeneralSecurityException;
import java.util.Arrays;
/**
* Hybrid Public Key Encryption (HPKE) decryption.
*
* HPKE RFC: https://www.rfc-editor.org/rfc/rfc9180.html
*/
@Immutable
public final class HpkeDecrypt implements HybridDecrypt {
private static final byte[] EMPTY_ASSOCIATED_DATA = new byte[0];
private final HpkeKemPrivateKey recipientPrivateKey;
private final HpkeKem kem;
private final HpkeKdf kdf;
private final HpkeAead aead;
private final int encapsulatedKeyLength;
@SuppressWarnings("Immutable") // We copy this on creation and never output it.
private final byte[] outputPrefix;
private HpkeDecrypt(
HpkeKemPrivateKey recipientPrivateKey,
HpkeKem kem,
HpkeKdf kdf,
HpkeAead aead,
int encapsulatedKeyLength,
Bytes outputPrefix) {
this.recipientPrivateKey = recipientPrivateKey;
this.kem = kem;
this.kdf = kdf;
this.aead = aead;
this.encapsulatedKeyLength = encapsulatedKeyLength;
this.outputPrefix = outputPrefix.toByteArray();
}
private static int encodingSizeInBytes(HpkeParameters.KemId kemId)
throws GeneralSecurityException {
if (kemId.equals(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256)) {
return 32;
}
if (kemId.equals(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256)) {
return 65;
}
if (kemId.equals(HpkeParameters.KemId.DHKEM_P384_HKDF_SHA384)) {
return 97;
}
if (kemId.equals(HpkeParameters.KemId.DHKEM_P521_HKDF_SHA512)) {
return 133;
}
throw new GeneralSecurityException("Unrecognized HPKE KEM identifier");
}
@AccessesPartialKey
private static HpkeKemPrivateKey createHpkeKemPrivateKey(HpkePrivateKey privateKey)
throws GeneralSecurityException {
HpkeParameters.KemId kemId = privateKey.getParameters().getKemId();
if (kemId.equals(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256)
|| kemId.equals(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256)
|| kemId.equals(HpkeParameters.KemId.DHKEM_P384_HKDF_SHA384)
|| kemId.equals(HpkeParameters.KemId.DHKEM_P521_HKDF_SHA512)) {
Bytes convertedPrivateKeyBytes =
Bytes.copyFrom(
privateKey.getPrivateKeyBytes().toByteArray(InsecureSecretKeyAccess.get()));
return new HpkeKemPrivateKey(
convertedPrivateKeyBytes, privateKey.getPublicKey().getPublicKeyBytes());
}
throw new GeneralSecurityException("Unrecognized HPKE KEM identifier");
}
public static HybridDecrypt create(HpkePrivateKey privateKey) throws GeneralSecurityException {
HpkeParameters parameters = privateKey.getParameters();
HpkeKem kem = HpkeEncrypt.createKem(parameters.getKemId());
HpkeKdf kdf = HpkeEncrypt.createKdf(parameters.getKdfId());
HpkeAead aead = HpkeEncrypt.createAead(parameters.getAeadId());
int encapsulatedKeyLength = encodingSizeInBytes(parameters.getKemId());
HpkeKemPrivateKey recipientKemPrivateKey = createHpkeKemPrivateKey(privateKey);
return new HpkeDecrypt(
recipientKemPrivateKey,
kem,
kdf,
aead,
encapsulatedKeyLength,
privateKey.getOutputPrefix());
}
@Override
public byte[] decrypt(final byte[] ciphertext, final byte[] contextInfo)
throws GeneralSecurityException {
int prefixAndHeaderLength = outputPrefix.length + encapsulatedKeyLength;
if (ciphertext.length < prefixAndHeaderLength) {
throw new GeneralSecurityException("Ciphertext is too short.");
}
if (!isPrefix(outputPrefix, ciphertext)) {
throw new GeneralSecurityException("Invalid ciphertext (output prefix mismatch)");
}
byte[] info = contextInfo;
if (info == null) {
info = new byte[0];
}
byte[] encapsulatedKey =
Arrays.copyOfRange(ciphertext, outputPrefix.length, prefixAndHeaderLength);
HpkeContext context =
HpkeContext.createRecipientContext(
encapsulatedKey, recipientPrivateKey, kem, kdf, aead, info);
return context.open(ciphertext, prefixAndHeaderLength, EMPTY_ASSOCIATED_DATA);
}
}