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 if(AlgFamily.AESKW.equals(algFamily)) { 179 encryptedKey = Base64URL.encode(AESKW.encryptCEK(cek, kek)); 180 modifiableHeader = header; // simply copy ref 181 } else if(AlgFamily.AESGCMKW.equals(algFamily)) { 182 keyIV = AESGCM.generateIV(randomGen); 183 authCiphCEK = AESGCMKW.encryptCEK(cek, keyIV, kek, keyEncryptionProvider); 184 encryptedKey = Base64URL.encode(authCiphCEK.getCipherText()); 185 186 // Add iv and tag to the header 187 modifiableHeader = new JWEHeader.Builder(header). 188 iv(Base64URL.encode(keyIV)). 189 authTag(Base64URL.encode(authCiphCEK.getAuthenticationTag())). 190 build(); 191 } else { 192 // This should never happen 193 throw new JOSEException("Unsupported JWE algorithm, must be A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW orA256GCMKW"); 194 } 195 196 // Apply compression if instructed 197 byte[] plainText = DeflateHelper.applyCompression(modifiableHeader, bytes); 198 199 // Compose the AAD 200 byte[] aad = StringUtils.toByteArray(modifiableHeader.toBase64URL().toString()); 201 202 // Encrypt the plain text according to the JWE enc 203 byte[] iv; 204 AuthenticatedCipherText authCipherText; 205 206 if (enc.equals(EncryptionMethod.A128CBC_HS256) || 207 enc.equals(EncryptionMethod.A192CBC_HS384) || 208 enc.equals(EncryptionMethod.A256CBC_HS512) ) { 209 210 iv = AESCBC.generateIV(randomGen); 211 212 authCipherText = AESCBC.encryptAuthenticated( 213 cek, iv, plainText, aad, 214 contentEncryptionProvider, macProvider); 215 216 } else if (enc.equals(EncryptionMethod.A128GCM) || 217 enc.equals(EncryptionMethod.A192GCM) || 218 enc.equals(EncryptionMethod.A256GCM) ) { 219 220 iv = AESGCM.generateIV(randomGen); 221 222 authCipherText = AESGCM.encrypt( 223 cek, iv, plainText, aad, 224 contentEncryptionProvider); 225 226 } else if (enc.equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) || 227 enc.equals(EncryptionMethod.A256CBC_HS512_DEPRECATED) ) { 228 229 iv = AESCBC.generateIV(randomGen); 230 231 authCipherText = AESCBC.encryptWithConcatKDF( 232 modifiableHeader, cek, encryptedKey, iv, plainText, 233 contentEncryptionProvider, macProvider); 234 235 } else { 236 237 throw new JOSEException("Unsupported encryption method, must be A128CBC_HS256, A192CBC_HS384, A256CBC_HS512, A128GCM, A192GCM or A256GCM"); 238 } 239 240 return new JWECryptoParts( 241 modifiableHeader, 242 encryptedKey, 243 Base64URL.encode(iv), 244 Base64URL.encode(authCipherText.getCipherText()), 245 Base64URL.encode(authCipherText.getAuthenticationTag())); 246 } 247}