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}