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 javax.crypto.SecretKey;
022
023import com.nimbusds.jose.JOSEException;
024import net.jcip.annotations.ThreadSafe;
025import org.bouncycastle.crypto.BlockCipher;
026import org.bouncycastle.crypto.CipherParameters;
027import org.bouncycastle.crypto.InvalidCipherTextException;
028import org.bouncycastle.crypto.engines.AESEngine;
029import org.bouncycastle.crypto.modes.GCMBlockCipher;
030import org.bouncycastle.crypto.params.AEADParameters;
031import org.bouncycastle.crypto.params.KeyParameter;
032
033
034/**
035 * Legacy AES/GSM/NoPadding encryption and decryption methods. Uses the
036 * BouncyCastle.org API. This class is thread-safe.
037 *
038 * @author Vladimir Dzhuvinov
039 * @author Axel Nennker
040 * @version 2015-11-15
041 */
042@ThreadSafe
043public class LegacyAESGCM {
044
045
046        /**
047         * The standard authentication tag length (128 bits).
048         */
049        public static final int AUTH_TAG_BIT_LENGTH = 128;
050
051
052        /**
053         * Creates a new AES cipher.
054         *
055         * @param secretKey     The AES key. Must not be {@code null}.
056         * @param forEncryption If {@code true} creates an AES encryption
057         *                      cipher, else creates an AES decryption
058         *                      cipher.
059         *
060         * @return The AES cipher.
061         */
062        public static AESEngine createAESCipher(final SecretKey secretKey,
063                                                final boolean forEncryption) {
064
065                AESEngine cipher = new AESEngine();
066
067                CipherParameters cipherParams = new KeyParameter(secretKey.getEncoded());
068
069                cipher.init(forEncryption, cipherParams);
070
071                return cipher;
072        }
073
074
075        /**
076         * Creates a new AES/GCM/NoPadding cipher.
077         *
078         * @param secretKey     The AES key. Must not be {@code null}.
079         * @param forEncryption If {@code true} creates an encryption cipher,
080         *                      else creates a decryption cipher.
081         * @param iv            The initialisation vector (IV). Must not be
082         *                      {@code null}.
083         * @param authData      The authenticated data. Must not be
084         *                      {@code null}.
085         *
086         * @return The AES/GCM/NoPadding cipher.
087         */
088        private static GCMBlockCipher createAESGCMCipher(final SecretKey secretKey,
089                                                         final boolean forEncryption,
090                                                         final byte[] iv,
091                                                         final byte[] authData) {
092
093                // Initialise AES cipher
094                BlockCipher cipher = createAESCipher(secretKey, forEncryption);
095
096                // Create GCM cipher with AES
097                GCMBlockCipher gcm = new GCMBlockCipher(cipher);
098
099                AEADParameters aeadParams = new AEADParameters(new KeyParameter(secretKey.getEncoded()),
100                        AUTH_TAG_BIT_LENGTH,
101                        iv,
102                        authData);
103                gcm.init(forEncryption, aeadParams);
104
105                return gcm;
106        }
107
108
109        /**
110         * Encrypts the specified plain text using AES/GCM/NoPadding.
111         *
112         * @param secretKey The AES key. Must not be {@code null}.
113         * @param plainText The plain text. Must not be {@code null}.
114         * @param iv        The initialisation vector (IV). Must not be
115         *                  {@code null}.
116         * @param authData  The authenticated data. Must not be {@code null}.
117         *
118         * @return The authenticated cipher text.
119         *
120         * @throws JOSEException If encryption failed.
121         */
122        public static AuthenticatedCipherText encrypt(final SecretKey secretKey,
123                                                      final byte[] iv,
124                                                      final byte[] plainText,
125                                                      final byte[] authData)
126                throws JOSEException {
127
128                // Initialise AES/GCM cipher for encryption
129                GCMBlockCipher cipher = createAESGCMCipher(secretKey, true, iv, authData);
130
131
132                // Prepare output buffer
133                int outputLength = cipher.getOutputSize(plainText.length);
134                byte[] output = new byte[outputLength];
135
136
137                // Produce cipher text
138                int outputOffset = cipher.processBytes(plainText, 0, plainText.length, output, 0);
139
140
141                // Produce authentication tag
142                try {
143                        outputOffset += cipher.doFinal(output, outputOffset);
144
145                } catch (InvalidCipherTextException e) {
146
147                        throw new JOSEException("Couldn't generate GCM authentication tag: " + e.getMessage(), e);
148                }
149
150                // Split output into cipher text and authentication tag
151                int authTagLength = AUTH_TAG_BIT_LENGTH / 8;
152
153                byte[] cipherText = new byte[outputOffset - authTagLength];
154                byte[] authTag = new byte[authTagLength];
155
156                System.arraycopy(output, 0, cipherText, 0, cipherText.length);
157                System.arraycopy(output, outputOffset - authTagLength, authTag, 0, authTag.length);
158
159                return new AuthenticatedCipherText(cipherText, authTag);
160        }
161
162
163        /**
164         * Decrypts the specified cipher text using AES/GCM/NoPadding.
165         *
166         * @param secretKey  The AES key. Must not be {@code null}.
167         * @param iv         The initialisation vector (IV). Must not be
168         *                   {@code null}.
169         * @param cipherText The cipher text. Must not be {@code null}.
170         * @param authData   The authenticated data. Must not be {@code null}.
171         * @param authTag    The authentication tag. Must not be {@code null}.
172         *
173         * @return The decrypted plain text.
174         *
175         * @throws JOSEException If decryption failed.
176         */
177        public static byte[] decrypt(final SecretKey secretKey,
178                                     final byte[] iv,
179                                     final byte[] cipherText,
180                                     final byte[] authData,
181                                     final byte[] authTag)
182                throws JOSEException {
183
184                // Initialise AES/GCM cipher for decryption
185                GCMBlockCipher cipher = createAESGCMCipher(secretKey, false, iv, authData);
186
187
188                // Join cipher text and authentication tag to produce cipher input
189                byte[] input = new byte[cipherText.length + authTag.length];
190
191                System.arraycopy(cipherText, 0, input, 0, cipherText.length);
192                System.arraycopy(authTag, 0, input, cipherText.length, authTag.length);
193
194                int outputLength = cipher.getOutputSize(input.length);
195
196                byte[] output = new byte[outputLength];
197
198
199                // Decrypt
200                int outputOffset = cipher.processBytes(input, 0, input.length, output, 0);
201
202                // Validate authentication tag
203                try {
204                        outputOffset += cipher.doFinal(output, outputOffset);
205
206                } catch (InvalidCipherTextException e) {
207
208                        throw new JOSEException("Couldn't validate GCM authentication tag: " + e.getMessage(), e);
209                }
210
211                return output;
212        }
213
214
215        /**
216         * Prevents public instantiation.
217         */
218        private LegacyAESGCM() { }
219}