com.google.crypto.tink.hybrid.HpkePrivateKey Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tink-android Show documentation
Show all versions of tink-android Show documentation
Tink is a small cryptographic library that provides a safe, simple, agile and fast way to accomplish some common cryptographic tasks.
The newest version!
// Copyright 2023 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;
import com.google.crypto.tink.AccessesPartialKey;
import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.Key;
import com.google.crypto.tink.internal.BigIntegerEncoding;
import com.google.crypto.tink.internal.EllipticCurvesUtil;
import com.google.crypto.tink.subtle.EllipticCurves;
import com.google.crypto.tink.subtle.X25519;
import com.google.crypto.tink.util.SecretBytes;
import com.google.errorprone.annotations.Immutable;
import com.google.errorprone.annotations.RestrictedApi;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.util.Arrays;
/** Representation of the decryption function for an HPKE hybrid encryption primitive. */
@Immutable
public final class HpkePrivateKey extends HybridPrivateKey {
private final HpkePublicKey publicKey;
private final SecretBytes privateKeyBytes;
private HpkePrivateKey(HpkePublicKey publicKey, SecretBytes privateKeyBytes) {
this.publicKey = publicKey;
this.privateKeyBytes = privateKeyBytes;
}
private static void validatePrivateKeyByteLength(
HpkeParameters.KemId kemId, SecretBytes privateKeyBytes) throws GeneralSecurityException {
// Key lengths from 'Nsk' column in https://www.rfc-editor.org/rfc/rfc9180.html#table-2.
int keyLengthInBytes = privateKeyBytes.size();
String parameterizedErrorMessage =
"Encoded private key byte length for " + kemId + " must be %d, not " + keyLengthInBytes;
if (kemId == HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) {
if (keyLengthInBytes != 32) {
throw new GeneralSecurityException(String.format(parameterizedErrorMessage, 32));
}
return;
}
if (kemId == HpkeParameters.KemId.DHKEM_P384_HKDF_SHA384) {
if (keyLengthInBytes != 48) {
throw new GeneralSecurityException(String.format(parameterizedErrorMessage, 48));
}
return;
}
if (kemId == HpkeParameters.KemId.DHKEM_P521_HKDF_SHA512) {
if (keyLengthInBytes != 66) {
throw new GeneralSecurityException(String.format(parameterizedErrorMessage, 66));
}
return;
}
if (kemId == HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256) {
if (keyLengthInBytes != 32) {
throw new GeneralSecurityException(String.format(parameterizedErrorMessage, 32));
}
return;
}
throw new GeneralSecurityException("Unable to validate private key length for " + kemId);
}
private static boolean isNistKem(HpkeParameters.KemId kemId) {
return kemId == HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256
|| kemId == HpkeParameters.KemId.DHKEM_P384_HKDF_SHA384
|| kemId == HpkeParameters.KemId.DHKEM_P521_HKDF_SHA512;
}
private static ECParameterSpec getNistCurveParams(HpkeParameters.KemId kemId) {
if (kemId == HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) {
return EllipticCurves.getNistP256Params();
}
if (kemId == HpkeParameters.KemId.DHKEM_P384_HKDF_SHA384) {
return EllipticCurves.getNistP384Params();
}
if (kemId == HpkeParameters.KemId.DHKEM_P521_HKDF_SHA512) {
return EllipticCurves.getNistP521Params();
}
throw new IllegalArgumentException("Unable to determine NIST curve params for " + kemId);
}
/**
* Confirms that the private key encoded as {@code privateKeyBytes} corresponds to the public key
* encoded as {@code publicKeyBytes} given the HPKE {@code kemId}.
*/
private static void validateKeyPair(
HpkeParameters.KemId kemId, byte[] publicKeyBytes, byte[] privateKeyBytes)
throws GeneralSecurityException {
if (isNistKem(kemId)) {
ECParameterSpec spec = getNistCurveParams(kemId);
BigInteger order = spec.getOrder();
BigInteger privateKey = BigIntegerEncoding.fromUnsignedBigEndianBytes(privateKeyBytes);
if ((privateKey.signum() <= 0) || (privateKey.compareTo(order) >= 0)) {
throw new GeneralSecurityException("Invalid private key.");
}
ECPoint expectedPoint = EllipticCurvesUtil.multiplyByGenerator(privateKey, spec);
ECPoint publicPoint =
EllipticCurves.pointDecode(
spec.getCurve(), EllipticCurves.PointFormatType.UNCOMPRESSED, publicKeyBytes);
if (!expectedPoint.equals(publicPoint)) {
throw new GeneralSecurityException("Invalid private key for public key.");
}
return;
}
if (kemId == HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256) {
byte[] expectedPublicKeyBytes = X25519.publicFromPrivate(privateKeyBytes);
if (!Arrays.equals(expectedPublicKeyBytes, publicKeyBytes)) {
throw new GeneralSecurityException("Invalid private key for public key.");
}
return;
}
throw new IllegalArgumentException("Unable to validate key pair for " + kemId);
}
/**
* Creates a new HPKE private key.
*
* @param publicKey Corresponding HPKE public key for this private key
* @param privateKeyBytes Private key encoded according to
* https://www.rfc-editor.org/rfc/rfc9180.html#section-7.1.2
*/
@AccessesPartialKey
@RestrictedApi(
explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey",
link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys",
allowedOnPath = ".*Test\\.java",
allowlistAnnotations = {AccessesPartialKey.class})
public static HpkePrivateKey create(HpkePublicKey publicKey, SecretBytes privateKeyBytes)
throws GeneralSecurityException {
if (publicKey == null) {
throw new GeneralSecurityException(
"HPKE private key cannot be constructed without an HPKE public key");
}
if (privateKeyBytes == null) {
throw new GeneralSecurityException("HPKE private key cannot be constructed without secret");
}
validatePrivateKeyByteLength(publicKey.getParameters().getKemId(), privateKeyBytes);
validateKeyPair(
publicKey.getParameters().getKemId(),
publicKey.getPublicKeyBytes().toByteArray(),
privateKeyBytes.toByteArray(InsecureSecretKeyAccess.get()));
return new HpkePrivateKey(publicKey, privateKeyBytes);
}
@RestrictedApi(
explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey",
link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys",
allowedOnPath = ".*Test\\.java",
allowlistAnnotations = {AccessesPartialKey.class})
public SecretBytes getPrivateKeyBytes() {
return privateKeyBytes;
}
@Override
public HpkeParameters getParameters() {
return publicKey.getParameters();
}
@Override
public HpkePublicKey getPublicKey() {
return publicKey;
}
@Override
public boolean equalsKey(Key o) {
if (!(o instanceof HpkePrivateKey)) {
return false;
}
HpkePrivateKey other = (HpkePrivateKey) o;
return publicKey.equalsKey(other.publicKey)
&& privateKeyBytes.equalsSecretBytes(other.privateKeyBytes);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy