001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose.crypto.impl;
019
020
021import java.nio.ByteBuffer;
022import java.security.Provider;
023import java.security.SecureRandom;
024import java.util.Arrays;
025import javax.crypto.Cipher;
026import javax.crypto.SecretKey;
027import javax.crypto.spec.IvParameterSpec;
028import javax.crypto.spec.SecretKeySpec;
029
030import net.jcip.annotations.ThreadSafe;
031
032import com.nimbusds.jose.JOSEException;
033import com.nimbusds.jose.JWEHeader;
034import com.nimbusds.jose.crypto.utils.ConstantTimeUtils;
035import com.nimbusds.jose.util.Base64URL;
036import com.nimbusds.jose.util.ByteUtils;
037import com.nimbusds.jose.util.StandardCharset;
038
039
040/**
041 * AES/CBC/PKCS5Padding and AES/CBC/PKCS5Padding/HMAC-SHA2 encryption and 
042 * decryption methods. This class is thread-safe.
043 *
044 * <p>Also supports the deprecated AES/CBC/HMAC encryption using a custom
045 * concat KDF (JOSE draft suite 08).
046 *
047 * <p>See RFC 7518 (JWA), section 5.2.
048 *
049 * @author Vladimir Dzhuvinov
050 * @author Axel Nennker
051 * @version 2018-01-04
052 */
053@ThreadSafe
054public class AESCBC {
055
056
057        /**
058         * The standard Initialisation Vector (IV) length (128 bits).
059         */
060        public static final int IV_BIT_LENGTH = 128;
061
062
063        /**
064         * Generates a random 128 bit (16 byte) Initialisation Vector(IV) for
065         * use in AES-CBC encryption.
066         *
067         * @param randomGen The secure random generator to use. Must be 
068         *                  correctly initialised and not {@code null}.
069         *
070         * @return The random 128 bit IV, as 16 byte array.
071         */
072        public static byte[] generateIV(final SecureRandom randomGen) {
073                
074                byte[] bytes = new byte[ByteUtils.byteLength(IV_BIT_LENGTH)];
075                randomGen.nextBytes(bytes);
076                return bytes;
077        }
078
079
080        /**
081         * Creates a new AES/CBC/PKCS5Padding cipher.
082         *
083         * @param secretKey     The AES key. Must not be {@code null}.
084         * @param forEncryption If {@code true} creates an encryption cipher, 
085         *                      else creates a decryption cipher.
086         * @param iv            The initialisation vector (IV). Must not be
087         *                      {@code null}.
088         * @param provider      The JCA provider, or {@code null} to use the
089         *                      default one.
090         *
091         * @return The AES/CBC/PKCS5Padding cipher.
092         */
093        private static Cipher createAESCBCCipher(final SecretKey secretKey,
094                                                 final boolean forEncryption,
095                                                 final byte[] iv,
096                                                 final Provider provider)
097                throws JOSEException {
098
099                Cipher cipher;
100
101                try {
102                        cipher = CipherHelper.getInstance("AES/CBC/PKCS5Padding", provider);
103
104                        SecretKeySpec keyspec = new SecretKeySpec(secretKey.getEncoded(), "AES");
105
106                        IvParameterSpec ivSpec = new IvParameterSpec(iv);
107
108                        if (forEncryption) {
109
110                                cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivSpec);
111
112                        } else {
113
114                                cipher.init(Cipher.DECRYPT_MODE, keyspec, ivSpec);
115                        }
116
117                } catch (Exception e) {
118
119                        throw new JOSEException(e.getMessage(), e);
120                }
121
122                return cipher;
123        }
124
125
126        /**
127         * Encrypts the specified plain text using AES/CBC/PKCS5Padding.
128         *
129         * @param secretKey The AES key. Must not be {@code null}.
130         * @param iv        The initialisation vector (IV). Must not be
131         *                  {@code null}.
132         * @param plainText The plain text. Must not be {@code null}.
133         * @param provider  The JCA provider, or {@code null} to use the
134         *                  default one.
135         *
136         * @return The cipher text.
137         *
138         * @throws JOSEException If encryption failed.
139         */
140        public static byte[] encrypt(final SecretKey secretKey, 
141                                     final byte[] iv,
142                                     final byte[] plainText,
143                                     final Provider provider)
144                throws JOSEException {
145
146                Cipher cipher = createAESCBCCipher(secretKey, true, iv, provider);
147
148                try {
149                        return cipher.doFinal(plainText);       
150                
151                } catch (Exception e) {
152
153                        throw new JOSEException(e.getMessage(), e);
154                }
155        }
156
157
158        /**
159         * Encrypts the specified plain text using AES/CBC/PKCS5Padding/
160         * HMAC-SHA2.
161         * 
162         * <p>See RFC 7518 (JWA), section 5.2.2.1
163         *
164         * <p>See draft-mcgrew-aead-aes-cbc-hmac-sha2-01
165         *
166         * @param secretKey   The secret key. Must be 256 or 512 bits long.
167         *                    Must not be {@code null}.
168         * @param iv          The initialisation vector (IV). Must not be
169         *                    {@code null}.
170         * @param plainText   The plain text. Must not be {@code null}.
171         * @param aad         The additional authenticated data. Must not be
172         *                    {@code null}.
173         * @param ceProvider  The JCA provider for the content encryption, or
174         *                    {@code null} to use the default one.
175         * @param macProvider The JCA provider for the MAC computation, or
176         *                    {@code null} to use the default one.
177         *
178         * @return The authenticated cipher text.
179         *
180         * @throws JOSEException If encryption failed.
181         */
182        public static AuthenticatedCipherText encryptAuthenticated(final SecretKey secretKey,
183                                                                   final byte[] iv,
184                                                                   final byte[] plainText,
185                                                                   final byte[] aad,
186                                                                   final Provider ceProvider,
187                                                                   final Provider macProvider)
188                throws JOSEException {
189
190                // Extract MAC + AES/CBC keys from input secret key
191                CompositeKey compositeKey = new CompositeKey(secretKey);
192
193                // Encrypt plain text
194                byte[] cipherText = encrypt(compositeKey.getAESKey(), iv, plainText, ceProvider);
195
196                // AAD length to 8 byte array
197                byte[] al = AAD.computeLength(aad);
198
199                // Do MAC
200                int hmacInputLength = aad.length + iv.length + cipherText.length + al.length;
201                byte[] hmacInput = ByteBuffer.allocate(hmacInputLength).put(aad).put(iv).put(cipherText).put(al).array();
202                byte[] hmac = HMAC.compute(compositeKey.getMACKey(), hmacInput, macProvider);
203                byte[] authTag = Arrays.copyOf(hmac, compositeKey.getTruncatedMACByteLength());
204
205                return new AuthenticatedCipherText(cipherText, authTag);
206        }
207
208
209        /**
210         * Encrypts the specified plain text using the deprecated concat KDF
211         * from JOSE draft suite 09.
212         *
213         * @param header       The JWE header. Must not be {@code null}.
214         * @param secretKey    The secret key. Must be 256 or 512 bits long.
215         *                     Must not be {@code null}.
216         * @param encryptedKey The encrypted key. Must not be {@code null}.
217         * @param iv           The initialisation vector (IV). Must not be
218         *                     {@code null}.
219         * @param plainText    The plain text. Must not be {@code null}.
220         * @param ceProvider   The JCA provider for the content encryption, or
221         *                     {@code null} to use the default one.
222         * @param macProvider  The JCA provider for the MAC computation, or
223         *                     {@code null} to use the default one.
224         *
225         * @return The authenticated cipher text.
226         *
227         * @throws JOSEException If encryption failed.
228         */
229        public static AuthenticatedCipherText encryptWithConcatKDF(final JWEHeader header,
230                                                                   final SecretKey secretKey,
231                                                                   final Base64URL encryptedKey,
232                                                                   final byte[] iv,
233                                                                   final byte[] plainText,
234                                                                   final Provider ceProvider,
235                                                                   final Provider macProvider)
236                throws JOSEException {
237
238                byte[] epu = null;
239
240                if (header.getCustomParam("epu") instanceof String) {
241
242                        epu = new Base64URL((String)header.getCustomParam("epu")).decode();
243                }
244
245                byte[] epv = null;
246
247                if (header.getCustomParam("epv") instanceof String) {
248
249                        epv = new Base64URL((String)header.getCustomParam("epv")).decode();
250                }
251
252                // Generate alternative CEK using concat-KDF
253                SecretKey altCEK = LegacyConcatKDF.generateCEK(secretKey, header.getEncryptionMethod(), epu, epv);
254
255                byte[] cipherText = AESCBC.encrypt(altCEK, iv, plainText, ceProvider);
256
257                // Generate content integrity key for HMAC
258                SecretKey cik = LegacyConcatKDF.generateCIK(secretKey, header.getEncryptionMethod(), epu, epv);
259
260                String macInput = header.toBase64URL().toString() + "." +
261                        encryptedKey.toString() + "." +
262                        Base64URL.encode(iv).toString() + "." +
263                        Base64URL.encode(cipherText);
264
265                byte[] mac = HMAC.compute(cik, macInput.getBytes(StandardCharset.UTF_8), macProvider);
266
267                return new AuthenticatedCipherText(cipherText, mac);
268        }
269
270
271        /**
272         * Decrypts the specified cipher text using AES/CBC/PKCS5Padding.
273         *
274         * @param secretKey  The AES key. Must not be {@code null}.
275         * @param iv         The initialisation vector (IV). Must not be
276         *                   {@code null}.
277         * @param cipherText The cipher text. Must not be {@code null}.
278         * @param provider   The JCA provider, or {@code null} to use the
279         *                   default one.
280         *
281         * @return The decrypted plain text.
282         *
283         * @throws JOSEException If decryption failed.
284         */
285        public static byte[] decrypt(final SecretKey secretKey, 
286                                     final byte[] iv,
287                                     final byte[] cipherText,
288                                     final Provider provider)
289                throws JOSEException {
290
291                Cipher cipher = createAESCBCCipher(secretKey, false, iv, provider);
292
293                try {
294                        return cipher.doFinal(cipherText);
295
296                } catch (Exception e) {
297
298                        throw new JOSEException(e.getMessage(), e);
299                }
300        }
301
302
303        /**
304         * Decrypts the specified cipher text using AES/CBC/PKCS5Padding/
305         * HMAC-SHA2.
306         * 
307         * <p>See RFC 7518 (JWA), section 5.2.2.2
308         *
309         * <p>See draft-mcgrew-aead-aes-cbc-hmac-sha2-01
310         *
311         * @param secretKey   The secret key. Must be 256 or 512 bits long.
312         *                    Must not be {@code null}.
313         * @param iv          The initialisation vector (IV). Must not be
314         *                    {@code null}.
315         * @param cipherText  The cipher text. Must not be {@code null}.
316         * @param aad         The additional authenticated data. Must not be
317         *                    {@code null}.
318         * @param authTag     The authentication tag. Must not be {@code null}.
319         * @param ceProvider  The JCA provider for the content encryption, or
320         *                    {@code null} to use the default one.
321         * @param macProvider The JCA provider for the MAC computation, or
322         *                    {@code null} to use the default one.
323         *
324         * @return The decrypted plain text.
325         *
326         * @throws JOSEException If decryption failed.
327         */
328        public static byte[] decryptAuthenticated(final SecretKey secretKey,
329                                                  final byte[] iv,
330                                                  final byte[] cipherText,
331                                                  final byte[] aad,
332                                                  final byte[] authTag,
333                                                  final Provider ceProvider,
334                                                  final Provider macProvider)
335                throws JOSEException {
336
337
338                // Extract MAC + AES/CBC keys from input secret key
339                CompositeKey compositeKey = new CompositeKey(secretKey);
340
341                // AAD length to 8 byte array
342                byte[] al = AAD.computeLength(aad);
343
344                // Check MAC
345                int hmacInputLength = aad.length + iv.length + cipherText.length + al.length;
346                byte[] hmacInput = ByteBuffer.allocate(hmacInputLength).
347                        put(aad).
348                        put(iv).
349                        put(cipherText).
350                        put(al).
351                        array();
352                byte[] hmac = HMAC.compute(compositeKey.getMACKey(), hmacInput, macProvider);
353
354                byte[] expectedAuthTag = Arrays.copyOf(hmac, compositeKey.getTruncatedMACByteLength());
355
356                if (! ConstantTimeUtils.areEqual(expectedAuthTag, authTag)) {
357                        throw new JOSEException("MAC check failed");
358                }
359
360                return decrypt(compositeKey.getAESKey(), iv, cipherText, ceProvider);
361        }
362
363
364        /**
365         * Decrypts the specified cipher text using the deprecated concat KDF
366         * from JOSE draft suite 09.
367         *
368         * @param header       The JWE header. Must not be {@code null}.
369         * @param secretKey    The secret key. Must be 256 or 512 bits long.
370         *                     Must not be {@code null}.
371         * @param encryptedKey The encrypted key. Must not be {@code null}.
372         * @param iv           The initialisation vector (IV). Must not be
373         *                     {@code null}.
374         * @param cipherText   The cipher text. Must not be {@code null}.
375         * @param authTag      The authentication tag. Must not be {@code null}.
376         * @param ceProvider   The JCA provider for the content encryption, or
377         *                    {@code null} to use the default one.
378         * @param macProvider The JCA provider for the MAC computation, or
379         *                    {@code null} to use the default one.
380         *
381         * @return The decrypted plain text.
382         *
383         * @throws JOSEException If decryption failed.
384         */
385        public static byte[] decryptWithConcatKDF(final JWEHeader header,
386                                                  final SecretKey secretKey,
387                                                  final Base64URL encryptedKey,
388                                                  final Base64URL iv,
389                                                  final Base64URL cipherText,
390                                                  final Base64URL authTag,
391                                                  final Provider ceProvider,
392                                                  final Provider macProvider)
393                throws JOSEException {
394
395                byte[] epu = null;
396
397                if (header.getCustomParam("epu") instanceof String) {
398
399                        epu = new Base64URL((String)header.getCustomParam("epu")).decode();
400                }
401
402                byte[] epv = null;
403
404                if (header.getCustomParam("epv") instanceof String) {
405
406                        epv = new Base64URL((String)header.getCustomParam("epv")).decode();
407                }
408                
409                SecretKey cik = LegacyConcatKDF.generateCIK(secretKey, header.getEncryptionMethod(), epu, epv);
410                
411                String macInput = header.toBase64URL().toString() + "." +
412                        encryptedKey.toString() + "." +
413                        iv.toString() + "." +
414                        cipherText.toString();
415                
416                byte[] mac = HMAC.compute(cik, macInput.getBytes(StandardCharset.UTF_8), macProvider);
417                
418                if (! ConstantTimeUtils.areEqual(authTag.decode(), mac)) {
419                        throw new JOSEException("MAC check failed");
420                }
421
422                SecretKey cekAlt = LegacyConcatKDF.generateCEK(secretKey, header.getEncryptionMethod(), epu, epv);
423
424                return AESCBC.decrypt(cekAlt, iv.decode(), cipherText.decode(), ceProvider);
425        }
426
427
428        /**
429         * Prevents public instantiation.
430         */
431        private AESCBC() { }
432}