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 java.security.*; 022import java.security.spec.InvalidParameterSpecException; 023import javax.crypto.*; 024import javax.crypto.spec.GCMParameterSpec; 025 026import com.nimbusds.jose.JOSEException; 027import com.nimbusds.jose.util.ByteUtils; 028import com.nimbusds.jose.util.Container; 029import com.nimbusds.jose.util.KeyUtils; 030import net.jcip.annotations.ThreadSafe; 031 032 033/** 034 * AES/GSM/NoPadding encryption and decryption methods. Falls back to the 035 * BouncyCastle.org provider on Java 6. This class is thread-safe. 036 * 037 * <p>See RFC 7518 (JWA), section 5.1 and appendix 3. 038 * 039 * @author Vladimir Dzhuvinov 040 * @author Axel Nennker 041 * @author Dimitar A. Stoikov 042 * @version 2018-01-11 043 */ 044@ThreadSafe 045public class AESGCM { 046 047 048 /** 049 * The standard Initialisation Vector (IV) length (96 bits). 050 */ 051 public static final int IV_BIT_LENGTH = 96; 052 053 054 /** 055 * The standard authentication tag length (128 bits). 056 */ 057 public static final int AUTH_TAG_BIT_LENGTH = 128; 058 059 060 /** 061 * Generates a random 96 bit (12 byte) Initialisation Vector(IV) for 062 * use in AES-GCM encryption. 063 * 064 * <p>See RFC 7518 (JWA), section 5.3. 065 * 066 * @param randomGen The secure random generator to use. Must be 067 * correctly initialised and not {@code null}. 068 * 069 * @return The random 96 bit IV, as 12 byte array. 070 */ 071 public static byte[] generateIV(final SecureRandom randomGen) { 072 073 byte[] bytes = new byte[IV_BIT_LENGTH / 8]; 074 randomGen.nextBytes(bytes); 075 return bytes; 076 } 077 078 079 /** 080 * Encrypts the specified plain text using AES/GCM/NoPadding. 081 * 082 * @param secretKey The AES key. Must not be {@code null}. 083 * @param plainText The plain text. Must not be {@code null}. 084 * @param ivContainer The initialisation vector (IV). Must not be 085 * {@code null}. This is both input and output 086 * parameter. On input, it carries externally 087 * generated IV; on output, it carries the IV the 088 * cipher actually used. JCA/JCE providers may 089 * prefer to use an internally generated IV, e.g. as 090 * described in 091 * <a href="http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf">NIST 092 * Special Publication 800-38D </a>. 093 * @param authData The authenticated data. Must not be {@code null}. 094 * 095 * @return The authenticated cipher text. 096 * 097 * @throws JOSEException If encryption failed. 098 */ 099 public static AuthenticatedCipherText encrypt(final SecretKey secretKey, 100 final Container<byte[]> ivContainer, 101 final byte[] plainText, 102 final byte[] authData, 103 final Provider provider) 104 throws JOSEException { 105 106 // Key alg must be "AES" 107 final SecretKey aesKey = KeyUtils.toAESKey(secretKey); 108 109 Cipher cipher; 110 111 byte[] iv = ivContainer.get(); 112 113 try { 114 if (provider != null) { 115 cipher = Cipher.getInstance("AES/GCM/NoPadding", provider); 116 } else { 117 cipher = Cipher.getInstance("AES/GCM/NoPadding"); 118 } 119 120 GCMParameterSpec gcmSpec = new GCMParameterSpec(AUTH_TAG_BIT_LENGTH, iv); 121 cipher.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec); 122 123 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) { 124 125 throw new JOSEException("Couldn't create AES/GCM/NoPadding cipher: " + e.getMessage(), e); 126 127 } catch (NoClassDefFoundError e) { 128 // We have Java 6, GCMParameterSpec not available, 129 // switch to BouncyCastle API 130 return LegacyAESGCM.encrypt(aesKey, iv, plainText, authData); 131 } 132 133 cipher.updateAAD(authData); 134 135 byte[] cipherOutput; 136 137 try { 138 cipherOutput = cipher.doFinal(plainText); 139 140 } catch (IllegalBlockSizeException | BadPaddingException e) { 141 142 throw new JOSEException("Couldn't encrypt with AES/GCM/NoPadding: " + e.getMessage(), e); 143 } 144 145 final int tagPos = cipherOutput.length - ByteUtils.byteLength(AUTH_TAG_BIT_LENGTH); 146 147 byte[] cipherText = ByteUtils.subArray(cipherOutput, 0, tagPos); 148 byte[] authTag = ByteUtils.subArray(cipherOutput, tagPos, ByteUtils.byteLength(AUTH_TAG_BIT_LENGTH)); 149 150 // retrieve the actual IV used by the cipher -- it may be internally-generated. 151 ivContainer.set(actualIVOf(cipher)); 152 153 return new AuthenticatedCipherText(cipherText, authTag); 154 } 155 156 157 /** 158 * Retrieves the actual algorithm parameters and validates them. 159 * 160 * @param cipher The cipher to interrogate for the parameters it 161 * actually used. 162 * 163 * @return The IV used by the specified cipher. 164 * 165 * @throws JOSEException If retrieval of the algorithm parameters from 166 * the cipher failed, or the parameters are 167 * deemed unusable. 168 * 169 * @see #actualParamsOf(Cipher) 170 * @see #validate(byte[], int) 171 */ 172 private static byte[] actualIVOf(final Cipher cipher) 173 throws JOSEException { 174 175 GCMParameterSpec actualParams = actualParamsOf(cipher); 176 177 byte[] iv = actualParams.getIV(); 178 int tLen = actualParams.getTLen(); 179 180 validate(iv, tLen); 181 182 return iv; 183 } 184 185 186 /** 187 * Validates the specified IV and authentication tag according to the 188 * AES GCM requirements in 189 * <a href="https://tools.ietf.org/html/rfc7518#section-5.3">JWA RFC</a>. 190 * 191 * @param iv The IV to check for compliance. 192 * @param authTagLength The authentication tag length to check for 193 * compliance. 194 * 195 * @throws JOSEException If the parameters don't match the JWA 196 * requirements. 197 * 198 * @see #IV_BIT_LENGTH 199 * @see #AUTH_TAG_BIT_LENGTH 200 */ 201 private static void validate(final byte[] iv, final int authTagLength) 202 throws JOSEException { 203 204 if (ByteUtils.safeBitLength(iv) != IV_BIT_LENGTH) { 205 throw new JOSEException(String.format("IV length of %d bits is required, got %d", IV_BIT_LENGTH, ByteUtils.safeBitLength(iv))); 206 } 207 208 if (authTagLength != AUTH_TAG_BIT_LENGTH) { 209 throw new JOSEException(String.format("Authentication tag length of %d bits is required, got %d", AUTH_TAG_BIT_LENGTH, authTagLength)); 210 } 211 } 212 213 214 /** 215 * Retrieves the actual AES GCM parameters used by the specified 216 * cipher. 217 * 218 * @param cipher The cipher to interrogate. Non-{@code null}. 219 * 220 * @return The AES GCM parameters. Non-{@code null}. 221 * 222 * @throws JOSEException If the parameters cannot be retrieved, are 223 * uninitialized, or are not in the correct form. We want to have the 224 * actual parameters used by the cipher and not rely on the assumption 225 * that they were the same as those we supplied it with. If at runtime 226 * the assumption is incorrect, the ciphertext would not be 227 * decryptable. 228 */ 229 private static GCMParameterSpec actualParamsOf(final Cipher cipher) 230 throws JOSEException { 231 232 AlgorithmParameters algorithmParameters = cipher.getParameters(); 233 234 if (algorithmParameters == null) { 235 throw new JOSEException("AES GCM ciphers are expected to make use of algorithm parameters"); 236 } 237 238 try { 239 // Note: GCMParameterSpec appears in Java 7 240 return algorithmParameters.getParameterSpec(GCMParameterSpec.class); 241 } catch (InvalidParameterSpecException shouldNotHappen) { 242 throw new JOSEException(shouldNotHappen.getMessage(), shouldNotHappen); 243 } 244 } 245 246 247 /** 248 * Decrypts the specified cipher text using AES/GCM/NoPadding. 249 * 250 * @param secretKey The AES key. Must not be {@code null}. 251 * @param iv The initialisation vector (IV). Must not be 252 * {@code null}. 253 * @param cipherText The cipher text. Must not be {@code null}. 254 * @param authData The authenticated data. Must not be {@code null}. 255 * @param authTag The authentication tag. Must not be {@code null}. 256 * 257 * @return The decrypted plain text. 258 * 259 * @throws JOSEException If decryption failed. 260 */ 261 public static byte[] decrypt(final SecretKey secretKey, 262 final byte[] iv, 263 final byte[] cipherText, 264 final byte[] authData, 265 final byte[] authTag, 266 final Provider provider) 267 throws JOSEException { 268 269 // Key alg must be "AES" 270 final SecretKey aesKey = KeyUtils.toAESKey(secretKey); 271 272 Cipher cipher; 273 274 try { 275 if (provider != null) { 276 cipher = Cipher.getInstance("AES/GCM/NoPadding", provider); 277 } else { 278 cipher = Cipher.getInstance("AES/GCM/NoPadding"); 279 } 280 281 GCMParameterSpec gcmSpec = new GCMParameterSpec(AUTH_TAG_BIT_LENGTH, iv); 282 cipher.init(Cipher.DECRYPT_MODE, aesKey, gcmSpec); 283 284 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) { 285 286 throw new JOSEException("Couldn't create AES/GCM/NoPadding cipher: " + e.getMessage(), e); 287 288 } catch (NoClassDefFoundError e) { 289 // We have Java 6, GCMParameterSpec not available, 290 // switch to BouncyCastle API 291 return LegacyAESGCM.decrypt(aesKey, iv, cipherText, authData, authTag); 292 } 293 294 cipher.updateAAD(authData); 295 296 try { 297 return cipher.doFinal(ByteUtils.concat(cipherText, authTag)); 298 299 } catch (IllegalBlockSizeException | BadPaddingException e) { 300 301 throw new JOSEException("AES/GCM/NoPadding decryption failed: " + e.getMessage(), e); 302 } 303 } 304 305 306 /** 307 * Prevents public instantiation. 308 */ 309 private AESGCM() { } 310}