com.google.crypto.tink.hybrid.internal.HpkeContext Maven / Gradle / Ivy
// 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 com.google.crypto.tink.AccessesPartialKey;
import com.google.crypto.tink.hybrid.HpkePublicKey;
import com.google.crypto.tink.internal.BigIntegerEncoding;
import com.google.crypto.tink.subtle.Bytes;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
/**
* Hybrid Public Key Encryption (HPKE) context for either a sender or a recipient.
*
* https://www.rfc-editor.org/rfc/rfc9180.html#name-creating-the-encryption-con
*/
@ThreadSafe
public final class HpkeContext {
private static final byte[] EMPTY_IKM = new byte[0];
private final HpkeAead aead;
private final BigInteger maxSequenceNumber;
private final byte[] key;
private final byte[] baseNonce;
private final byte[] encapsulatedKey;
@GuardedBy("this")
private BigInteger sequenceNumber;
private HpkeContext(
byte[] encapsulatedKey,
byte[] key,
byte[] baseNonce,
BigInteger maxSequenceNumber,
HpkeAead aead) {
this.encapsulatedKey = encapsulatedKey;
this.key = key;
this.baseNonce = baseNonce;
this.sequenceNumber = BigInteger.ZERO;
this.maxSequenceNumber = maxSequenceNumber;
this.aead = aead;
}
/** Helper function factored out to facilitate unit testing. */
static HpkeContext createContext(
byte[] mode,
byte[] encapsulatedKey,
byte[] sharedSecret,
HpkeKem kem,
HpkeKdf kdf,
HpkeAead aead,
byte[] info)
throws GeneralSecurityException {
byte[] suiteId = HpkeUtil.hpkeSuiteId(kem.getKemId(), kdf.getKdfId(), aead.getAeadId());
byte[] pskIdHash = kdf.labeledExtract(HpkeUtil.EMPTY_SALT, EMPTY_IKM, "psk_id_hash", suiteId);
byte[] infoHash = kdf.labeledExtract(HpkeUtil.EMPTY_SALT, info, "info_hash", suiteId);
byte[] keyScheduleContext = Bytes.concat(mode, pskIdHash, infoHash);
byte[] secret = kdf.labeledExtract(sharedSecret, EMPTY_IKM, "secret", suiteId);
byte[] key = kdf.labeledExpand(secret, keyScheduleContext, "key", suiteId, aead.getKeyLength());
byte[] baseNonce =
kdf.labeledExpand(secret, keyScheduleContext, "base_nonce", suiteId, aead.getNonceLength());
BigInteger maxSeqNo = maxSequenceNumber(aead.getNonceLength());
return new HpkeContext(encapsulatedKey, key, baseNonce, maxSeqNo, aead);
}
/**
* Creates HPKE sender context according to KeySchedule() defined in
* https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1-9.
*
* @param recipientPublicKey recipient's public key (pkR)
* @param kem key encapsulation mechanism primitive
* @param kdf key derivation function primitive
* @param aead authenticated encryption with associated data primitive
* @param info application-specific information parameter to influence key generation
*/
static HpkeContext createSenderContext(
byte[] recipientPublicKey, HpkeKem kem, HpkeKdf kdf, HpkeAead aead, byte[] info)
throws GeneralSecurityException {
HpkeKemEncapOutput encapOutput = kem.encapsulate(recipientPublicKey);
byte[] encapsulatedKey = encapOutput.getEncapsulatedKey();
byte[] sharedSecret = encapOutput.getSharedSecret();
return createContext(HpkeUtil.BASE_MODE, encapsulatedKey, sharedSecret, kem, kdf, aead, info);
}
/**
* Creates HPKE sender context with authentication according to KeySchedule() defined in
* https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.3.
*
* @param recipientPublicKey recipient's public key (pkR)
* @param kem key encapsulation mechanism primitive
* @param kdf key derivation function primitive
* @param aead authenticated encryption with associated data primitive
* @param info application-specific information parameter to influence key generation
* @param senderPrivateKey sender's private key (skS)
*/
@AccessesPartialKey
public static HpkeContext createAuthSenderContext(
HpkePublicKey recipientPublicKey,
HpkeKem kem,
HpkeKdf kdf,
HpkeAead aead,
byte[] info,
HpkeKemPrivateKey senderPrivateKey)
throws GeneralSecurityException {
HpkeKemEncapOutput encapOutput =
kem.authEncapsulate(recipientPublicKey.getPublicKeyBytes().toByteArray(), senderPrivateKey);
byte[] encapsulatedKey = encapOutput.getEncapsulatedKey();
byte[] sharedSecret = encapOutput.getSharedSecret();
return createContext(HpkeUtil.AUTH_MODE, encapsulatedKey, sharedSecret, kem, kdf, aead, info);
}
/**
* Creates HPKE sender recipient context according to KeySchedule() defined in
* https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1-9.
*
* @param encapsulatedKey encapsulated key (enc)
* @param recipientPrivateKey recipient's private key (skR)
* @param kem key encapsulation mechanism primitive
* @param kdf key derivation function primitive
* @param aead authenticated encryption with associated data primitive
* @param info application-specific information parameter to influence key generation
*/
public static HpkeContext createRecipientContext(
byte[] encapsulatedKey,
HpkeKemPrivateKey recipientPrivateKey,
HpkeKem kem,
HpkeKdf kdf,
HpkeAead aead,
byte[] info)
throws GeneralSecurityException {
byte[] sharedSecret = kem.decapsulate(encapsulatedKey, recipientPrivateKey);
return createContext(HpkeUtil.BASE_MODE, encapsulatedKey, sharedSecret, kem, kdf, aead, info);
}
/**
* Creates HPKE recipient context with authentication according to KeySchedule() defined in
* https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.3.
*
* @param encapsulatedKey encapsulated key (enc)
* @param recipientPrivateKey recipient's private key (skR)
* @param kem key encapsulation mechanism primitive
* @param kdf key derivation function primitive
* @param aead authenticated encryption with associated data primitive
* @param info application-specific information parameter to influence key generation
* @param senderPublicKey sender's public key (pkS)
*/
@AccessesPartialKey
public static HpkeContext createAuthRecipientContext(
byte[] encapsulatedKey,
HpkeKemPrivateKey recipientPrivateKey,
HpkeKem kem,
HpkeKdf kdf,
HpkeAead aead,
byte[] info,
HpkePublicKey senderPublicKey)
throws GeneralSecurityException {
byte[] sharedSecret =
kem.authDecapsulate(
encapsulatedKey,
recipientPrivateKey,
senderPublicKey.getPublicKeyBytes().toByteArray());
return createContext(HpkeUtil.AUTH_MODE, encapsulatedKey, sharedSecret, kem, kdf, aead, info);
}
private static BigInteger maxSequenceNumber(int nonceLength) {
return BigInteger.ONE.shiftLeft(8 * nonceLength).subtract(BigInteger.ONE);
}
@GuardedBy("this")
private void incrementSequenceNumber() throws GeneralSecurityException {
if (sequenceNumber.compareTo(maxSequenceNumber) >= 0) {
throw new GeneralSecurityException("message limit reached");
}
sequenceNumber = sequenceNumber.add(BigInteger.ONE);
}
/** ComputeNonce() from https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2-11. */
@GuardedBy("this")
private byte[] computeNonce() throws GeneralSecurityException {
return Bytes.xor(
baseNonce,
BigIntegerEncoding.toBigEndianBytesOfFixedLength(sequenceNumber, aead.getNonceLength()));
}
/** Returns the next nonce to use for seal/open. Also, increments the sequence number. */
private synchronized byte[] computeNonceAndIncrementSequenceNumber()
throws GeneralSecurityException {
byte[] nonce = computeNonce();
incrementSequenceNumber();
return nonce;
}
byte[] getKey() {
return key;
}
byte[] getBaseNonce() {
return baseNonce;
}
public byte[] getEncapsulatedKey() {
return encapsulatedKey;
}
/**
* Performs AEAD encryption of {@code plaintext} with {@code associatedData} according to
* ContextS.Seal() defined in https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2-8.
*
* @return ciphertext
*/
public byte[] seal(byte[] plaintext, byte[] associatedData) throws GeneralSecurityException {
byte[] nonce = computeNonceAndIncrementSequenceNumber();
return aead.seal(key, nonce, plaintext, associatedData);
}
byte[] seal(byte[] plaintext, int ciphertextOffset, byte[] associatedData)
throws GeneralSecurityException {
byte[] nonce = computeNonceAndIncrementSequenceNumber();
return aead.seal(key, nonce, plaintext, ciphertextOffset, associatedData);
}
/**
* Performs AEAD decryption of {@code ciphertext} with {@code associatedData} according to
* ContextR.Open() defined in https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2-10.
*
* @return plaintext
*/
public byte[] open(byte[] ciphertext, byte[] associatedData) throws GeneralSecurityException {
return open(ciphertext, /* ciphertextOffset = */ 0, associatedData);
}
byte[] open(byte[] ciphertext, int ciphertextOffset, byte[] associatedData)
throws GeneralSecurityException {
byte[] nonce = computeNonceAndIncrementSequenceNumber();
return aead.open(key, nonce, ciphertext, ciphertextOffset, associatedData);
}
}