001package com.nimbusds.jose.crypto; 002 003import java.security.SecureRandom; 004 005import javax.crypto.SecretKey; 006import javax.crypto.spec.SecretKeySpec; 007 008import net.jcip.annotations.ThreadSafe; 009 010import com.nimbusds.jose.EncryptionMethod; 011import com.nimbusds.jose.JOSEException; 012import com.nimbusds.jose.JWEAlgorithm; 013import com.nimbusds.jose.JWECryptoParts; 014import com.nimbusds.jose.JWEEncrypter; 015import com.nimbusds.jose.JWEHeader; 016import com.nimbusds.jose.util.Base64URL; 017import com.nimbusds.jose.util.StringUtils; 018 019 020/** 021 * AES encrypter of {@link com.nimbusds.jose.JWEObject JWE objects}. This class 022 * is thread-safe. 023 * 024 * <p>Supports the following JWE algorithms: 025 * 026 * <ul> 027 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A128KW} 028 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A192KW} 029 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A256KW} 030 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A128GCMKW} 031 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A192GCMKW} 032 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A256GCMKW} 033 * </ul> 034 * 035 * <p>Supports the following encryption methods: 036 * 037 * <ul> 038 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256} 039 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384} 040 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512} 041 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM} 042 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM} 043 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM} 044 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED} 045 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED} 046 * </ul> 047 * 048 * @author Melisa Halsband 049 * @version $version$ (2014-08-20) 050 */ 051@ThreadSafe 052public class AESEncrypter extends AESCryptoProvider implements JWEEncrypter { 053 054 055 /** 056 * Constants used for clarity. 057 */ 058 private static enum AlgFamily { 059 060 AESKW, AESGCMKW 061 } 062 063 064 /** 065 * The key encrypting key. 066 */ 067 private final SecretKey kek; 068 069 070 /** 071 * Creates a new AES encrypter. 072 * 073 * @param kek The Key Encrypting Key. Must be 128 bits (16 bytes), 192 074 * bits (24 bytes) or 256 bits (32 bytes). Must not be 075 * {@code null}. 076 */ 077 public AESEncrypter(final SecretKey kek) { 078 079 if (kek == null) { 080 throw new IllegalArgumentException("The Key Encrypting Key must not be null"); 081 } 082 083 this.kek = kek; 084 } 085 086 /** 087 * Creates a new AES encrypter. 088 * 089 * @param keyBytes The Key Encrypting Key, as a byte array. Must be 128 090 * bits (16 bytes), 192 bits (24 bytes) or 256 bits (32 091 * bytes). Must not be {@code null}. 092 */ 093 public AESEncrypter(final byte[] keyBytes) { 094 095 this(new SecretKeySpec(keyBytes, "AES")); 096 } 097 098 099 /** 100 * Gets the Key Encrypting Key. 101 * 102 * @return The Key Encrypting Key. 103 */ 104 public SecretKey getKey() { 105 106 return kek; 107 } 108 109 110 @Override 111 public JWECryptoParts encrypt(final JWEHeader header, final byte[] bytes) 112 throws JOSEException { 113 114 final JWEAlgorithm alg = header.getAlgorithm(); 115 final EncryptionMethod enc = header.getEncryptionMethod(); 116 117 // Generate and encrypt the CEK according to the enc method 118 final SecureRandom randomGen = getSecureRandom(); 119 final SecretKey cek = AES.generateKey(enc.cekBitLength(), randomGen); 120 byte[] keyIV; 121 122 final AuthenticatedCipherText authCiphCEK; 123 124 AlgFamily algFamily; 125 126 Base64URL encryptedKey; // The second JWE part 127 128 if (alg.equals(JWEAlgorithm.A128KW)) { 129 130 if(kek.getEncoded().length != 16){ 131 throw new JOSEException("The Key Encryption Key (KEK) length must be 128 bits for A128KW encryption"); 132 } 133 algFamily = AlgFamily.AESKW; 134 135 } else if (alg.equals(JWEAlgorithm.A192KW)) { 136 137 if(kek.getEncoded().length != 24){ 138 throw new JOSEException("The Key Encryption Key (KEK) length must be 192 bits for A192KW encryption"); 139 } 140 algFamily = AlgFamily.AESKW; 141 142 } else if (alg.equals(JWEAlgorithm.A256KW)) { 143 144 if (kek.getEncoded().length != 32) { 145 throw new JOSEException("The Key Encryption Key (KEK) length must be 256 bits for A256KW encryption"); 146 } 147 algFamily = AlgFamily.AESKW; 148 149 } else if (alg.equals(JWEAlgorithm.A128GCMKW)) { 150 151 if(kek.getEncoded().length != 16){ 152 throw new JOSEException("The Key Encryption Key (KEK) length must be 128 bits for A128GCMKW encryption"); 153 } 154 algFamily = AlgFamily.AESGCMKW; 155 156 } else if (alg.equals(JWEAlgorithm.A192GCMKW)) { 157 158 if(kek.getEncoded().length != 24){ 159 throw new JOSEException("The Key Encryption Key (KEK) length must be 192 bits for A192GCMKW encryption"); 160 } 161 algFamily = AlgFamily.AESGCMKW; 162 163 } else if (alg.equals(JWEAlgorithm.A256GCMKW)) { 164 165 if(kek.getEncoded().length != 32){ 166 throw new JOSEException("The Key Encryption Key (KEK) length must be 256 bits for A256GCMKW encryption"); 167 } 168 algFamily = AlgFamily.AESGCMKW; 169 170 } else { 171 172 throw new JOSEException("Unsupported JWE algorithm, must be A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW orA256GCMKW"); 173 } 174 175 // We need to work on the header 176 JWEHeader modifiableHeader; 177 178 switch (algFamily) { 179 180 case AESKW: 181 encryptedKey = Base64URL.encode(AESKW.encryptCEK(cek, kek)); 182 modifiableHeader = header; // simply copy ref 183 break; 184 185 case AESGCMKW: 186 keyIV = AESGCM.generateIV(randomGen); 187 authCiphCEK = AESGCMKW.encryptCEK(cek, keyIV, kek, keyEncryptionProvider); 188 encryptedKey = Base64URL.encode(authCiphCEK.getCipherText()); 189 190 // Add iv and tag to the header 191 modifiableHeader = new JWEHeader.Builder(header). 192 iv(Base64URL.encode(keyIV)). 193 authTag(Base64URL.encode(authCiphCEK.getAuthenticationTag())). 194 build(); 195 break; 196 197 default: 198 // This should never happen 199 throw new JOSEException("Unsupported JWE algorithm, must be A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW orA256GCMKW"); 200 } 201 202 // Apply compression if instructed 203 byte[] plainText = DeflateHelper.applyCompression(modifiableHeader, bytes); 204 205 // Compose the AAD 206 byte[] aad = StringUtils.toByteArray(modifiableHeader.toBase64URL().toString()); 207 208 // Encrypt the plain text according to the JWE enc 209 byte[] iv; 210 AuthenticatedCipherText authCipherText; 211 212 if (enc.equals(EncryptionMethod.A128CBC_HS256) || 213 enc.equals(EncryptionMethod.A192CBC_HS384) || 214 enc.equals(EncryptionMethod.A256CBC_HS512) ) { 215 216 iv = AESCBC.generateIV(randomGen); 217 218 authCipherText = AESCBC.encryptAuthenticated( 219 cek, iv, plainText, aad, 220 contentEncryptionProvider, macProvider); 221 222 } else if (enc.equals(EncryptionMethod.A128GCM) || 223 enc.equals(EncryptionMethod.A192GCM) || 224 enc.equals(EncryptionMethod.A256GCM) ) { 225 226 iv = AESGCM.generateIV(randomGen); 227 228 authCipherText = AESGCM.encrypt( 229 cek, iv, plainText, aad, 230 contentEncryptionProvider); 231 232 } else if (enc.equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) || 233 enc.equals(EncryptionMethod.A256CBC_HS512_DEPRECATED) ) { 234 235 iv = AESCBC.generateIV(randomGen); 236 237 authCipherText = AESCBC.encryptWithConcatKDF( 238 modifiableHeader, cek, encryptedKey, iv, plainText, 239 contentEncryptionProvider, macProvider); 240 241 } else { 242 243 throw new JOSEException("Unsupported encryption method, must be A128CBC_HS256, A192CBC_HS384, A256CBC_HS512, A128GCM, A192GCM or A256GCM"); 244 } 245 246 return new JWECryptoParts( 247 modifiableHeader, 248 encryptedKey, 249 Base64URL.encode(iv), 250 Base64URL.encode(authCipherText.getCipherText()), 251 Base64URL.encode(authCipherText.getAuthenticationTag())); 252 } 253}