001package com.nimbusds.jose.crypto; 002 003 004import java.security.NoSuchAlgorithmException; 005import java.security.SecureRandom; 006 007import javax.crypto.SecretKey; 008 009import com.nimbusds.jose.EncryptionMethod; 010import com.nimbusds.jose.JOSEException; 011import com.nimbusds.jose.JWEAlgorithm; 012import com.nimbusds.jose.JWECryptoParts; 013import com.nimbusds.jose.JWEEncrypter; 014import com.nimbusds.jose.ReadOnlyJWEHeader; 015import com.nimbusds.jose.util.Base64URL; 016import com.nimbusds.jose.util.StringUtils; 017 018 019/** 020 * Direct encrypter of {@link com.nimbusds.jose.JWEObject JWE objects} with a 021 * shared symmetric key. This class is thread-safe. 022 * 023 * <p>Supports the following JWE algorithms: 024 * 025 * <ul> 026 * <li>{@link com.nimbusds.jose.JWEAlgorithm#DIR} 027 * </ul> 028 * 029 * <p>Supports the following encryption methods: 030 * 031 * <ul> 032 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256} 033 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512} 034 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM} 035 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM} 036 * </ul> 037 * 038 * @author Vladimir Dzhuvinov 039 * @version $version$ (2013-05-29) 040 */ 041public class DirectEncrypter extends DirectCryptoProvider implements JWEEncrypter { 042 043 044 /** 045 * Random byte generator. 046 */ 047 private static SecureRandom randomGen; 048 049 050 /** 051 * Initialises the secure random byte generator. 052 * 053 * @throws JOSEException If the secure random byte generator couldn't 054 * be instantiated. 055 */ 056 private void initSecureRandom() 057 throws JOSEException { 058 059 try { 060 randomGen = SecureRandom.getInstance("SHA1PRNG"); 061 062 } catch(NoSuchAlgorithmException e) { 063 064 throw new JOSEException(e.getMessage(), e); 065 } 066 } 067 068 069 /** 070 * Creates a new direct encrypter. 071 * 072 * @param key The shared symmetric key. Its algorithm must be "AES". 073 * Must be 128 bits (16 bytes), 256 bits (32 bytes) or 512 074 * bits (64 bytes) long. Must not be {@code null}. 075 * 076 * @throws JOSEException If the key length or algorithm are unexpected, 077 * or if the underlying secure random generator 078 * couldn't be instantiated. 079 */ 080 public DirectEncrypter(final SecretKey key) 081 throws JOSEException { 082 083 super(key); 084 085 if (randomGen == null) { 086 087 initSecureRandom(); 088 } 089 } 090 091 092 /** 093 * Creates a new direct encrypter. 094 * 095 * @param keyBytes The shared symmetric key, as a byte array. Must be 096 * 128 bits (16 bytes), 256 bits (32 bytes) or 512 bits 097 * (64 bytes) long. Must not be {@code null}. 098 * 099 * @throws JOSEException If the key length or algorithm are unexpected, 100 * or if the underlying secure random generator 101 * couldn't be instantiated. 102 */ 103 public DirectEncrypter(final byte[] keyBytes) 104 throws JOSEException { 105 106 super(keyBytes); 107 108 initSecureRandom(); 109 } 110 111 112 @Override 113 public JWECryptoParts encrypt(final ReadOnlyJWEHeader readOnlyJWEHeader, final byte[] bytes) 114 throws JOSEException { 115 116 JWEAlgorithm alg = readOnlyJWEHeader.getAlgorithm(); 117 118 if (! alg.equals(JWEAlgorithm.DIR)) { 119 120 throw new JOSEException("Unsupported JWE algorithm, must be \"dir\""); 121 } 122 123 // Check key length matches matches encryption method 124 EncryptionMethod enc = readOnlyJWEHeader.getEncryptionMethod(); 125 126 if (enc.cekBitLength() != getKey().getEncoded().length * 8) { 127 128 throw new JOSEException("The Content Encryption Key (CEK) length must be " + enc.cekBitLength() + " bits for " + enc + " encryption"); 129 } 130 131 final Base64URL encryptedKey = null; // The second JWE part 132 133 134 // Apply compression if instructed 135 byte[] plainText = DeflateHelper.applyCompression(readOnlyJWEHeader, bytes); 136 137 138 // Compose the AAD 139 byte[] aad = StringUtils.toByteArray(readOnlyJWEHeader.toBase64URL().toString()); 140 141 142 // Encrypt the plain text according to the JWE enc 143 byte[] iv; 144 AuthenticatedCipherText authCipherText; 145 146 if (enc.equals(EncryptionMethod.A128CBC_HS256) || enc.equals(EncryptionMethod.A256CBC_HS512)) { 147 148 iv = AESCBC.generateIV(randomGen); 149 150 authCipherText = AESCBC.encryptAuthenticated(getKey(), iv, plainText, aad); 151 152 } else if (enc.equals(EncryptionMethod.A128GCM) || enc.equals(EncryptionMethod.A256GCM)) { 153 154 iv = AESGCM.generateIV(randomGen); 155 156 authCipherText = AESGCM.encrypt(getKey(), iv, plainText, aad); 157 158 } else { 159 160 throw new JOSEException("Unsupported encryption method, must be A128CBC_HS256, A256CBC_HS512, A128GCM or A128GCM"); 161 } 162 163 return new JWECryptoParts(encryptedKey, 164 Base64URL.encode(iv), 165 Base64URL.encode(authCipherText.getCipherText()), 166 Base64URL.encode(authCipherText.getAuthenticationTag())); 167 } 168}