• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 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.KeyWrap;
20 import java.security.GeneralSecurityException;
21 import java.util.Arrays;
22 import javax.crypto.BadPaddingException;
23 import javax.crypto.Cipher;
24 import javax.crypto.SecretKey;
25 import javax.crypto.spec.SecretKeySpec;
26 
27 /**
28  * Implements the key wrapping primitive KWP defined in NIST SP 800 38f. The same encryption mode is
29  * also defined in RFC 5649. The NIST document is used here as a primary reference, since it
30  * contains a security analysis and further recommendations. In particular, Section 8 of NIST SP 800
31  * 38f suggests that the allowed key sizes may be restricted. The implementation in this class
32  * requires that the key sizes are in the range MIN_WRAP_KEY_SIZE and MAX_WRAP_KEY_SIZE.
33  *
34  * <p>The minimum of 16 bytes has been chosen, because 128 bit keys are the smallest key sizes used
35  * in tink. Additionally, wrapping short keys with KWP does not use the function W and hence
36  * prevents using security arguments based on the assumption that W is strong pseudorandom. (I.e.
37  * one consequence of using a strong pseudorandom permutation as an underlying function is that
38  * leaking partial information about decrypted bytes is not useful for an attack.)
39  *
40  * <p>The upper bound for the key size is somewhat arbitrary. Setting an upper bound is motivated by
41  * the analysis in section A.4 of NIST SP 800 38f: forgeries of long messages is simpler than
42  * forgeries of short message.
43  *
44  * @deprecated Tink does not support KeyWrap anymore. This implementation was fallback code for old
45  *     providers that did not implement KWP. It implements the same functionality as {@code
46  *     Cipher.getInstance("AESWRAPPAD");}. Some provider use a different algorithm name: {@code
47  *     Cipher.getInstance("AES/KWP/NoPadding");}.
48  */
49 @Deprecated
50 public class Kwp implements KeyWrap {
51   private final SecretKey aesKey;
52 
53   static final int MIN_WRAP_KEY_SIZE = 16;
54   static final int MAX_WRAP_KEY_SIZE = 4096;
55   static final int ROUNDS = 6;
56   static final byte[] PREFIX = new byte[]{(byte) 0xa6, (byte) 0x59, (byte) 0x59, (byte) 0xa6};
57 
58   /**
59    * Construct a new Instance for KWP.
60    * @param key the wrapping key. This is an AES key.
61    *   Supported key sizes are 128 and 256 bits.
62    */
Kwp(final byte[] key)63   public Kwp(final byte[] key) throws GeneralSecurityException {
64     if (key.length != 16 && key.length != 32) {
65       throw new GeneralSecurityException("Unsupported key length");
66     }
67     aesKey = new SecretKeySpec(key, "AES");
68   }
69 
70   /**
71    * Returns the size of a wrapped key for a given input size.
72    * @param inputSize the size of the key to wrap in bytes.
73    */
wrappingSize(int inputSize)74   private int wrappingSize(int inputSize) {
75     int paddingSize = 7 - (inputSize + 7) % 8;
76     return inputSize + paddingSize + 8;
77   }
78 
79   /**
80    * Computes the pseudorandom permutation W over the IV
81    * concatenated with zero padded key material.
82    * @param iv an IV of size 8.
83    * @param key the key to wrap.
84    *            The pseudorandom permutation W is only defined for
85    *            inputs with a size that is a multiple of 8 bytes and
86    *            that is at least 24 bytes long. Hence computeW is undefined
87    *            for keys of size 8 bytes or shorter.
88    */
computeW(final byte[] iv, final byte[] key)89   private byte[] computeW(final byte[] iv, final byte[] key)
90       throws GeneralSecurityException {
91     // Checks the parameter sizes for which W is defined.
92     // Note, that the caller ensures stricter limits.
93     if (key.length <= 8 || key.length > Integer.MAX_VALUE - 16 || iv.length != 8) {
94       throw new GeneralSecurityException("computeW called with invalid parameters");
95     }
96     byte[] data = new byte[wrappingSize(key.length)];
97     System.arraycopy(iv, 0, data, 0, iv.length);
98     System.arraycopy(key, 0, data, 8, key.length);
99     int blocks = data.length / 8 - 1;
100     Cipher aes = EngineFactory.CIPHER.getInstance("AES/ECB/NoPadding");
101     aes.init(Cipher.ENCRYPT_MODE, aesKey);
102     byte[] block = new byte[16];
103     System.arraycopy(data, 0, block, 0, 8);
104     for (int i = 0; i < ROUNDS; i++) {
105       for (int j = 0; j < blocks; j++) {
106         System.arraycopy(data, 8 * (j + 1), block, 8, 8);
107         int length = aes.doFinal(block, 0, 16, block);
108         assert length == 16;
109         // xor the round constant in bigendian order to the left half of block.
110         int roundConst = i * blocks + j + 1;
111         for (int b = 0; b < 4; b++) {
112           block[7 - b] ^= (byte) (roundConst & 0xff);
113           roundConst >>>= 8;
114         }
115         System.arraycopy(block, 8, data, 8 * (j + 1), 8);
116       }
117     }
118     System.arraycopy(block, 0, data, 0, 8);
119     return data;
120   }
121 
122   /**
123    * Compute the inverse of the pseudorandom permutation W.
124    * @param wrapped the input data to invert. This is the wrapped key.
125    * @return the concatenation of the IV followed by a potentially
126    *         zero padded key.
127    *         invertW does not perform an integrity check.
128    */
invertW(final byte[] wrapped)129   private byte[] invertW(final byte[] wrapped) throws GeneralSecurityException {
130     // Checks the input size for which invertW is defined.
131     // The caller ensures stricter limits
132     if (wrapped.length < 24 || wrapped.length % 8 != 0) {
133       throw new GeneralSecurityException("Incorrect data size");
134     }
135     byte[] data = Arrays.copyOf(wrapped, wrapped.length);
136     int blocks = data.length / 8 - 1;
137     Cipher aes = EngineFactory.CIPHER.getInstance("AES/ECB/NoPadding");
138     aes.init(Cipher.DECRYPT_MODE, aesKey);
139     byte[] block = new byte[16];
140     System.arraycopy(data, 0, block, 0, 8);
141     for (int i = ROUNDS - 1; i >= 0; i--) {
142       for (int j = blocks - 1; j >= 0; j--) {
143         System.arraycopy(data, 8 * (j + 1), block, 8, 8);
144         // xor the round constant in bigendian order to the left half of block.
145         int roundConst = i * blocks + j + 1;
146         for (int b = 0; b < 4; b++) {
147           block[7 - b] ^= (byte) (roundConst & 0xff);
148           roundConst >>>= 8;
149         }
150 
151         int length = aes.doFinal(block, 0, 16, block);
152         assert length == 16;
153         System.arraycopy(block, 8, data, 8 * (j + 1), 8);
154       }
155     }
156     System.arraycopy(block, 0, data, 0, 8);
157     return data;
158   }
159 
160   /**
161    * Wraps some key material {@code data}.
162    *
163    * @param data the key to wrap.
164    * @return the wrapped key
165    */
166   @Override
wrap(final byte[] data)167   public byte[] wrap(final byte[] data) throws GeneralSecurityException {
168     if (data.length < MIN_WRAP_KEY_SIZE) {
169       throw new GeneralSecurityException("Key size of key to wrap too small");
170     }
171     if (data.length > MAX_WRAP_KEY_SIZE) {
172       throw new GeneralSecurityException("Key size of key to wrap too large");
173     }
174     byte[] iv = new byte[8];
175     System.arraycopy(PREFIX, 0, iv, 0, PREFIX.length);
176     for (int i = 0; i < 4; i++) {
177       iv[4 + i] = (byte) ((data.length >> (8 * (3 - i))) & 0xff);
178     }
179     return computeW(iv, data);
180   }
181 
182   /**
183    * Unwraps a wrapped key.
184    *
185    * @throws GeneralSecurityException if {@code data} fails the integrity check.
186    */
187   @Override
unwrap(final byte[] data)188   public byte[] unwrap(final byte[] data) throws GeneralSecurityException {
189     if (data.length < wrappingSize(MIN_WRAP_KEY_SIZE)) {
190       throw new GeneralSecurityException("Wrapped key size is too small");
191     }
192     if (data.length > wrappingSize(MAX_WRAP_KEY_SIZE)) {
193       throw new GeneralSecurityException("Wrapped key size is too large");
194     }
195     if (data.length % 8 != 0) {
196       throw new GeneralSecurityException(
197           "Wrapped key size must be a multiple of 8 bytes");
198     }
199     byte[] unwrapped = invertW(data);
200     // Check the padding.
201     // W has been designed to be a strong pseudorandom permutation.
202     // Hence leaking any amount of information about improperly padded keys
203     // would not be a vulnerability. This means that here we don't have to go to
204     // some extra length to assure that the code is constant time.
205     boolean ok = true;
206     for (int i = 0; i < 4; i++) {
207       if (PREFIX[i] != unwrapped[i]) {
208         ok = false;
209       }
210     }
211     int encodedSize = 0;
212     for (int i = 4; i < 8; i++) {
213       encodedSize = (encodedSize << 8) + (unwrapped[i] & 0xff);
214     }
215     if (wrappingSize(encodedSize) != unwrapped.length) {
216       ok = false;
217     } else {
218       for (int j = 8 + encodedSize; j < unwrapped.length; j++) {
219         if (unwrapped[j] != 0) {
220           ok = false;
221         }
222       }
223     }
224     if (ok) {
225       return Arrays.copyOfRange(unwrapped, 8, 8 + encodedSize);
226     } else {
227       throw new BadPaddingException("Invalid padding");
228     }
229   }
230 }
231