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.aead; 18 19 import com.google.errorprone.annotations.CanIgnoreReturnValue; 20 import com.google.errorprone.annotations.Immutable; 21 import java.security.GeneralSecurityException; 22 import java.security.InvalidAlgorithmParameterException; 23 import java.util.Objects; 24 import javax.annotation.Nullable; 25 26 /** Describes the parameters of an {@link AesCtrHmacAeadKey}. */ 27 public final class AesCtrHmacAeadParameters extends AeadParameters { 28 private static final int PREFIX_SIZE_IN_BYTES = 5; 29 /** 30 * Describes how the prefix is computed. For AEAD there are three main possibilities: NO_PREFIX 31 * (empty prefix), TINK (prefix the ciphertext with 0x01 followed by a 4-byte key id in big endian 32 * format) or CRUNCHY (prefix the ciphertext with 0x00 followed by a 4-byte key id in big endian 33 * format) 34 */ 35 @Immutable 36 public static final class Variant { 37 public static final Variant TINK = new Variant("TINK"); 38 public static final Variant CRUNCHY = new Variant("CRUNCHY"); 39 public static final Variant NO_PREFIX = new Variant("NO_PREFIX"); 40 41 private final String name; 42 Variant(String name)43 private Variant(String name) { 44 this.name = name; 45 } 46 47 @Override toString()48 public String toString() { 49 return name; 50 } 51 } 52 53 /** The Hash algorithm used for the HMAC. */ 54 @Immutable 55 public static final class HashType { 56 public static final HashType SHA1 = new HashType("SHA1"); 57 public static final HashType SHA224 = new HashType("SHA224"); 58 public static final HashType SHA256 = new HashType("SHA256"); 59 public static final HashType SHA384 = new HashType("SHA384"); 60 public static final HashType SHA512 = new HashType("SHA512"); 61 62 private final String name; 63 HashType(String name)64 private HashType(String name) { 65 this.name = name; 66 } 67 68 @Override toString()69 public String toString() { 70 return name; 71 } 72 } 73 74 /** Builds a new AesCtrHmacAeadParameters instance. */ 75 public static final class Builder { 76 @Nullable private Integer aesKeySizeBytes = null; 77 @Nullable private Integer hmacKeySizeBytes = null; 78 @Nullable private Integer ivSizeBytes = null; 79 @Nullable private Integer tagSizeBytes = null; 80 private HashType hashType = null; 81 private Variant variant = Variant.NO_PREFIX; 82 Builder()83 private Builder() {} 84 85 /** Accepts key sizes of 16, 24 or 32 bytes. */ 86 @CanIgnoreReturnValue setAesKeySizeBytes(int aesKeySizeBytes)87 public Builder setAesKeySizeBytes(int aesKeySizeBytes) throws GeneralSecurityException { 88 if (aesKeySizeBytes != 16 && aesKeySizeBytes != 24 && aesKeySizeBytes != 32) { 89 throw new InvalidAlgorithmParameterException( 90 String.format( 91 "Invalid key size %d; only 16-byte, 24-byte and 32-byte AES keys are supported", 92 aesKeySizeBytes)); 93 } 94 this.aesKeySizeBytes = aesKeySizeBytes; 95 return this; 96 } 97 98 /** Accepts key sizes of at least 16 bytes. */ 99 @CanIgnoreReturnValue setHmacKeySizeBytes(int hmacKeySizeBytes)100 public Builder setHmacKeySizeBytes(int hmacKeySizeBytes) throws GeneralSecurityException { 101 if (hmacKeySizeBytes < 16) { 102 throw new InvalidAlgorithmParameterException( 103 String.format( 104 "Invalid key size in bytes %d; HMAC key must be at least 16 bytes", 105 hmacKeySizeBytes)); 106 } 107 this.hmacKeySizeBytes = hmacKeySizeBytes; 108 return this; 109 } 110 111 /** IV size must be between 12 and 16 bytes. */ 112 @CanIgnoreReturnValue setIvSizeBytes(int ivSizeBytes)113 public Builder setIvSizeBytes(int ivSizeBytes) throws GeneralSecurityException { 114 if (ivSizeBytes < 12 || ivSizeBytes > 16) { 115 throw new GeneralSecurityException( 116 String.format( 117 "Invalid IV size in bytes %d; IV size must be between 12 and 16 bytes", 118 ivSizeBytes)); 119 } 120 this.ivSizeBytes = ivSizeBytes; 121 return this; 122 } 123 124 @CanIgnoreReturnValue setTagSizeBytes(int tagSizeBytes)125 public Builder setTagSizeBytes(int tagSizeBytes) throws GeneralSecurityException { 126 if (tagSizeBytes < 10) { 127 throw new GeneralSecurityException( 128 String.format("Invalid tag size in bytes %d; must be at least 10 bytes", tagSizeBytes)); 129 } 130 this.tagSizeBytes = tagSizeBytes; 131 return this; 132 } 133 134 @CanIgnoreReturnValue setVariant(Variant variant)135 public Builder setVariant(Variant variant) { 136 this.variant = variant; 137 return this; 138 } 139 140 @CanIgnoreReturnValue setHashType(HashType hashType)141 public Builder setHashType(HashType hashType) { 142 this.hashType = hashType; 143 return this; 144 } 145 validateTagSizeBytes(int tagSizeBytes, HashType hashType)146 private static void validateTagSizeBytes(int tagSizeBytes, HashType hashType) 147 throws GeneralSecurityException { 148 if (hashType == HashType.SHA1) { 149 if (tagSizeBytes > 20) { 150 throw new GeneralSecurityException( 151 String.format( 152 "Invalid tag size in bytes %d; can be at most 20 bytes for SHA1", tagSizeBytes)); 153 } 154 return; 155 } 156 if (hashType == HashType.SHA224) { 157 if (tagSizeBytes > 28) { 158 throw new GeneralSecurityException( 159 String.format( 160 "Invalid tag size in bytes %d; can be at most 28 bytes for SHA224", 161 tagSizeBytes)); 162 } 163 return; 164 } 165 if (hashType == HashType.SHA256) { 166 if (tagSizeBytes > 32) { 167 throw new GeneralSecurityException( 168 String.format( 169 "Invalid tag size in bytes %d; can be at most 32 bytes for SHA256", 170 tagSizeBytes)); 171 } 172 return; 173 } 174 if (hashType == HashType.SHA384) { 175 if (tagSizeBytes > 48) { 176 throw new GeneralSecurityException( 177 String.format( 178 "Invalid tag size in bytes %d; can be at most 48 bytes for SHA384", 179 tagSizeBytes)); 180 } 181 return; 182 } 183 if (hashType == HashType.SHA512) { 184 if (tagSizeBytes > 64) { 185 throw new GeneralSecurityException( 186 String.format( 187 "Invalid tag size in bytes %d; can be at most 64 bytes for SHA512", 188 tagSizeBytes)); 189 } 190 return; 191 } 192 throw new GeneralSecurityException( 193 "unknown hash type; must be SHA1, SHA224, SHA256, SHA384 or SHA512"); 194 } 195 build()196 public AesCtrHmacAeadParameters build() throws GeneralSecurityException { 197 if (aesKeySizeBytes == null) { 198 throw new GeneralSecurityException("AES key size is not set"); 199 } 200 if (hmacKeySizeBytes == null) { 201 throw new GeneralSecurityException("HMAC key size is not set"); 202 } 203 if (ivSizeBytes == null) { 204 throw new GeneralSecurityException("iv size is not set"); 205 } 206 if (tagSizeBytes == null) { 207 throw new GeneralSecurityException("tag size is not set"); 208 } 209 if (hashType == null) { 210 throw new GeneralSecurityException("hash type is not set"); 211 } 212 if (variant == null) { 213 throw new GeneralSecurityException("variant is not set"); 214 } 215 validateTagSizeBytes(tagSizeBytes, hashType); 216 return new AesCtrHmacAeadParameters( 217 aesKeySizeBytes, hmacKeySizeBytes, ivSizeBytes, tagSizeBytes, variant, hashType); 218 } 219 } 220 221 private final int aesKeySizeBytes; 222 private final int hmacKeySizeBytes; 223 private final int ivSizeBytes; 224 private final int tagSizeBytes; 225 private final Variant variant; 226 private final HashType hashType; 227 AesCtrHmacAeadParameters( int aesKeySizeBytes, int hmacKeySizeBytes, int ivSizeBytes, int tagSizeBytes, Variant variant, HashType hashType)228 private AesCtrHmacAeadParameters( 229 int aesKeySizeBytes, 230 int hmacKeySizeBytes, 231 int ivSizeBytes, 232 int tagSizeBytes, 233 Variant variant, 234 HashType hashType) { 235 this.aesKeySizeBytes = aesKeySizeBytes; 236 this.hmacKeySizeBytes = hmacKeySizeBytes; 237 this.ivSizeBytes = ivSizeBytes; 238 this.tagSizeBytes = tagSizeBytes; 239 this.variant = variant; 240 this.hashType = hashType; 241 } 242 builder()243 public static Builder builder() { 244 return new Builder(); 245 } 246 getAesKeySizeBytes()247 public int getAesKeySizeBytes() { 248 return aesKeySizeBytes; 249 } 250 getHmacKeySizeBytes()251 public int getHmacKeySizeBytes() { 252 return hmacKeySizeBytes; 253 } 254 getTagSizeBytes()255 public int getTagSizeBytes() { 256 return tagSizeBytes; 257 } 258 getIvSizeBytes()259 public int getIvSizeBytes() { 260 return ivSizeBytes; 261 } 262 263 /** 264 * Returns the size of the overhead added to the actual ciphertext (i.e. the size of the IV plus 265 * the size of the security relevant tag plus the size of the prefix with which this key prefixes 266 * the ciphertext. 267 */ getCiphertextOverheadSizeBytes()268 public int getCiphertextOverheadSizeBytes() { 269 if (variant == Variant.NO_PREFIX) { 270 return getTagSizeBytes() + getIvSizeBytes(); 271 } 272 if (variant == Variant.TINK || variant == Variant.CRUNCHY) { 273 return getTagSizeBytes() + getIvSizeBytes() + PREFIX_SIZE_IN_BYTES; 274 } 275 throw new IllegalStateException("Unknown variant"); 276 } 277 278 /** Returns a variant object. */ getVariant()279 public Variant getVariant() { 280 return variant; 281 } 282 283 /** Returns a hash type object. */ getHashType()284 public HashType getHashType() { 285 return hashType; 286 } 287 288 @Override equals(Object o)289 public boolean equals(Object o) { 290 if (!(o instanceof AesCtrHmacAeadParameters)) { 291 return false; 292 } 293 AesCtrHmacAeadParameters that = (AesCtrHmacAeadParameters) o; 294 return that.getAesKeySizeBytes() == getAesKeySizeBytes() 295 && that.getHmacKeySizeBytes() == getHmacKeySizeBytes() 296 && that.getIvSizeBytes() == getIvSizeBytes() 297 && that.getTagSizeBytes() == getTagSizeBytes() 298 && that.getVariant() == getVariant() 299 && that.getHashType() == getHashType(); 300 } 301 302 @Override hashCode()303 public int hashCode() { 304 return Objects.hash( 305 AesCtrHmacAeadParameters.class, 306 aesKeySizeBytes, 307 hmacKeySizeBytes, 308 ivSizeBytes, 309 tagSizeBytes, 310 variant, 311 hashType); 312 } 313 314 @Override hasIdRequirement()315 public boolean hasIdRequirement() { 316 return variant != Variant.NO_PREFIX; 317 } 318 319 @Override toString()320 public String toString() { 321 return "AesCtrHmacAead Parameters (variant: " 322 + variant 323 + ", hashType: " 324 + hashType 325 + ", " 326 + ivSizeBytes 327 + "-byte IV, and " 328 + tagSizeBytes 329 + "-byte tags, and " 330 + aesKeySizeBytes 331 + "-byte AES key, and " 332 + hmacKeySizeBytes 333 + "-byte HMAC key)"; 334 } 335 } 336