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 2022-01-24 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, {@code null} to use the 089 * default. 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, {@code null} to use the default. 134 * 135 * @return The cipher text. 136 * 137 * @throws JOSEException If encryption failed. 138 */ 139 public static byte[] encrypt(final SecretKey secretKey, 140 final byte[] iv, 141 final byte[] plainText, 142 final Provider provider) 143 throws JOSEException { 144 145 Cipher cipher = createAESCBCCipher(secretKey, true, iv, provider); 146 147 try { 148 return cipher.doFinal(plainText); 149 150 } catch (Exception e) { 151 152 throw new JOSEException(e.getMessage(), e); 153 } 154 } 155 156 157 /** 158 * Encrypts the specified plain text using AES/CBC/PKCS5Padding/ 159 * HMAC-SHA2. 160 * 161 * <p>See RFC 7518 (JWA), section 5.2.2.1 162 * 163 * <p>See draft-mcgrew-aead-aes-cbc-hmac-sha2-01 164 * 165 * @param secretKey The secret key. Must be 256 or 512 bits long. 166 * Must not be {@code null}. 167 * @param iv The initialisation vector (IV). Must not be 168 * {@code null}. 169 * @param plainText The plain text. Must not be {@code null}. 170 * @param aad The additional authenticated data. Must not be 171 * {@code null}. 172 * @param ceProvider The JCA provider for the content encryption, or 173 * {@code null} to use the default one. 174 * @param macProvider The JCA provider for the MAC computation, or 175 * {@code null} to use the default one. 176 * 177 * @return The authenticated cipher text. 178 * 179 * @throws JOSEException If encryption failed. 180 */ 181 public static AuthenticatedCipherText encryptAuthenticated(final SecretKey secretKey, 182 final byte[] iv, 183 final byte[] plainText, 184 final byte[] aad, 185 final Provider ceProvider, 186 final Provider macProvider) 187 throws JOSEException { 188 189 // Extract MAC + AES/CBC keys from input secret key 190 CompositeKey compositeKey = new CompositeKey(secretKey); 191 192 // Encrypt plain text 193 byte[] cipherText = encrypt(compositeKey.getAESKey(), iv, plainText, ceProvider); 194 195 // AAD length to 8 byte array 196 byte[] al = AAD.computeLength(aad); 197 198 // Do MAC 199 int hmacInputLength = aad.length + iv.length + cipherText.length + al.length; 200 byte[] hmacInput = ByteBuffer.allocate(hmacInputLength).put(aad).put(iv).put(cipherText).put(al).array(); 201 byte[] hmac = HMAC.compute(compositeKey.getMACKey(), hmacInput, macProvider); 202 byte[] authTag = Arrays.copyOf(hmac, compositeKey.getTruncatedMACByteLength()); 203 204 return new AuthenticatedCipherText(cipherText, authTag); 205 } 206 207 208 /** 209 * Encrypts the specified plain text using the deprecated concat KDF 210 * from JOSE draft suite 09. 211 * 212 * @param header The JWE header. Must not be {@code null}. 213 * @param secretKey The secret key. Must be 256 or 512 bits long. 214 * Must not be {@code null}. 215 * @param encryptedKey The encrypted key. Must not be {@code null}. 216 * @param iv The initialisation vector (IV). Must not be 217 * {@code null}. 218 * @param plainText The plain text. Must not be {@code null}. 219 * @param ceProvider The JCA provider for the content encryption, or 220 * {@code null} to use the default one. 221 * @param macProvider The JCA provider for the MAC computation, or 222 * {@code null} to use the default one. 223 * 224 * @return The authenticated cipher text. 225 * 226 * @throws JOSEException If encryption failed. 227 */ 228 public static AuthenticatedCipherText encryptWithConcatKDF(final JWEHeader header, 229 final SecretKey secretKey, 230 final Base64URL encryptedKey, 231 final byte[] iv, 232 final byte[] plainText, 233 final Provider ceProvider, 234 final Provider macProvider) 235 throws JOSEException { 236 237 byte[] epu = null; 238 239 if (header.getCustomParam("epu") instanceof String) { 240 241 epu = new Base64URL((String)header.getCustomParam("epu")).decode(); 242 } 243 244 byte[] epv = null; 245 246 if (header.getCustomParam("epv") instanceof String) { 247 248 epv = new Base64URL((String)header.getCustomParam("epv")).decode(); 249 } 250 251 // Generate alternative CEK using concat-KDF 252 SecretKey altCEK = LegacyConcatKDF.generateCEK(secretKey, header.getEncryptionMethod(), epu, epv); 253 254 byte[] cipherText = AESCBC.encrypt(altCEK, iv, plainText, ceProvider); 255 256 // Generate content integrity key for HMAC 257 SecretKey cik = LegacyConcatKDF.generateCIK(secretKey, header.getEncryptionMethod(), epu, epv); 258 259 String macInput = header.toBase64URL() + "." + 260 encryptedKey + "." + 261 Base64URL.encode(iv) + "." + 262 Base64URL.encode(cipherText); 263 264 byte[] mac = HMAC.compute(cik, macInput.getBytes(StandardCharset.UTF_8), macProvider); 265 266 return new AuthenticatedCipherText(cipherText, mac); 267 } 268 269 270 /** 271 * Decrypts the specified cipher text using AES/CBC/PKCS5Padding. 272 * 273 * @param secretKey The AES key. Must not be {@code null}. 274 * @param iv The initialisation vector (IV). Must not be 275 * {@code null}. 276 * @param cipherText The cipher text. Must not be {@code null}. 277 * @param provider The JCA provider, {@code null} to use the default. 278 * 279 * @return The decrypted plain text. 280 * 281 * @throws JOSEException If decryption failed. 282 */ 283 public static byte[] decrypt(final SecretKey secretKey, 284 final byte[] iv, 285 final byte[] cipherText, 286 final Provider provider) 287 throws JOSEException { 288 289 Cipher cipher = createAESCBCCipher(secretKey, false, iv, provider); 290 291 try { 292 return cipher.doFinal(cipherText); 293 294 } catch (Exception e) { 295 296 throw new JOSEException(e.getMessage(), e); 297 } 298 } 299 300 301 /** 302 * Decrypts the specified cipher text using AES/CBC/PKCS5Padding/ 303 * HMAC-SHA2. 304 * 305 * <p>See RFC 7518 (JWA), section 5.2.2.2 306 * 307 * <p>See draft-mcgrew-aead-aes-cbc-hmac-sha2-01 308 * 309 * @param secretKey The secret key. Must be 256 or 512 bits long. 310 * Must not be {@code null}. 311 * @param iv The initialisation vector (IV). Must not be 312 * {@code null}. 313 * @param cipherText The cipher text. Must not be {@code null}. 314 * @param aad The additional authenticated data. Must not be 315 * {@code null}. 316 * @param authTag The authentication tag. Must not be {@code null}. 317 * @param ceProvider The JCA provider for the content encryption, or 318 * {@code null} to use the default one. 319 * @param macProvider The JCA provider for the MAC computation, or 320 * {@code null} to use the default one. 321 * 322 * @return The decrypted plain text. 323 * 324 * @throws JOSEException If decryption failed. 325 */ 326 public static byte[] decryptAuthenticated(final SecretKey secretKey, 327 final byte[] iv, 328 final byte[] cipherText, 329 final byte[] aad, 330 final byte[] authTag, 331 final Provider ceProvider, 332 final Provider macProvider) 333 throws JOSEException { 334 335 336 // Extract MAC + AES/CBC keys from input secret key 337 CompositeKey compositeKey = new CompositeKey(secretKey); 338 339 // AAD length to 8 byte array 340 byte[] al = AAD.computeLength(aad); 341 342 // Check MAC 343 int hmacInputLength = aad.length + iv.length + cipherText.length + al.length; 344 byte[] hmacInput = ByteBuffer.allocate(hmacInputLength). 345 put(aad). 346 put(iv). 347 put(cipherText). 348 put(al). 349 array(); 350 byte[] hmac = HMAC.compute(compositeKey.getMACKey(), hmacInput, macProvider); 351 352 byte[] expectedAuthTag = Arrays.copyOf(hmac, compositeKey.getTruncatedMACByteLength()); 353 354 if (! ConstantTimeUtils.areEqual(expectedAuthTag, authTag)) { 355 throw new JOSEException("MAC check failed"); 356 } 357 358 return decrypt(compositeKey.getAESKey(), iv, cipherText, ceProvider); 359 } 360 361 362 /** 363 * Decrypts the specified cipher text using the deprecated concat KDF 364 * from JOSE draft suite 09. 365 * 366 * @param header The JWE header. Must not be {@code null}. 367 * @param secretKey The secret key. Must be 256 or 512 bits long. 368 * Must not be {@code null}. 369 * @param encryptedKey The encrypted key. Must not be {@code null}. 370 * @param iv The initialisation vector (IV). Must not be 371 * {@code null}. 372 * @param cipherText The cipher text. Must not be {@code null}. 373 * @param authTag The authentication tag. Must not be {@code null}. 374 * @param ceProvider The JCA provider for the content encryption, or 375 * {@code null} to use the default one. 376 * @param macProvider The JCA provider for the MAC computation, or 377 * {@code null} to use the default one. 378 * 379 * @return The decrypted plain text. 380 * 381 * @throws JOSEException If decryption failed. 382 */ 383 public static byte[] decryptWithConcatKDF(final JWEHeader header, 384 final SecretKey secretKey, 385 final Base64URL encryptedKey, 386 final Base64URL iv, 387 final Base64URL cipherText, 388 final Base64URL authTag, 389 final Provider ceProvider, 390 final Provider macProvider) 391 throws JOSEException { 392 393 byte[] epu = null; 394 395 if (header.getCustomParam("epu") instanceof String) { 396 397 epu = new Base64URL((String)header.getCustomParam("epu")).decode(); 398 } 399 400 byte[] epv = null; 401 402 if (header.getCustomParam("epv") instanceof String) { 403 404 epv = new Base64URL((String)header.getCustomParam("epv")).decode(); 405 } 406 407 SecretKey cik = LegacyConcatKDF.generateCIK(secretKey, header.getEncryptionMethod(), epu, epv); 408 409 String macInput = header.toBase64URL().toString() + "." + 410 encryptedKey.toString() + "." + 411 iv.toString() + "." + 412 cipherText.toString(); 413 414 byte[] mac = HMAC.compute(cik, macInput.getBytes(StandardCharset.UTF_8), macProvider); 415 416 if (! ConstantTimeUtils.areEqual(authTag.decode(), mac)) { 417 throw new JOSEException("MAC check failed"); 418 } 419 420 SecretKey cekAlt = LegacyConcatKDF.generateCEK(secretKey, header.getEncryptionMethod(), epu, epv); 421 422 return AESCBC.decrypt(cekAlt, iv.decode(), cipherText.decode(), ceProvider); 423 } 424 425 426 /** 427 * Prevents public instantiation. 428 */ 429 private AESCBC() { } 430}