1 // Copyright 2017 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 //////////////////////////////////////////////////////////////////////////////// 16 17 package com.google.crypto.tink.subtle; 18 19 import com.google.crypto.tink.config.internal.TinkFipsUtil; 20 import java.security.GeneralSecurityException; 21 import javax.crypto.Cipher; 22 import javax.crypto.spec.IvParameterSpec; 23 import javax.crypto.spec.SecretKeySpec; 24 25 /** 26 * The primitive implements AES counter mode with random IVs, using JCE. 27 * 28 * <h3>Warning</h3> 29 * 30 * <p>It is safe against chosen-plaintext attacks, but does not provide ciphertext integrity, thus 31 * is unsafe against chosen-ciphertext attacks. 32 * 33 * @since 1.0.0 34 */ 35 public final class AesCtrJceCipher implements IndCpaCipher { 36 public static final TinkFipsUtil.AlgorithmFipsCompatibility FIPS = 37 TinkFipsUtil.AlgorithmFipsCompatibility.ALGORITHM_REQUIRES_BORINGCRYPTO; 38 39 private static final ThreadLocal<Cipher> localCipher = 40 new ThreadLocal<Cipher>() { 41 @Override 42 protected Cipher initialValue() { 43 try { 44 return EngineFactory.CIPHER.getInstance(CIPHER_ALGORITHM); 45 } catch (GeneralSecurityException ex) { 46 throw new IllegalStateException(ex); 47 } 48 } 49 }; 50 51 private static final String KEY_ALGORITHM = "AES"; 52 private static final String CIPHER_ALGORITHM = "AES/CTR/NoPadding"; 53 54 // In counter mode each message is encrypted with an initialization vector (IV) that must be 55 // unique. If one single IV is ever used to encrypt two or more messages, the confidentiality of 56 // these messages might be lost. This cipher uses a randomly generated IV for each message. The 57 // birthday paradox says that if one encrypts 2^k messages, the probability that the random IV 58 // will repeat is roughly 2^{2k - t}, where t is the size in bits of the IV. Thus with 96-bit 59 // (12-byte) IV, if one encrypts 2^32 messages the probability of IV collision is less than 60 // 2^-33 (i.e., less than one in eight billion). 61 private static final int MIN_IV_SIZE_IN_BYTES = 12; 62 63 private final SecretKeySpec keySpec; 64 private final int ivSize; 65 private final int blockSize; 66 AesCtrJceCipher(final byte[] key, int ivSize)67 public AesCtrJceCipher(final byte[] key, int ivSize) throws GeneralSecurityException { 68 if (!FIPS.isCompatible()) { 69 throw new GeneralSecurityException( 70 "Can not use AES-CTR in FIPS-mode, as BoringCrypto module is not available."); 71 } 72 73 Validators.validateAesKeySize(key.length); 74 this.keySpec = new SecretKeySpec(key, KEY_ALGORITHM); 75 this.blockSize = localCipher.get().getBlockSize(); 76 if (ivSize < MIN_IV_SIZE_IN_BYTES || ivSize > blockSize) { 77 throw new GeneralSecurityException("invalid IV size"); 78 } 79 this.ivSize = ivSize; 80 } 81 82 /** 83 * Encrypts the plaintext with counter mode encryption using randomly generated iv. The output 84 * format is iv || raw ciphertext. 85 * 86 * @param plaintext the plaintext to be encrypted. 87 * @return the encryption of plaintext. 88 */ 89 @Override encrypt(final byte[] plaintext)90 public byte[] encrypt(final byte[] plaintext) throws GeneralSecurityException { 91 if (plaintext.length > Integer.MAX_VALUE - ivSize) { 92 throw new GeneralSecurityException( 93 "plaintext length can not exceed " + (Integer.MAX_VALUE - ivSize)); 94 } 95 byte[] ciphertext = new byte[ivSize + plaintext.length]; 96 byte[] iv = Random.randBytes(ivSize); 97 System.arraycopy(iv, 0, ciphertext, 0, ivSize); 98 doCtr(plaintext, 0, plaintext.length, ciphertext, ivSize, iv, true); 99 return ciphertext; 100 } 101 102 /** 103 * Decrypts the ciphertext with counter mode decryption. The ciphertext format is iv || raw 104 * ciphertext. 105 * 106 * @param ciphertext the ciphertext to be decrypted. 107 * @return the decrypted plaintext. 108 */ 109 @Override decrypt(final byte[] ciphertext)110 public byte[] decrypt(final byte[] ciphertext) throws GeneralSecurityException { 111 if (ciphertext.length < ivSize) { 112 throw new GeneralSecurityException("ciphertext too short"); 113 } 114 byte[] iv = new byte[ivSize]; 115 System.arraycopy(ciphertext, 0, iv, 0, ivSize); 116 byte[] plaintext = new byte[ciphertext.length - ivSize]; 117 doCtr(ciphertext, ivSize, ciphertext.length - ivSize, plaintext, 0, iv, false); 118 return plaintext; 119 } 120 doCtr( final byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset, final byte[] iv, boolean encrypt)121 private void doCtr( 122 final byte[] input, 123 int inputOffset, 124 int inputLen, 125 byte[] output, 126 int outputOffset, 127 final byte[] iv, 128 boolean encrypt) 129 throws GeneralSecurityException { 130 Cipher cipher = localCipher.get(); 131 // The counter is big-endian. The counter is composed of iv and (blockSize - ivSize) of zeros. 132 byte[] counter = new byte[blockSize]; 133 System.arraycopy(iv, 0, counter, 0, ivSize); 134 135 IvParameterSpec paramSpec = new IvParameterSpec(counter); 136 if (encrypt) { 137 cipher.init(Cipher.ENCRYPT_MODE, keySpec, paramSpec); 138 } else { 139 cipher.init(Cipher.DECRYPT_MODE, keySpec, paramSpec); 140 } 141 int numBytes = cipher.doFinal(input, inputOffset, inputLen, output, outputOffset); 142 if (numBytes != inputLen) { 143 throw new GeneralSecurityException("stored output's length does not match input's length"); 144 } 145 } 146 } 147