1 // Copyright 2023 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 //////////////////////////////////////////////////////////////////////////////// 16 17 package com.google.crypto.tink.hybrid; 18 19 import static com.google.crypto.tink.internal.TinkBugException.exceptionIsBug; 20 21 import com.google.crypto.tink.Parameters; 22 import com.google.crypto.tink.aead.AesCtrHmacAeadParameters; 23 import com.google.crypto.tink.aead.AesGcmParameters; 24 import com.google.crypto.tink.aead.XChaCha20Poly1305Parameters; 25 import com.google.crypto.tink.daead.AesSivParameters; 26 import com.google.crypto.tink.util.Bytes; 27 import com.google.errorprone.annotations.CanIgnoreReturnValue; 28 import com.google.errorprone.annotations.Immutable; 29 import java.security.GeneralSecurityException; 30 import java.util.Collections; 31 import java.util.HashSet; 32 import java.util.Objects; 33 import java.util.Set; 34 import javax.annotation.Nullable; 35 36 /** 37 * Parameters for an ECIES primitive with HKDF and AEAD encryption. 38 * 39 * <p>This API follows loosely ECIES ISO 18033-2 standard (Elliptic Curve Integrated Encryption 40 * Scheme, see http://www.shoup.net/iso/std6.pdf), but with some differences: 41 * 42 * <ul> 43 * <li>use of HKDF key derivation function (instead of KDF1 and KDF2) enabling the use of optional 44 * parameters to the key derivation function, which strenghten the overall security and allow 45 * for binding the key material to application-specific information (see RFC 5869) 46 * <li>use of modern AEAD/Deterministic AEAD schemes rather than "manual composition" of symmetric 47 * encryption with message authentication codes (as in DEM1, DEM2, and DEM3 schemes of ISO 48 * 18033-2) 49 * </ul> 50 */ 51 public final class EciesParameters extends HybridParameters { listAcceptedDemParameters()52 private static Set<Parameters> listAcceptedDemParameters() throws GeneralSecurityException { 53 HashSet<Parameters> acceptedDemParameters = new HashSet<>(); 54 // AES128_GCM_RAW 55 acceptedDemParameters.add( 56 AesGcmParameters.builder() 57 .setIvSizeBytes(12) 58 .setKeySizeBytes(16) 59 .setTagSizeBytes(16) 60 .setVariant(AesGcmParameters.Variant.NO_PREFIX) 61 .build()); 62 // AES256_GCM_RAW 63 acceptedDemParameters.add( 64 AesGcmParameters.builder() 65 .setIvSizeBytes(12) 66 .setKeySizeBytes(32) 67 .setTagSizeBytes(16) 68 .setVariant(AesGcmParameters.Variant.NO_PREFIX) 69 .build()); 70 // AES128_CTR_HMAC_SHA256_RAW 71 acceptedDemParameters.add( 72 AesCtrHmacAeadParameters.builder() 73 .setAesKeySizeBytes(16) 74 .setHmacKeySizeBytes(32) 75 .setTagSizeBytes(16) 76 .setIvSizeBytes(16) 77 .setHashType(AesCtrHmacAeadParameters.HashType.SHA256) 78 .setVariant(AesCtrHmacAeadParameters.Variant.NO_PREFIX) 79 .build()); 80 // AES256_CTR_HMAC_SHA256_RAW 81 acceptedDemParameters.add( 82 AesCtrHmacAeadParameters.builder() 83 .setAesKeySizeBytes(32) 84 .setHmacKeySizeBytes(32) 85 .setTagSizeBytes(32) 86 .setIvSizeBytes(16) 87 .setHashType(AesCtrHmacAeadParameters.HashType.SHA256) 88 .setVariant(AesCtrHmacAeadParameters.Variant.NO_PREFIX) 89 .build()); 90 // XCHACHA20_POLY1305_RAW 91 acceptedDemParameters.add(XChaCha20Poly1305Parameters.create()); 92 // AES256_SIV_RAW 93 acceptedDemParameters.add( 94 AesSivParameters.builder() 95 .setKeySizeBytes(64) 96 .setVariant(AesSivParameters.Variant.NO_PREFIX) 97 .build()); 98 return Collections.unmodifiableSet(acceptedDemParameters); 99 } 100 101 private static final Set<Parameters> acceptedDemParameters = 102 exceptionIsBug(() -> listAcceptedDemParameters()); 103 104 /** Description of the output prefix prepended to the ciphertext. */ 105 @Immutable 106 public static final class Variant { 107 /** Leading 0x01-byte followed by 4-byte key id (big endian format). */ 108 public static final Variant TINK = new Variant("TINK"); 109 110 /** Leading 0x00-byte followed by 4-byte key id (big endian format). */ 111 public static final Variant CRUNCHY = new Variant("CRUNCHY"); 112 113 /** Empty prefix. */ 114 public static final Variant NO_PREFIX = new Variant("NO_PREFIX"); 115 116 private final String name; 117 Variant(String name)118 private Variant(String name) { 119 this.name = name; 120 } 121 122 @Override toString()123 public String toString() { 124 return name; 125 } 126 } 127 128 /** The elliptic curve type used for the KEM. */ 129 @Immutable 130 public static final class CurveType { 131 public static final CurveType NIST_P256 = new CurveType("NIST_P256"); 132 public static final CurveType NIST_P384 = new CurveType("NIST_P384"); 133 public static final CurveType NIST_P521 = new CurveType("NIST_P521"); 134 public static final CurveType X25519 = new CurveType("X25519"); 135 136 private final String name; 137 CurveType(String name)138 private CurveType(String name) { 139 this.name = name; 140 } 141 142 @Override toString()143 public String toString() { 144 return name; 145 } 146 } 147 148 /** The Hash algorithm used for the KEM. */ 149 @Immutable 150 public static final class HashType { 151 public static final HashType SHA1 = new HashType("SHA1"); 152 public static final HashType SHA224 = new HashType("SHA224"); 153 public static final HashType SHA256 = new HashType("SHA256"); 154 public static final HashType SHA384 = new HashType("SHA384"); 155 public static final HashType SHA512 = new HashType("SHA512"); 156 157 private final String name; 158 HashType(String name)159 private HashType(String name) { 160 this.name = name; 161 } 162 163 @Override toString()164 public String toString() { 165 return name; 166 } 167 } 168 169 /** The Elliptic Curve Point Format. */ 170 @Immutable 171 public static final class PointFormat { 172 public static final PointFormat COMPRESSED = new PointFormat("COMPRESSED"); 173 public static final PointFormat UNCOMPRESSED = new PointFormat("UNCOMPRESSED"); 174 175 /** 176 * Like {@code UNCOMPRESSED}, but without the \x04 prefix. Crunchy uses this format. DO NOT USE 177 * unless you are a Crunchy user moving to Tink. 178 */ 179 public static final PointFormat LEGACY_UNCOMPRESSED = new PointFormat("LEGACY_UNCOMPRESSED"); 180 181 private final String name; 182 PointFormat(String name)183 private PointFormat(String name) { 184 this.name = name; 185 } 186 187 @Override toString()188 public String toString() { 189 return name; 190 } 191 } 192 193 /** Builds a new {@link EciesParameters} instance. */ 194 public static final class Builder { 195 private CurveType curveType = null; 196 private HashType hashType = null; 197 private PointFormat nistCurvePointFormat = null; 198 private Parameters demParameters = null; 199 private Variant variant = Variant.NO_PREFIX; 200 @Nullable private Bytes salt = null; 201 Builder()202 private Builder() {} 203 204 @CanIgnoreReturnValue setCurveType(CurveType curveType)205 public Builder setCurveType(CurveType curveType) { 206 this.curveType = curveType; 207 return this; 208 } 209 210 @CanIgnoreReturnValue setHashType(HashType hashType)211 public Builder setHashType(HashType hashType) { 212 this.hashType = hashType; 213 return this; 214 } 215 216 @CanIgnoreReturnValue setNistCurvePointFormat(PointFormat pointFormat)217 public Builder setNistCurvePointFormat(PointFormat pointFormat) { 218 this.nistCurvePointFormat = pointFormat; 219 return this; 220 } 221 222 /** 223 * Current implementation only accepts certain NO_PREFIX instances of AesGcmParameters, 224 * AesCtrHmacAeadParameters, XChaCha20Poly1305Parameters or AesSivParameters. 225 */ 226 @CanIgnoreReturnValue setDemParameters(Parameters demParameters)227 public Builder setDemParameters(Parameters demParameters) throws GeneralSecurityException { 228 if (!acceptedDemParameters.contains(demParameters)) { 229 throw new GeneralSecurityException( 230 "Invalid DEM parameters " 231 + demParameters 232 + "; only AES128_GCM_RAW, AES256_GCM_RAW, AES128_CTR_HMAC_SHA256_RAW," 233 + " AES256_CTR_HMAC_SHA256_RAW XCHACHA20_POLY1305_RAW and AES256_SIV_RAW are" 234 + " currently supported."); 235 } 236 this.demParameters = demParameters; 237 return this; 238 } 239 240 @CanIgnoreReturnValue setVariant(Variant variant)241 public Builder setVariant(Variant variant) { 242 this.variant = variant; 243 return this; 244 } 245 246 /** Defaults to null if not set. */ 247 @CanIgnoreReturnValue setSalt(Bytes salt)248 public Builder setSalt(Bytes salt) { 249 if (salt.size() == 0) { 250 this.salt = null; 251 return this; 252 } 253 254 this.salt = salt; 255 return this; 256 } 257 build()258 public EciesParameters build() throws GeneralSecurityException { 259 if (curveType == null) { 260 throw new GeneralSecurityException("Elliptic curve type is not set"); 261 } 262 if (hashType == null) { 263 throw new GeneralSecurityException("Hash type is not set"); 264 } 265 if (demParameters == null) { 266 throw new GeneralSecurityException("DEM parameters are not set"); 267 } 268 if (variant == null) { 269 throw new GeneralSecurityException("Variant is not set"); 270 } 271 272 if (curveType != CurveType.X25519 && nistCurvePointFormat == null) { 273 throw new GeneralSecurityException("Point format is not set"); 274 } 275 if (curveType == CurveType.X25519 && nistCurvePointFormat != null) { 276 throw new GeneralSecurityException("For Curve25519 point format must not be set"); 277 } 278 return new EciesParameters( 279 curveType, hashType, nistCurvePointFormat, demParameters, variant, salt); 280 } 281 } 282 283 private final CurveType curveType; 284 private final HashType hashType; 285 @Nullable private final PointFormat nistCurvePointFormat; 286 private final Variant variant; 287 private final Parameters demParameters; 288 @Nullable private final Bytes salt; 289 EciesParameters( CurveType curveType, HashType hashType, @Nullable PointFormat pointFormat, Parameters demParameters, Variant variant, Bytes salt)290 private EciesParameters( 291 CurveType curveType, 292 HashType hashType, 293 @Nullable PointFormat pointFormat, 294 Parameters demParameters, 295 Variant variant, 296 Bytes salt) { 297 this.curveType = curveType; 298 this.hashType = hashType; 299 this.nistCurvePointFormat = pointFormat; 300 this.demParameters = demParameters; 301 this.variant = variant; 302 this.salt = salt; 303 } 304 builder()305 public static Builder builder() { 306 return new Builder(); 307 } 308 getCurveType()309 public CurveType getCurveType() { 310 return curveType; 311 } 312 getHashType()313 public HashType getHashType() { 314 return hashType; 315 } 316 317 @Nullable getNistCurvePointFormat()318 public PointFormat getNistCurvePointFormat() { 319 return nistCurvePointFormat; 320 } 321 getDemParameters()322 public Parameters getDemParameters() { 323 return demParameters; 324 } 325 getVariant()326 public Variant getVariant() { 327 return variant; 328 } 329 330 /** 331 * Gets the salt value, which defaults to null if not set. 332 * 333 * <p>This class does not store an RFC compliant default value and the converion must be done in 334 * the implementation (meaning that a null salt must be converted to a string of zeros that is of 335 * the length of the hash function output, as per RFC 5869). 336 */ 337 @Nullable getSalt()338 public Bytes getSalt() { 339 return salt; 340 } 341 342 @Override hasIdRequirement()343 public boolean hasIdRequirement() { 344 return variant != Variant.NO_PREFIX; 345 } 346 347 @Override equals(Object o)348 public boolean equals(Object o) { 349 if (!(o instanceof EciesParameters)) { 350 return false; 351 } 352 EciesParameters that = (EciesParameters) o; 353 return Objects.equals(that.getCurveType(), getCurveType()) 354 && Objects.equals(that.getHashType(), getHashType()) 355 && Objects.equals(that.getNistCurvePointFormat(), getNistCurvePointFormat()) 356 && Objects.equals(that.getDemParameters(), getDemParameters()) 357 && Objects.equals(that.getVariant(), getVariant()) 358 && Objects.equals(that.getSalt(), getSalt()); 359 } 360 361 @Override hashCode()362 public int hashCode() { 363 return Objects.hash( 364 EciesParameters.class, 365 curveType, 366 hashType, 367 nistCurvePointFormat, 368 demParameters, 369 variant, 370 salt); 371 } 372 373 @Override toString()374 public String toString() { 375 return String.format( 376 "EciesParameters(curveType=%s, hashType=%s, pointFormat=%s, demParameters=%s, variant=%s," 377 + " salt=%s)", 378 curveType, hashType, nistCurvePointFormat, demParameters, variant, salt); 379 } 380 } 381