• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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