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}