1 // Copyright 2017 Google Inc. 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.subtle; 18 19 import com.google.crypto.tink.AccessesPartialKey; 20 import com.google.crypto.tink.InsecureSecretKeyAccess; 21 import com.google.crypto.tink.StreamingAead; 22 import com.google.crypto.tink.streamingaead.AesGcmHkdfStreamingKey; 23 import com.google.crypto.tink.streamingaead.AesGcmHkdfStreamingParameters.HashType; 24 import java.nio.ByteBuffer; 25 import java.nio.ByteOrder; 26 import java.security.GeneralSecurityException; 27 import java.security.InvalidAlgorithmParameterException; 28 import java.util.Arrays; 29 import javax.crypto.Cipher; 30 import javax.crypto.spec.GCMParameterSpec; 31 import javax.crypto.spec.SecretKeySpec; 32 33 /** 34 * Streaming encryption using {@code AES-GCM} with {@code HKDF} as key derivation function. 35 * 36 * <p>Each ciphertext uses a new AES-GCM key that is derived from the key derivation key, a randomly 37 * chosen salt of the same size as the key and a nonce prefix. 38 * 39 * <p>The format of a ciphertext is header || segment_0 || segment_1 || ... || segment_k. The header 40 * has size this.getHeaderLength(). Its format is headerLength || salt || prefix. where headerLength 41 * is 1 byte determining the size of the header, salt is a salt used in the key derivation and 42 * prefix is the prefix of the nonce. In principle headerLength is redundant information, since the 43 * length of the header can be determined from the key size. 44 * 45 * <p>segment_i is the i-th segment of the ciphertext. The size of segment_1 .. segment_{k-1} is 46 * ciphertextSegmentSize. segment_0 is shorter, so that segment_0, the header and other information 47 * of size firstSegmentOffset align with ciphertextSegmentSize. 48 * 49 * <p>This class does not work on Android KitKat (API level 19) or older. 50 * 51 * @since 1.1.0 52 */ 53 @AccessesPartialKey 54 public final class AesGcmHkdfStreaming extends NonceBasedStreamingAead { 55 // TODO(bleichen): Some things that are not yet decided: 56 // - What can we assume about the state of objects after getting an exception? 57 // - Should there be a simple test to detect invalid ciphertext offsets? 58 // - Should we encode the size of the header? 59 // - Should we encode the size of the segments? 60 // - Should a version be included in the header to allow header modification? 61 // - Should we allow other information, header and the first segment the to be a multiple of 62 // ciphertextSegmentSize? 63 // - This implementation fixes a number of parameters. Should there be more options? 64 // - Should we authenticate ciphertextSegmentSize and firstSegmentSize? 65 // If an attacker can change these parameters then this would allow to move 66 // the position of plaintext in the file. While these parameters are currently 67 // specified by the key it is unclear whether this will remain so in the future. 68 // 69 // The size of the IVs for GCM 70 private static final int NONCE_SIZE_IN_BYTES = 12; 71 72 // The nonce has the format nonce_prefix || ctr || last_block 73 // The nonce_prefix is constant for the whole file. 74 // The ctr is a 32 bit ctr, the last_block is 1 if this is the 75 // last block of the file and 0 otherwise. 76 private static final int NONCE_PREFIX_IN_BYTES = 7; 77 78 // The size of the tags of each ciphertext segment. 79 private static final int TAG_SIZE_IN_BYTES = 16; 80 81 private final int keySizeInBytes; 82 private final int ciphertextSegmentSize; 83 private final int plaintextSegmentSize; 84 private final int firstSegmentOffset; 85 private final String hkdfAlg; 86 private final byte[] ikm; 87 88 /** 89 * Initializes a streaming primitive with a key derivation key and encryption parameters. 90 * 91 * @param ikm input keying material used to derive sub keys. 92 * @param hkdfAlg the JCE MAC algorithm name, e.g., HmacSha256, used for the HKDF key derivation. 93 * @param keySizeInBytes the key size of the sub keys 94 * @param ciphertextSegmentSize the size of ciphertext segments. 95 * @param firstSegmentOffset the offset of the first ciphertext segment. That means the first 96 * segment has size ciphertextSegmentSize - getHeaderLength() - firstSegmentOffset 97 * @throws InvalidAlgorithmParameterException if ikm is too short, the key size not supported or 98 * ciphertextSegmentSize is to short. 99 */ AesGcmHkdfStreaming(byte[] ikm, String hkdfAlg, int keySizeInBytes, int ciphertextSegmentSize, int firstSegmentOffset)100 public AesGcmHkdfStreaming(byte[] ikm, String hkdfAlg, int keySizeInBytes, 101 int ciphertextSegmentSize, int firstSegmentOffset) 102 throws InvalidAlgorithmParameterException { 103 if (ikm.length < 16 || ikm.length < keySizeInBytes) { 104 throw new InvalidAlgorithmParameterException( 105 "ikm too short, must be >= " + Math.max(16, keySizeInBytes)); 106 } 107 Validators.validateAesKeySize(keySizeInBytes); 108 if (ciphertextSegmentSize <= firstSegmentOffset + getHeaderLength() + TAG_SIZE_IN_BYTES) { 109 throw new InvalidAlgorithmParameterException("ciphertextSegmentSize too small"); 110 } 111 this.ikm = Arrays.copyOf(ikm, ikm.length); 112 this.hkdfAlg = hkdfAlg; 113 this.keySizeInBytes = keySizeInBytes; 114 this.ciphertextSegmentSize = ciphertextSegmentSize; 115 this.firstSegmentOffset = firstSegmentOffset; 116 this.plaintextSegmentSize = ciphertextSegmentSize - TAG_SIZE_IN_BYTES; 117 } 118 AesGcmHkdfStreaming(AesGcmHkdfStreamingKey key)119 private AesGcmHkdfStreaming(AesGcmHkdfStreamingKey key) 120 throws GeneralSecurityException { 121 this.ikm = key.getInitialKeyMaterial().toByteArray(InsecureSecretKeyAccess.get()); 122 String hkdfAlgString = ""; 123 if (key.getParameters().getHkdfHashType().equals(HashType.SHA1)) { 124 hkdfAlgString = "HmacSha1"; 125 } else if (key.getParameters().getHkdfHashType().equals(HashType.SHA256)) { 126 hkdfAlgString = "HmacSha256"; 127 } else if (key.getParameters().getHkdfHashType().equals(HashType.SHA512)) { 128 hkdfAlgString = "HmacSha512"; 129 } else { 130 throw new GeneralSecurityException( 131 "Unknown HKDF algorithm " + key.getParameters().getHkdfHashType()); 132 } 133 this.hkdfAlg = hkdfAlgString; 134 this.keySizeInBytes = key.getParameters().getDerivedAesGcmKeySizeBytes(); 135 this.ciphertextSegmentSize = key.getParameters().getCiphertextSegmentSizeBytes(); 136 // Current KeyTypeManager always sets this to 0. 137 this.firstSegmentOffset = 0; 138 this.plaintextSegmentSize = this.ciphertextSegmentSize - TAG_SIZE_IN_BYTES; 139 } 140 create(AesGcmHkdfStreamingKey key)141 public static StreamingAead create(AesGcmHkdfStreamingKey key) 142 throws GeneralSecurityException { 143 return new AesGcmHkdfStreaming(key); 144 } 145 146 @Override newStreamSegmentEncrypter(byte[] aad)147 public AesGcmHkdfStreamEncrypter newStreamSegmentEncrypter(byte[] aad) 148 throws GeneralSecurityException { 149 return new AesGcmHkdfStreamEncrypter(aad); 150 } 151 152 @Override newStreamSegmentDecrypter()153 public AesGcmHkdfStreamDecrypter newStreamSegmentDecrypter() throws GeneralSecurityException { 154 return new AesGcmHkdfStreamDecrypter(); 155 } 156 157 @Override getPlaintextSegmentSize()158 public int getPlaintextSegmentSize() { 159 return plaintextSegmentSize; 160 } 161 162 @Override getCiphertextSegmentSize()163 public int getCiphertextSegmentSize() { 164 return ciphertextSegmentSize; 165 } 166 167 @Override getHeaderLength()168 public int getHeaderLength() { 169 return 1 + keySizeInBytes + NONCE_PREFIX_IN_BYTES; 170 } 171 172 @Override getCiphertextOffset()173 public int getCiphertextOffset() { 174 return getHeaderLength() + firstSegmentOffset; 175 } 176 177 @Override getCiphertextOverhead()178 public int getCiphertextOverhead() { 179 return TAG_SIZE_IN_BYTES; 180 } 181 getFirstSegmentOffset()182 public int getFirstSegmentOffset() { 183 return firstSegmentOffset; 184 } 185 186 /** 187 * Returns the expected size of the ciphertext for a given plaintext. The returned value includes 188 * the header and offset. 189 */ expectedCiphertextSize(long plaintextSize)190 public long expectedCiphertextSize(long plaintextSize) { 191 long offset = getCiphertextOffset(); 192 long fullSegments = (plaintextSize + offset) / plaintextSegmentSize; 193 long ciphertextSize = fullSegments * ciphertextSegmentSize; 194 long lastSegmentSize = (plaintextSize + offset) % plaintextSegmentSize; 195 if (lastSegmentSize > 0) { 196 ciphertextSize += lastSegmentSize + TAG_SIZE_IN_BYTES; 197 } 198 return ciphertextSize; 199 } 200 cipherInstance()201 private static Cipher cipherInstance() throws GeneralSecurityException { 202 return EngineFactory.CIPHER.getInstance("AES/GCM/NoPadding"); 203 } 204 randomSalt()205 private byte[] randomSalt() { 206 return Random.randBytes(keySizeInBytes); 207 } 208 paramsForSegment(byte[] prefix, long segmentNr, boolean last)209 private static GCMParameterSpec paramsForSegment(byte[] prefix, long segmentNr, boolean last) 210 throws GeneralSecurityException { 211 ByteBuffer nonce = ByteBuffer.allocate(NONCE_SIZE_IN_BYTES); 212 nonce.order(ByteOrder.BIG_ENDIAN); 213 nonce.put(prefix); 214 SubtleUtil.putAsUnsigedInt(nonce, segmentNr); 215 nonce.put((byte) (last ? 1 : 0)); 216 return new GCMParameterSpec(8 * TAG_SIZE_IN_BYTES, nonce.array()); 217 } 218 randomNonce()219 private static byte[] randomNonce() { 220 return Random.randBytes(NONCE_PREFIX_IN_BYTES); 221 } 222 deriveKeySpec(byte[] salt, byte[] aad)223 private SecretKeySpec deriveKeySpec(byte[] salt, byte[] aad) throws GeneralSecurityException { 224 byte[] key = Hkdf.computeHkdf(hkdfAlg, ikm, salt, aad, keySizeInBytes); 225 return new SecretKeySpec(key, "AES"); 226 } 227 228 /** 229 * An instance of a crypter used to encrypt a plaintext stream. The instances have state: 230 * encryptedSegments counts the number of encrypted segments. This state is used to generate the 231 * IV for each segment. By enforcing that only the method encryptSegment can increment this state, 232 * we can guarantee that the IV does not repeat. 233 */ 234 class AesGcmHkdfStreamEncrypter implements StreamSegmentEncrypter { 235 private final SecretKeySpec keySpec; 236 private final Cipher cipher; 237 private final byte[] noncePrefix; 238 private final ByteBuffer header; 239 private long encryptedSegments = 0; 240 AesGcmHkdfStreamEncrypter(byte[] aad)241 public AesGcmHkdfStreamEncrypter(byte[] aad) throws GeneralSecurityException { 242 cipher = cipherInstance(); 243 encryptedSegments = 0; 244 byte[] salt = randomSalt(); 245 noncePrefix = randomNonce(); 246 header = ByteBuffer.allocate(getHeaderLength()); 247 header.put((byte) getHeaderLength()); 248 header.put(salt); 249 header.put(noncePrefix); 250 header.flip(); 251 keySpec = deriveKeySpec(salt, aad); 252 } 253 254 @Override getHeader()255 public ByteBuffer getHeader() { 256 return header.asReadOnlyBuffer(); 257 } 258 259 /** 260 * Encrypts the next plaintext segment. This uses encryptedSegments as the segment number for 261 * the encryption. 262 */ 263 @Override encryptSegment( ByteBuffer plaintext, boolean isLastSegment, ByteBuffer ciphertext)264 public synchronized void encryptSegment( 265 ByteBuffer plaintext, boolean isLastSegment, ByteBuffer ciphertext) 266 throws GeneralSecurityException { 267 cipher.init( 268 Cipher.ENCRYPT_MODE, 269 keySpec, 270 paramsForSegment(noncePrefix, encryptedSegments, isLastSegment)); 271 encryptedSegments++; 272 cipher.doFinal(plaintext, ciphertext); 273 } 274 275 /** 276 * Encrypt a segment consisting of two parts. This method simplifies the case where one part of 277 * the plaintext is buffered and the other part is passed in by the caller. 278 */ 279 @Override encryptSegment( ByteBuffer part1, ByteBuffer part2, boolean isLastSegment, ByteBuffer ciphertext)280 public synchronized void encryptSegment( 281 ByteBuffer part1, ByteBuffer part2, boolean isLastSegment, ByteBuffer ciphertext) 282 throws GeneralSecurityException { 283 cipher.init( 284 Cipher.ENCRYPT_MODE, 285 keySpec, 286 paramsForSegment(noncePrefix, encryptedSegments, isLastSegment)); 287 encryptedSegments++; 288 // `update(nonEmpty)`, `doFinal(empty)` is known to cause problems on Android 23. 289 // See https://github.com/google/tink/issues/229 290 if (part2.hasRemaining()) { 291 cipher.update(part1, ciphertext); 292 cipher.doFinal(part2, ciphertext); 293 } else { 294 cipher.doFinal(part1, ciphertext); 295 } 296 } 297 } 298 299 /** An instance of a crypter used to decrypt a ciphertext stream. */ 300 class AesGcmHkdfStreamDecrypter implements StreamSegmentDecrypter { 301 private SecretKeySpec keySpec; 302 private Cipher cipher; 303 private byte[] noncePrefix; 304 AesGcmHkdfStreamDecrypter()305 AesGcmHkdfStreamDecrypter() {}; 306 307 @Override init(ByteBuffer header, byte[] aad)308 public synchronized void init(ByteBuffer header, byte[] aad) throws GeneralSecurityException { 309 if (header.remaining() != getHeaderLength()) { 310 throw new InvalidAlgorithmParameterException("Invalid header length"); 311 } 312 byte firstByte = header.get(); 313 if (firstByte != getHeaderLength()) { 314 // We expect the first byte to be the length of the header. 315 // If this is not the case then either the ciphertext is incorrectly 316 // aligned or invalid. 317 throw new GeneralSecurityException("Invalid ciphertext"); 318 } 319 noncePrefix = new byte[NONCE_PREFIX_IN_BYTES]; 320 byte[] salt = new byte[keySizeInBytes]; 321 header.get(salt); 322 header.get(noncePrefix); 323 keySpec = deriveKeySpec(salt, aad); 324 cipher = cipherInstance(); 325 } 326 327 @Override decryptSegment( ByteBuffer ciphertext, int segmentNr, boolean isLastSegment, ByteBuffer plaintext)328 public synchronized void decryptSegment( 329 ByteBuffer ciphertext, int segmentNr, boolean isLastSegment, ByteBuffer plaintext) 330 throws GeneralSecurityException { 331 GCMParameterSpec params = paramsForSegment(noncePrefix, segmentNr, isLastSegment); 332 cipher.init(Cipher.DECRYPT_MODE, keySpec, params); 333 cipher.doFinal(ciphertext, plaintext); 334 } 335 } 336 } 337