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 com.nimbusds.jose.*;
022import com.nimbusds.jose.jca.JWEJCAContext;
023import com.nimbusds.jose.util.Base64URL;
024import com.nimbusds.jose.util.ByteUtils;
025import com.nimbusds.jose.util.Container;
026import com.nimbusds.jose.util.IntegerOverflowException;
027
028import javax.crypto.SecretKey;
029import javax.crypto.spec.SecretKeySpec;
030import java.security.SecureRandom;
031import java.util.*;
032
033
034/**
035 * JWE content encryption / decryption provider.
036 *
037 * @author Vladimir Dzhuvinov
038 * @version 2026-09-16
039 */
040public class ContentCryptoProvider {
041
042
043        /**
044         * The supported encryption methods.
045         */
046        public static final Set<EncryptionMethod> SUPPORTED_ENCRYPTION_METHODS;
047
048
049        /**
050         * The encryption methods compatible with each key size in bits.
051         */
052        public static final Map<Integer,Set<EncryptionMethod>> COMPATIBLE_ENCRYPTION_METHODS;
053
054
055        static {
056                Set<EncryptionMethod> methods = new LinkedHashSet<>();
057                methods.add(EncryptionMethod.A128CBC_HS256);
058                methods.add(EncryptionMethod.A192CBC_HS384);
059                methods.add(EncryptionMethod.A256CBC_HS512);
060                methods.add(EncryptionMethod.A128GCM);
061                methods.add(EncryptionMethod.A192GCM);
062                methods.add(EncryptionMethod.A256GCM);
063                methods.add(EncryptionMethod.A128CBC_HS256_DEPRECATED);
064                methods.add(EncryptionMethod.A256CBC_HS512_DEPRECATED);
065                methods.add(EncryptionMethod.XC20P);
066                SUPPORTED_ENCRYPTION_METHODS = Collections.unmodifiableSet(methods);
067
068                Map<Integer,Set<EncryptionMethod>> encsMap = new HashMap<>();
069                Set<EncryptionMethod> bit128Encs = new HashSet<>();
070                Set<EncryptionMethod> bit192Encs = new HashSet<>();
071                Set<EncryptionMethod> bit256Encs = new HashSet<>();
072                Set<EncryptionMethod> bit384Encs = new HashSet<>();
073                Set<EncryptionMethod> bit512Encs = new HashSet<>();
074                bit128Encs.add(EncryptionMethod.A128GCM);
075                bit192Encs.add(EncryptionMethod.A192GCM);
076                bit256Encs.add(EncryptionMethod.A256GCM);
077                bit256Encs.add(EncryptionMethod.A128CBC_HS256);
078                bit256Encs.add(EncryptionMethod.A128CBC_HS256_DEPRECATED);
079                bit256Encs.add(EncryptionMethod.XC20P);
080                bit384Encs.add(EncryptionMethod.A192CBC_HS384);
081                bit512Encs.add(EncryptionMethod.A256CBC_HS512);
082                bit512Encs.add(EncryptionMethod.A256CBC_HS512_DEPRECATED);
083                encsMap.put(128,Collections.unmodifiableSet(bit128Encs));
084                encsMap.put(192,Collections.unmodifiableSet(bit192Encs));
085                encsMap.put(256,Collections.unmodifiableSet(bit256Encs));
086                encsMap.put(384,Collections.unmodifiableSet(bit384Encs));
087                encsMap.put(512, Collections.unmodifiableSet(bit512Encs));
088                COMPATIBLE_ENCRYPTION_METHODS = Collections.unmodifiableMap(encsMap);
089        }
090
091
092        /**
093         * Generates a Content Encryption Key (CEK) for the specified JOSE
094         * encryption method.
095         *
096         * @param enc       The encryption method. Must not be {@code null}.
097         * @param randomGen The secure random generator to use. Must not be
098         *                  {@code null}.
099         *
100         * @return The generated CEK (with algorithm "AES").
101         *
102         * @throws JOSEException If the encryption method is not supported.
103         */
104        public static SecretKey generateCEK(final EncryptionMethod enc, final SecureRandom randomGen)
105                throws JOSEException {
106
107                if (! SUPPORTED_ENCRYPTION_METHODS.contains(enc)) {
108                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(enc, SUPPORTED_ENCRYPTION_METHODS));
109                }
110
111                final byte[] cekMaterial = new byte[ByteUtils.byteLength(enc.cekBitLength())];
112
113                randomGen.nextBytes(cekMaterial);
114
115                return new SecretKeySpec(cekMaterial, "AES");
116        }
117
118
119        /**
120         * Checks the length of the Content Encryption Key (CEK) according to
121         * the encryption method.
122         *
123         * @param cek The CEK. Must not be {@code null}.
124         * @param enc The encryption method. Must not be {@code null}.
125         *
126         * @throws KeyLengthException If the CEK length doesn't match the
127         *                            encryption method.
128         */
129        private static void checkCEKLength(final SecretKey cek, final EncryptionMethod enc)
130                throws KeyLengthException {
131                
132                final int cekBitLength;
133                try {
134                        cekBitLength = ByteUtils.safeBitLength(cek.getEncoded());
135                } catch (IntegerOverflowException e) {
136                        throw new KeyLengthException("The Content Encryption Key (CEK) is too long: " + e.getMessage());
137                }
138                
139                if (cekBitLength == 0) {
140                        // Suspect HSM that doesn't expose key material
141                        // https://bitbucket.org/connect2id/nimbus-jose-jwt/issues/490/jwe-with-shared-key-support-for-android
142                        return;
143                }
144                
145                if (enc.cekBitLength() != cekBitLength) {
146                        throw new KeyLengthException("The Content Encryption Key (CEK) length for " + enc + " must be " + enc.cekBitLength() + " bits");
147                }
148        }
149
150
151        /**
152         * Encrypts the specified clear text (content).
153         *
154         * @param header       The final JWE header. Must not be {@code null}.
155         * @param clearText    The clear text to encrypt and optionally
156         *                     compress. Must not be {@code null}.
157         * @param cek          The Content Encryption Key (CEK). Must not be
158         *                     {@code null}.
159         * @param encryptedKey The encrypted CEK, {@code null} if not required.
160         * @param jcaProvider  The JWE JCA provider specification. Must not be
161         *                     {@code null}.
162         *
163         * @return The JWE crypto parts.
164         *
165         * @throws JOSEException If encryption failed.
166         */
167        public static JWECryptoParts encrypt(final JWEHeader header,
168                                             final byte[] clearText,
169                                             final SecretKey cek,
170                                             final Base64URL encryptedKey,
171                                             final JWEJCAContext jcaProvider)
172                throws JOSEException {
173
174                return encrypt(header, clearText, null, cek, encryptedKey, jcaProvider);
175        }
176
177
178        /**
179         * Encrypts the specified clear text (content).
180         *
181         * @param header       The final JWE header. Must not be {@code null}.
182         * @param clearText    The clear text to encrypt and optionally
183         *                     compress. Must not be {@code null}.
184         * @param aad          The Additional Authenticated Data (AAD), if
185         *                     {@code null} the JWE header becomes the AAD.
186         * @param cek          The Content Encryption Key (CEK). Must not be
187         *                     {@code null}.
188         * @param encryptedKey The encrypted CEK, {@code null} if not required.
189         * @param jcaProvider  The JWE JCA provider specification. Must not be
190         *                     {@code null}.
191         *
192         * @return The JWE crypto parts.
193         *
194         * @throws JOSEException If encryption failed.
195         */
196        public static JWECryptoParts encrypt(final JWEHeader header,
197                                             final byte[] clearText,
198                                             final byte[] aad,
199                                             final SecretKey cek,
200                                             final Base64URL encryptedKey,
201                                             final JWEJCAContext jcaProvider)
202                throws JOSEException {
203
204                
205                if (aad == null) {
206                        // The AAD is the JWE header
207                        return encrypt(header, clearText, AAD.compute(header), cek, encryptedKey, jcaProvider);
208                }
209
210                checkCEKLength(cek, header.getEncryptionMethod());
211
212                // Apply compression if instructed
213                final byte[] plainText = DeflateHelper.applyCompression(header, clearText);
214
215                // Encrypt the plain text according to the JWE enc
216                final byte[] iv;
217                final AuthenticatedCipherText authCipherText;
218
219                if (    header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) ||
220                        header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) ||
221                        header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512)    ) {
222
223                        iv = AESCBC.generateIV(jcaProvider.getSecureRandom());
224
225                        authCipherText = AESCBC.encryptAuthenticated(
226                                cek, iv, plainText, aad,
227                                jcaProvider.getContentEncryptionProvider(),
228                                jcaProvider.getMACProvider());
229
230                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) ||
231                           header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) ||
232                           header.getEncryptionMethod().equals(EncryptionMethod.A256GCM)    ) {
233
234                        Container<byte[]> ivContainer = new Container<>(AESGCM.generateIV(jcaProvider.getSecureRandom()));
235
236                        authCipherText = AESGCM.encrypt(
237                                cek, ivContainer, plainText, aad,
238                                jcaProvider.getContentEncryptionProvider());
239
240                        iv = ivContainer.get();
241
242                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) ||
243                           header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)    ) {
244
245                        iv = AESCBC.generateIV(jcaProvider.getSecureRandom());
246
247                        authCipherText = AESCBC.encryptWithConcatKDF(
248                                header, cek, encryptedKey, iv, plainText,
249                                jcaProvider.getContentEncryptionProvider(),
250                                jcaProvider.getMACProvider());
251
252                } else if (header.getEncryptionMethod().equals(EncryptionMethod.XC20P)) {
253
254                        Container<byte[]> ivContainer = new Container<>(null);
255
256                        authCipherText = XC20P.encryptAuthenticated(cek, ivContainer, plainText, aad);
257
258                        iv = ivContainer.get();
259
260                } else {
261
262                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(
263                                header.getEncryptionMethod(),
264                                SUPPORTED_ENCRYPTION_METHODS));
265                }
266
267                return new JWECryptoParts(
268                        header,
269                        encryptedKey,
270                        Base64URL.encode(iv),
271                        Base64URL.encode(authCipherText.getCipherText()),
272                        Base64URL.encode(authCipherText.getAuthenticationTag()));
273        }
274
275
276        /**
277         * Decrypts the specified cipher text.
278         *
279         * @param header       The JWE header. Must not be {@code null}.
280         * @param encryptedKey The encrypted key, {@code null} if not
281         *                     specified.
282         * @param iv           The initialisation vector (IV). Must not be
283         *                     {@code null}.
284         * @param cipherText   The cipher text. Must not be {@code null}.
285         * @param authTag      The authentication tag. Must not be
286         *                     {@code null}.
287         * @param cek          The Content Encryption Key (CEK). Must not be
288         *                     {@code null}.
289         * @param jcaProvider  The JWE JCA provider specification. Must not be
290         *                     {@code null}.
291         *
292         * @return The clear text.
293         *
294         * @throws JOSEException If decryption failed.
295         */
296        @Deprecated
297        public static byte[] decrypt(final JWEHeader header,
298                                     final Base64URL encryptedKey,
299                                     final Base64URL iv,
300                                     final Base64URL cipherText,
301                                     final Base64URL authTag,
302                                     final SecretKey cek,
303                                     final JWEJCAContext jcaProvider)
304                throws JOSEException {
305
306                return decrypt(header, null, encryptedKey, iv, cipherText, authTag, cek, jcaProvider, JWEObject.MAX_DECOMPRESSED_PLAIN_TEXT_LENGTH);
307        }
308
309
310        /**
311         * Decrypts the specified cipher text.
312         *
313         * @param header                          The JWE header. Must not be
314         *                                        {@code null}.
315         * @param encryptedKey                    The encrypted key,
316         *                                        {@code null} if not
317         *                                        specified.
318         * @param iv                              The initialisation vector
319         *                                        (IV). Must not be
320         *                                        {@code null}.
321         * @param cipherText                      The cipher text. Must not be
322         *                                        {@code null}.
323         * @param authTag                         The authentication tag. Must
324         *                                        not be {@code null}.
325         * @param cek                             The Content Encryption Key
326         *                                        (CEK). Must not be
327         *                                        {@code null}.
328         * @param jcaProvider                     The JWE JCA provider
329         *                                        specification. Must not be
330         *                                        {@code null}.
331         * @param maxDecompressedPlainTextLength The maximum allowed
332         *                                        decompressed plain text
333         *                                        length, in bytes.
334         *
335         * @return The clear text.
336         *
337         * @throws JOSEException If decryption failed.
338         */
339        public static byte[] decrypt(final JWEHeader header,
340                                     final Base64URL encryptedKey,
341                                     final Base64URL iv,
342                                     final Base64URL cipherText,
343                                     final Base64URL authTag,
344                                     final SecretKey cek,
345                                     final JWEJCAContext jcaProvider,
346                                     final int maxDecompressedPlainTextLength)
347                throws JOSEException {
348
349                return decrypt(header, null, encryptedKey, iv, cipherText, authTag, cek, jcaProvider, maxDecompressedPlainTextLength);
350        }
351
352
353        /**
354         * Decrypts the specified cipher text.
355         *
356         * @param header       The JWE header. Must not be {@code null}.
357         * @param aad          The Additional Authenticated Data (AAD), if
358         *                     {@code null} the JWE header becomes the AAD.
359         * @param encryptedKey The encrypted key, {@code null} if not
360         *                     specified.
361         * @param iv           The initialisation vector (IV). Must not be
362         *                     {@code null}.
363         * @param cipherText   The cipher text. Must not be {@code null}.
364         * @param authTag      The authentication tag. Must not be
365         *                     {@code null}.
366         * @param cek          The Content Encryption Key (CEK). Must not be
367         *                     {@code null}.
368         * @param jcaProvider  The JWE JCA provider specification. Must not be
369         *                     {@code null}.
370         *
371         * @return The clear text.
372         *
373         * @throws JOSEException If decryption failed.
374         */
375        @Deprecated
376        public static byte[] decrypt(final JWEHeader header,
377                                     final byte[] aad,
378                                     final Base64URL encryptedKey,
379                                     final Base64URL iv,
380                                     final Base64URL cipherText,
381                                     final Base64URL authTag,
382                                     final SecretKey cek,
383                                     final JWEJCAContext jcaProvider)
384                throws JOSEException {
385
386                return decrypt(header, aad, encryptedKey, iv, cipherText, authTag, cek, jcaProvider, JWEObject.MAX_DECOMPRESSED_PLAIN_TEXT_LENGTH);
387        }
388
389
390        /**
391         * Decrypts the specified cipher text.
392         *
393         * @param header                          The JWE header. Must not be
394         *                                        {@code null}.
395         * @param aad                             The Additional Authenticated
396         *                                        Data (AAD), if {@code null}
397         *                                        the JWE header becomes the
398         *                                        AAD.
399         * @param encryptedKey                    The encrypted key,
400         *                                        {@code null} if not
401         *                                        specified.
402         * @param iv                              The initialisation vector
403         *                                        (IV). Must not be
404         *                                        {@code null}.
405         * @param cipherText                      The cipher text. Must not be
406         *                                        {@code null}.
407         * @param authTag                         The authentication tag. Must
408         *                                        not be {@code null}.
409         * @param cek                             The Content Encryption Key
410         *                                        (CEK). Must not be
411         *                                        {@code null}.
412         * @param jcaProvider                     The JWE JCA provider
413         *                                        specification. Must not be
414         *                                        {@code null}.
415         * @param maxDecompressedPlainTextLength The maximum allowed
416         *                                        decompressed plain text
417         *                                        length, in bytes.
418         *
419         * @return The clear text.
420         *
421         * @throws JOSEException If decryption failed.
422         */
423        public static byte[] decrypt(final JWEHeader header,
424                                     final byte[] aad,
425                                     final Base64URL encryptedKey,
426                                     final Base64URL iv,
427                                     final Base64URL cipherText,
428                                     final Base64URL authTag,
429                                     final SecretKey cek,
430                                     final JWEJCAContext jcaProvider,
431                                     final int maxDecompressedPlainTextLength)
432                throws JOSEException {
433                
434                if (aad == null) {
435                        // The AAD is the JWE header
436                        return decrypt(header, AAD.compute(header), encryptedKey, iv, cipherText, authTag, cek, jcaProvider, maxDecompressedPlainTextLength);
437                }
438
439                checkCEKLength(cek, header.getEncryptionMethod());
440
441                // Decrypt the cipher text according to the JWE enc
442
443                byte[] plainText;
444
445                if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) ||
446                        header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) ||
447                        header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512)) {
448
449                        plainText = AESCBC.decryptAuthenticated(
450                                cek,
451                                iv.decode(),
452                                cipherText.decode(),
453                                aad,
454                                authTag.decode(),
455                                jcaProvider.getContentEncryptionProvider(),
456                                jcaProvider.getMACProvider());
457
458                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) ||
459                        header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) ||
460                        header.getEncryptionMethod().equals(EncryptionMethod.A256GCM)) {
461
462                        plainText = AESGCM.decrypt(
463                                cek,
464                                iv.decode(),
465                                cipherText.decode(),
466                                aad,
467                                authTag.decode(),
468                                jcaProvider.getContentEncryptionProvider());
469
470                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) ||
471                        header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)) {
472
473                        plainText = AESCBC.decryptWithConcatKDF(
474                                header,
475                                cek,
476                                encryptedKey,
477                                iv,
478                                cipherText,
479                                authTag,
480                                jcaProvider.getContentEncryptionProvider(),
481                                jcaProvider.getMACProvider());
482
483                } else if (header.getEncryptionMethod().equals(EncryptionMethod.XC20P)) {
484
485                        plainText = XC20P.decryptAuthenticated(
486                                        cek,
487                                        iv.decode(),
488                                        cipherText.decode(),
489                                        aad,
490                                        authTag.decode()
491                        );
492
493                } else {
494                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(
495                                header.getEncryptionMethod(),
496                                SUPPORTED_ENCRYPTION_METHODS));
497                }
498
499
500                // Apply decompression if requested
501                return DeflateHelper.applyDecompression(header, plainText, maxDecompressedPlainTextLength);
502        }
503}