• 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 com.google.crypto.tink.AccessesPartialKey;
20 import com.google.crypto.tink.InsecureSecretKeyAccess;
21 import com.google.crypto.tink.config.internal.TinkFipsUtil;
22 import com.google.crypto.tink.mac.internal.AesUtil;
23 import com.google.crypto.tink.prf.AesCmacPrfKey;
24 import com.google.crypto.tink.prf.Prf;
25 import com.google.errorprone.annotations.Immutable;
26 import java.security.GeneralSecurityException;
27 import java.security.InvalidAlgorithmParameterException;
28 import java.util.Arrays;
29 import javax.crypto.Cipher;
30 import javax.crypto.SecretKey;
31 import javax.crypto.spec.SecretKeySpec;
32 
33 /**
34  * An implementation of CMAC following <a href="https://tools.ietf.org/html/rfc4493">RFC 4493</a>.
35  */
36 @Immutable
37 @AccessesPartialKey
38 public final class PrfAesCmac implements Prf {
39   public static final TinkFipsUtil.AlgorithmFipsCompatibility FIPS =
40       TinkFipsUtil.AlgorithmFipsCompatibility.ALGORITHM_NOT_FIPS;
41 
42   @SuppressWarnings("Immutable")
43   private final SecretKey keySpec;
44 
45   @SuppressWarnings("Immutable")
46   private byte[] subKey1;
47 
48   @SuppressWarnings("Immutable")
49   private byte[] subKey2;
50 
51   private static final ThreadLocal<Cipher> localAesCipher =
52       new ThreadLocal<Cipher>() {
53         @Override
54         protected Cipher initialValue() {
55           try {
56             return EngineFactory.CIPHER.getInstance("AES/ECB/NoPadding");
57           } catch (GeneralSecurityException ex) {
58             throw new IllegalStateException(ex);
59           }
60         }
61       };
62 
instance()63   private static Cipher instance() throws GeneralSecurityException {
64     if (!FIPS.isCompatible()) {
65       throw new GeneralSecurityException("Can not use AES-CMAC in FIPS-mode.");
66     }
67     return localAesCipher.get();
68   }
69 
PrfAesCmac(final byte[] key)70   public PrfAesCmac(final byte[] key) throws GeneralSecurityException {
71     Validators.validateAesKeySize(key.length);
72 
73     keySpec = new SecretKeySpec(key, "AES");
74     generateSubKeys();
75   }
76 
create(AesCmacPrfKey key)77   public static Prf create(AesCmacPrfKey key) throws GeneralSecurityException {
78     return new PrfAesCmac(key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get()));
79   }
80 
81   // Only visible for testing.
calcN(int dataLength)82   static int calcN(int dataLength) {
83     if (dataLength == 0) {
84       return 1;
85     }
86     return (dataLength - 1) / AesUtil.BLOCK_SIZE + 1;
87   }
88 
xorBlock(final byte[] x, final byte[] y, int offsetY, byte[] output)89   private static void xorBlock(final byte[] x, final byte[] y, int offsetY, byte[] output) {
90     for (int i = 0; i < AesUtil.BLOCK_SIZE; i++) {
91       output[i] = (byte) (x[i] ^ y[i + offsetY]);
92     }
93   }
94 
95   // https://tools.ietf.org/html/rfc4493#section-2.4
96   @Override
compute(final byte[] data, int outputLength)97   public byte[] compute(final byte[] data, int outputLength) throws GeneralSecurityException {
98     if (outputLength > AesUtil.BLOCK_SIZE) {
99       throw new InvalidAlgorithmParameterException(
100           "outputLength too large, max is " + AesUtil.BLOCK_SIZE + " bytes");
101     }
102     Cipher aes = instance();
103     aes.init(Cipher.ENCRYPT_MODE, keySpec);
104 
105     // n is the number of blocks (including partial blocks) into which the data
106     // is divided. Empty data is divided into 1 empty block.
107     // Step 2: n = ceil(length / blocksize)
108     // TODO(b/68969256): Adding a test that computes a CMAC of length 2**31-1.
109     int n = calcN(data.length);
110 
111     // Step 3
112     boolean flag = (n * AesUtil.BLOCK_SIZE == data.length);
113 
114     // Step 4
115     byte[] mLast;
116     if (flag) {
117       mLast = Bytes.xor(data, (n - 1) * AesUtil.BLOCK_SIZE, subKey1, 0, AesUtil.BLOCK_SIZE);
118     } else {
119       mLast =
120           Bytes.xor(
121               AesUtil.cmacPad(Arrays.copyOfRange(data, (n - 1) * AesUtil.BLOCK_SIZE, data.length)),
122               subKey2);
123     }
124 
125     // Step 5
126     byte[] x = new byte[AesUtil.BLOCK_SIZE];
127 
128     // Step 6
129     byte[] y = new byte[AesUtil.BLOCK_SIZE];
130     for (int i = 0; i < n - 1; i++) {
131       xorBlock(x, data, i * AesUtil.BLOCK_SIZE, /* output= */ y);
132       int written = aes.doFinal(y, 0, AesUtil.BLOCK_SIZE, /* output= */ x);
133       if (written != AesUtil.BLOCK_SIZE) {
134         throw new IllegalStateException("Cipher didn't write full block");
135       }
136     }
137     xorBlock(x, mLast, 0, /* output= */ y);
138 
139     // Step 7
140     int written = aes.doFinal(y, 0, AesUtil.BLOCK_SIZE, /* output= */ x);
141     if (written != AesUtil.BLOCK_SIZE) {
142       throw new IllegalStateException("Cipher didn't write full block");
143     }
144     if (x.length == outputLength) {
145       return x;
146     }
147     return Arrays.copyOf(x, outputLength);
148   }
149 
150   // https://tools.ietf.org/html/rfc4493#section-2.3
generateSubKeys()151   private void generateSubKeys() throws GeneralSecurityException {
152     Cipher aes = instance();
153     aes.init(Cipher.ENCRYPT_MODE, keySpec);
154     byte[] zeroes = new byte[AesUtil.BLOCK_SIZE];
155     byte[] l = aes.doFinal(zeroes);
156     subKey1 = AesUtil.dbl(l);
157     subKey2 = AesUtil.dbl(subKey1);
158   }
159 }
160