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}