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 static com.google.crypto.tink.internal.Util.isPrefix; 20 21 import com.google.crypto.tink.AccessesPartialKey; 22 import com.google.crypto.tink.Aead; 23 import com.google.crypto.tink.InsecureSecretKeyAccess; 24 import com.google.crypto.tink.aead.AesGcmKey; 25 import com.google.crypto.tink.aead.internal.AesGcmJceUtil; 26 import com.google.crypto.tink.config.internal.TinkFipsUtil; 27 import com.google.crypto.tink.util.Bytes; 28 import com.google.errorprone.annotations.Immutable; 29 import java.security.GeneralSecurityException; 30 import java.security.spec.AlgorithmParameterSpec; 31 import java.util.Arrays; 32 import javax.crypto.Cipher; 33 import javax.crypto.SecretKey; 34 35 /** 36 * This primitive implements AesGcm using JCE. 37 * 38 * @since 1.0.0 39 */ 40 @Immutable 41 public final class AesGcmJce implements Aead { 42 public static final TinkFipsUtil.AlgorithmFipsCompatibility FIPS = 43 TinkFipsUtil.AlgorithmFipsCompatibility.ALGORITHM_REQUIRES_BORINGCRYPTO; 44 45 private static final int IV_SIZE_IN_BYTES = AesGcmJceUtil.IV_SIZE_IN_BYTES; 46 private static final int TAG_SIZE_IN_BYTES = AesGcmJceUtil.TAG_SIZE_IN_BYTES; 47 48 @SuppressWarnings("Immutable") 49 private final SecretKey keySpec; 50 51 @SuppressWarnings("Immutable") 52 private final byte[] outputPrefix; 53 AesGcmJce(final byte[] key, Bytes outputPrefix)54 private AesGcmJce(final byte[] key, Bytes outputPrefix) throws GeneralSecurityException { 55 if (!FIPS.isCompatible()) { 56 throw new GeneralSecurityException( 57 "Can not use AES-GCM in FIPS-mode, as BoringCrypto module is not available."); 58 } 59 this.keySpec = AesGcmJceUtil.getSecretKey(key); 60 this.outputPrefix = outputPrefix.toByteArray(); 61 } 62 AesGcmJce(final byte[] key)63 public AesGcmJce(final byte[] key) throws GeneralSecurityException { 64 this(key, Bytes.copyFrom(new byte[] {})); 65 } 66 67 @AccessesPartialKey create(AesGcmKey key)68 public static Aead create(AesGcmKey key) throws GeneralSecurityException { 69 if (key.getParameters().getIvSizeBytes() != IV_SIZE_IN_BYTES) { 70 throw new GeneralSecurityException( 71 "Expected IV Size 12, got " + key.getParameters().getIvSizeBytes()); 72 } 73 if (key.getParameters().getTagSizeBytes() != TAG_SIZE_IN_BYTES) { 74 throw new GeneralSecurityException( 75 "Expected tag Size 16, got " + key.getParameters().getTagSizeBytes()); 76 } 77 78 return new AesGcmJce( 79 key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get()), key.getOutputPrefix()); 80 } 81 82 /** 83 * On Android KitKat (API level 19) this method does not support non null or non empty {@code 84 * associatedData}. It might not work at all in older versions. 85 */ 86 @Override encrypt(final byte[] plaintext, final byte[] associatedData)87 public byte[] encrypt(final byte[] plaintext, final byte[] associatedData) 88 throws GeneralSecurityException { 89 if (plaintext == null) { 90 throw new NullPointerException("plaintext is null"); 91 } 92 byte[] nonce = Random.randBytes(IV_SIZE_IN_BYTES); 93 AlgorithmParameterSpec params = AesGcmJceUtil.getParams(nonce); 94 Cipher cipher = AesGcmJceUtil.getThreadLocalCipher(); 95 cipher.init(Cipher.ENCRYPT_MODE, keySpec, params); 96 if (associatedData != null && associatedData.length != 0) { 97 cipher.updateAAD(associatedData); 98 } 99 int outputSize = cipher.getOutputSize(plaintext.length); 100 if (outputSize > Integer.MAX_VALUE - outputPrefix.length - IV_SIZE_IN_BYTES) { 101 throw new GeneralSecurityException("plaintext too long"); 102 } 103 int len = outputPrefix.length + IV_SIZE_IN_BYTES + outputSize; 104 byte[] output = Arrays.copyOf(outputPrefix, len); 105 System.arraycopy( 106 /* src= */ nonce, 107 /* srcPos= */ 0, 108 /* dest= */ output, 109 /* destPos= */ outputPrefix.length, 110 /* length= */ IV_SIZE_IN_BYTES); 111 int written = 112 cipher.doFinal( 113 plaintext, 0, plaintext.length, output, outputPrefix.length + IV_SIZE_IN_BYTES); 114 if (written != outputSize) { 115 throw new GeneralSecurityException("not enough data written"); 116 } 117 return output; 118 } 119 120 @Override decrypt(final byte[] ciphertext, final byte[] associatedData)121 public byte[] decrypt(final byte[] ciphertext, final byte[] associatedData) 122 throws GeneralSecurityException { 123 if (ciphertext == null) { 124 throw new NullPointerException("ciphertext is null"); 125 } 126 if (ciphertext.length < outputPrefix.length + IV_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES) { 127 throw new GeneralSecurityException("ciphertext too short"); 128 } 129 if (!isPrefix(outputPrefix, ciphertext)) { 130 throw new GeneralSecurityException("Decryption failed (OutputPrefix mismatch)."); 131 } 132 // IV is at position outputPrefix.length in ciphertext. 133 AlgorithmParameterSpec params = 134 AesGcmJceUtil.getParams(ciphertext, outputPrefix.length, IV_SIZE_IN_BYTES); 135 Cipher cipher = AesGcmJceUtil.getThreadLocalCipher(); 136 cipher.init(Cipher.DECRYPT_MODE, keySpec, params); 137 if (associatedData != null && associatedData.length != 0) { 138 cipher.updateAAD(associatedData); 139 } 140 int offset = outputPrefix.length + IV_SIZE_IN_BYTES; 141 int len = ciphertext.length - outputPrefix.length - IV_SIZE_IN_BYTES; 142 return cipher.doFinal(ciphertext, offset, len); 143 } 144 } 145