• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 Google LLC
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 //     https://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 package com.google.security.cryptauth.lib.securemessage;
16 
17 import com.google.protobuf.InvalidProtocolBufferException;
18 import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType;
19 import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType;
20 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey;
21 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header;
22 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody;
23 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage;
24 import java.security.KeyFactory;
25 import java.security.KeyPair;
26 import java.security.NoSuchAlgorithmException;
27 import java.security.PrivateKey;
28 import java.security.PublicKey;
29 import java.security.spec.InvalidKeySpecException;
30 import java.security.spec.PKCS8EncodedKeySpec;
31 import java.util.Arrays;
32 import javax.crypto.KeyGenerator;
33 import javax.crypto.SecretKey;
34 import javax.crypto.spec.SecretKeySpec;
35 import junit.framework.TestCase;
36 
37 /**
38  * Tests the library against some very basic test vectors, to help ensure wire-format
39  * compatibility is not broken.
40  */
41 public class SecureMessageSimpleTestVectorTest extends TestCase {
42 
43   private static final KeyFactory EC_KEY_FACTORY;
44   static {
45     try {
46       if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
47         EC_KEY_FACTORY = null;
48       } else {
49         EC_KEY_FACTORY = KeyFactory.getInstance("EC");
50       }
51     } catch (Exception e) {
52       throw new RuntimeException(e);
53     }
54   }
55 
56   private static final byte[] TEST_ASSOCIATED_DATA = {
57     11, 22, 33, 44, 55
58   };
59   private static final byte[] TEST_METADATA = {
60     10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28
61   };
62   private static final byte[] TEST_VKID  = {
63     0, 0, 1
64   };
65   private static final byte[] TEST_DKID = {
66     -1, -1, 0,
67   };
68   private static final byte[] TEST_MESSAGE = {
69     0, 99, 1, 98, 2, 97, 3, 96, 4, 95, 5, 94, 6, 93, 7, 92, 8, 91, 9, 90
70   };
71 
72   // The following fields are initialized below, in a static block that contains auto-generated test
73   // vectors. Initialization can't just be done inline due to code that throws checked exceptions.
74   private static final PublicKey TEST_EC_PUBLIC_KEY;
75   private static final PrivateKey TEST_EC_PRIVATE_KEY;
76   private static final SecretKey TEST_KEY1;
77   private static final SecretKey TEST_KEY2;
78   private static final byte[] TEST_VECTOR_ECDSA_ONLY;
79   private static final byte[] TEST_VECTOR_ECDSA_AND_AES;
80   private static final byte[] TEST_VECTOR_HMAC_AND_AES_SAME_KEYS;
81   private static final byte[] TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS;
82 
testEcdsaOnly()83   public void testEcdsaOnly() throws Exception {
84    if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
85       // On older Android platforms we can't run this test.
86       return;
87     }
88     SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_ECDSA_ONLY);
89     Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
90     HeaderAndBody headerAndBody = SecureMessageParser.parseSignedCleartextMessage(
91         testVector, TEST_EC_PUBLIC_KEY, SigType.ECDSA_P256_SHA256, TEST_ASSOCIATED_DATA);
92     assertTrue(Arrays.equals(
93         unverifiedHeader.toByteArray(),
94         headerAndBody.getHeader().toByteArray()));
95     assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
96     assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
97     assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
98     assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
99     assertFalse(unverifiedHeader.hasDecryptionKeyId());
100   }
101 
testEcdsaAndAes()102   public void testEcdsaAndAes() throws Exception {
103    if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
104       // On older Android platforms we can't run this test.
105       return;
106     }
107     SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_ECDSA_AND_AES);
108     Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
109     HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
110         testVector,
111         TEST_EC_PUBLIC_KEY,
112         SigType.ECDSA_P256_SHA256,
113         TEST_KEY1,
114         EncType.AES_256_CBC,
115         TEST_ASSOCIATED_DATA);
116     assertTrue(Arrays.equals(
117         unverifiedHeader.toByteArray(),
118         headerAndBody.getHeader().toByteArray()));
119     assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
120     assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
121     assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
122     assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
123     assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray()));
124   }
125 
testHmacAndAesSameKeys()126   public void testHmacAndAesSameKeys() throws Exception {
127     SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_HMAC_AND_AES_SAME_KEYS);
128     Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
129 
130     HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
131         testVector,
132         TEST_KEY1,
133         SigType.HMAC_SHA256,
134         TEST_KEY1,
135         EncType.AES_256_CBC,
136         TEST_ASSOCIATED_DATA);
137     assertTrue(Arrays.equals(
138         unverifiedHeader.toByteArray(),
139         headerAndBody.getHeader().toByteArray()));
140     assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
141     assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
142     assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
143     assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
144     assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray()));
145   }
146 
testHmacAndAesDifferentKeys()147   public void testHmacAndAesDifferentKeys() throws Exception {
148     SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS);
149     Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector);
150     HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage(
151         testVector,
152         TEST_KEY1,
153         SigType.HMAC_SHA256,
154         TEST_KEY2,
155         EncType.AES_256_CBC,
156         TEST_ASSOCIATED_DATA);
157     assertTrue(Arrays.equals(
158         unverifiedHeader.toByteArray(),
159         headerAndBody.getHeader().toByteArray()));
160     assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray()));
161     assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength());
162     assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray()));
163     assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray()));
164     assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray()));
165   }
166 
167   /**
168    * This code emits the test vectors to {@code System.out}. It will not generate fresh test
169    * vectors unless an existing test vector is set to {@code null}, but it contains all of the code
170    * used to the generate the test vector values. Ideally, existing test vectors should never be
171    * regenerated, but having this code available should make it easier to add new test vectors.
172    */
testGenerateTestVectorsPseudoTest()173   public void testGenerateTestVectorsPseudoTest() throws Exception {
174    if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
175       // On older Android platforms we can't run this test.
176       return;
177     }
178     System.out.printf("  static {\n    try {\n");
179     String indent = "      ";
180     PublicKey testEcPublicKey = TEST_EC_PUBLIC_KEY;
181     PrivateKey testEcPrivateKey = TEST_EC_PRIVATE_KEY;
182     if (testEcPublicKey == null) {
183       KeyPair testEcKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair();
184       testEcPublicKey = testEcKeyPair.getPublic();
185       testEcPrivateKey = testEcKeyPair.getPrivate();
186     }
187     System.out.printf("%s%s = parsePublicKey(new byte[] %s);\n",
188         indent,
189         "TEST_EC_PUBLIC_KEY",
190         byteArrayToJavaCode(indent, encodePublicKey(testEcPublicKey)));
191     System.out.printf("%s%s = parseEcPrivateKey(new byte[] %s);\n",
192         indent,
193         "TEST_EC_PRIVATE_KEY",
194         byteArrayToJavaCode(indent, encodeEcPrivateKey(testEcPrivateKey)));
195 
196     SecretKey testKey1 = TEST_KEY1;
197     if (testKey1 == null) {
198       testKey1 = makeAesKey();
199     }
200     System.out.printf("%s%s = new SecretKeySpec(new byte[] %s, \"AES\");\n",
201         indent,
202         "TEST_KEY1",
203         byteArrayToJavaCode(indent, testKey1.getEncoded()));
204 
205     SecretKey testKey2 = TEST_KEY2;
206     if (testKey2 == null) {
207       testKey2 = makeAesKey();
208     }
209     System.out.printf("%s%s = new SecretKeySpec(new byte[] %s, \"AES\");\n",
210         indent,
211         "TEST_KEY2",
212         byteArrayToJavaCode(indent, testKey2.getEncoded()));
213 
214     byte[] testVectorEcdsaOnly = TEST_VECTOR_ECDSA_ONLY;
215     if (testVectorEcdsaOnly == null) {
216       testVectorEcdsaOnly = new SecureMessageBuilder()
217           .setAssociatedData(TEST_ASSOCIATED_DATA)
218           .setPublicMetadata(TEST_METADATA)
219           .setVerificationKeyId(TEST_VKID)
220           .buildSignedCleartextMessage(
221               testEcPrivateKey, SigType.ECDSA_P256_SHA256, TEST_MESSAGE).toByteArray();
222     }
223     printInitializerFor(indent, "TEST_VECTOR_ECDSA_ONLY", testVectorEcdsaOnly);
224 
225     byte[] testVectorEcdsaAndAes = TEST_VECTOR_ECDSA_AND_AES;
226     if (testVectorEcdsaAndAes == null) {
227       testVectorEcdsaAndAes = new SecureMessageBuilder()
228           .setAssociatedData(TEST_ASSOCIATED_DATA)
229           .setDecryptionKeyId(TEST_DKID)
230           .setPublicMetadata(TEST_METADATA)
231           .setVerificationKeyId(TEST_VKID)
232           .buildSignCryptedMessage(
233               testEcPrivateKey,
234               SigType.ECDSA_P256_SHA256,
235               testKey1,
236               EncType.AES_256_CBC,
237               TEST_MESSAGE).toByteArray();
238     }
239     printInitializerFor(indent, "TEST_VECTOR_ECDSA_AND_AES", testVectorEcdsaAndAes);
240 
241     byte[] testVectorHmacAndAesSameKeys = TEST_VECTOR_HMAC_AND_AES_SAME_KEYS;
242     if (testVectorHmacAndAesSameKeys == null) {
243       testVectorHmacAndAesSameKeys = new SecureMessageBuilder()
244           .setAssociatedData(TEST_ASSOCIATED_DATA)
245           .setDecryptionKeyId(TEST_DKID)
246           .setPublicMetadata(TEST_METADATA)
247           .setVerificationKeyId(TEST_VKID)
248           .buildSignCryptedMessage(
249               testKey1,
250               SigType.HMAC_SHA256,
251               testKey1,
252               EncType.AES_256_CBC,
253               TEST_MESSAGE).toByteArray();
254     }
255     printInitializerFor(indent, "TEST_VECTOR_HMAC_AND_AES_SAME_KEYS", testVectorHmacAndAesSameKeys);
256 
257     byte[] testVectorHmacAndAesDifferentKeys = TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS;
258     if (testVectorHmacAndAesDifferentKeys == null) {
259       testVectorHmacAndAesDifferentKeys = new SecureMessageBuilder()
260           .setAssociatedData(TEST_ASSOCIATED_DATA)
261           .setDecryptionKeyId(TEST_DKID)
262           .setPublicMetadata(TEST_METADATA)
263           .setVerificationKeyId(TEST_VKID)
264           .buildSignCryptedMessage(
265               testKey1,
266               SigType.HMAC_SHA256,
267               testKey2,
268               EncType.AES_256_CBC,
269               TEST_MESSAGE).toByteArray();
270     }
271     printInitializerFor(
272         indent, "TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS", testVectorHmacAndAesDifferentKeys);
273 
274     System.out.printf(
275         "    } catch (Exception e) {\n      throw new RuntimeException(e);\n    }\n  }\n");
276   }
277 
makeAesKey()278   private SecretKey makeAesKey() throws NoSuchAlgorithmException {
279     KeyGenerator aesKeygen = KeyGenerator.getInstance("AES");
280     aesKeygen.init(256);
281     return aesKeygen.generateKey();
282   }
283 
printInitializerFor(String indent, String name, byte[] value)284   private void printInitializerFor(String indent, String name, byte[] value) {
285     System.out.printf("%s%s = new byte[] %s;\n",
286         indent,
287         name,
288         byteArrayToJavaCode(indent, value));
289   }
290 
byteArrayToJavaCode(String lineIndent, byte[] array)291   private static String byteArrayToJavaCode(String lineIndent, byte[] array) {
292     String newline = "\n" + lineIndent + "    ";
293     String unwrappedArray = Arrays.toString(array).replace("[", "").replace("]", "");
294     int wrapAfter = 16;
295     int count = wrapAfter;
296     StringBuilder result = new StringBuilder("{");
297     for (String entry : unwrappedArray.split(" ")) {
298       if (++count > wrapAfter) {
299         result.append(newline);
300         count = 0;
301       } else {
302         result.append(" ");
303       }
304       result.append(entry);
305     }
306     result.append(" }");
307     return result.toString();
308   }
309 
encodePublicKey(PublicKey pk)310   private static byte[] encodePublicKey(PublicKey pk) {
311     return PublicKeyProtoUtil.encodePublicKey(pk).toByteArray();
312   }
313 
parsePublicKey(byte[] encodedPk)314   private static PublicKey parsePublicKey(byte[] encodedPk)
315       throws InvalidKeySpecException, InvalidProtocolBufferException {
316     GenericPublicKey gpk = GenericPublicKey.parseFrom(encodedPk);
317     if (PublicKeyProtoUtil.isLegacyCryptoRequired()
318         && gpk.getType() == SecureMessageProto.PublicKeyType.EC_P256) {
319       return null;
320     }
321     return PublicKeyProtoUtil.parsePublicKey(gpk);
322   }
323 
encodeEcPrivateKey(PrivateKey sk)324   private static byte[] encodeEcPrivateKey(PrivateKey sk) {
325     return sk.getEncoded();
326   }
327 
parseEcPrivateKey(byte[] sk)328   private static PrivateKey parseEcPrivateKey(byte[] sk) throws InvalidKeySpecException {
329     if (PublicKeyProtoUtil.isLegacyCryptoRequired()) {
330       return null;
331     }
332     return EC_KEY_FACTORY.generatePrivate(new PKCS8EncodedKeySpec(sk));
333   }
334 
335   // The following block of code was automatically generated by cut and pasting the output of the
336   // generateTestVectorsPseudoTest, which should reliably emit this same test vectors. Please
337   // DO NOT DELETE any of these existing test vectors unless you _really_ know what you are doing.
338   //
339   // --- AUTO GENERATED CODE BEGINS HERE ---
340   static {
341     try {
342       TEST_EC_PUBLIC_KEY = parsePublicKey(new byte[] {
343           8, 1, 18, 70, 10, 33, 0, -109, 9, 5, 8, -89, -3, -68, -86, -19, 17,
344           -126, -11, -95, 35, 101, 102, -57, -84, -118, 73, 83, 66, -62, -49, -91, 71, -19,
345           52, 123, 113, 119, 45, 18, 33, 0, -65, -19, 83, -66, -12, 62, 102, -67, 116,
346           64, 42, 55, -84, -101, 90, -106, 113, -89, -30, 57, -112, 96, -99, -126, 14, 83,
347           41, 95, -24, -114, 23, -5 });
348       TEST_EC_PRIVATE_KEY = parseEcPrivateKey(new byte[] {
349           48, 65, 2, 1, 0, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6,
350           8, 42, -122, 72, -50, 61, 3, 1, 7, 4, 39, 48, 37, 2, 1, 1, 4,
351           32, 26, -82, -61, -86, -59, -8, 2, -62, -17, -20, 122, 3, 85, -102, -76, 81,
352           51, 39, -9, 12, 99, -117, 127, 19, 121, 109, -31, -49, 110, 121, 76, -107 });
353       TEST_KEY1 = new SecretKeySpec(new byte[] {
354           -89, 105, 62, -41, -75, 78, 70, 110, -62, -58, -80, -81, -99, -62, 39, 38, 37,
355           -7, -112, -83, 81, 23, 125, -72, -100, 103, -34, -23, -68, 21, -46, -104 }, "AES");
356       TEST_KEY2 = new SecretKeySpec(new byte[] {
357           -6, 48, 107, 61, -99, -89, 111, 33, 70, 54, -13, 111, 81, -120, 50, 89, -119,
358           -113, -114, 63, 12, -68, 40, 42, -77, -58, -49, 18, 69, 91, -20, -65 }, "AES");
359       TEST_VECTOR_ECDSA_ONLY = new byte[] {
360           10, 56, 10, 32, 8, 2, 16, 1, 26, 3, 0, 0, 1, 50, 19, 10, 11,
361           12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
362           56, 5, 18, 20, 0, 99, 1, 98, 2, 97, 3, 96, 4, 95, 5, 94, 6,
363           93, 7, 92, 8, 91, 9, 90, 18, 72, 48, 70, 2, 33, 0, -79, 59, 50,
364           21, 54, 61, -92, 77, -34, -77, -45, -105, 107, -28, -19, 91, -78, 120, 68, 33,
365           11, -76, -1, 50, 64, -127, -78, 6, 108, 115, -13, 126, 2, 33, 0, -72, -44,
366           52, 93, 105, 109, -127, -111, 11, 33, -111, 97, -114, 9, 117, -68, -45, 64, 63,
367           43, 60, -44, -89, -107, -59, -45, 56, 100, -66, -40, 46, -60 };
368       TEST_VECTOR_ECDSA_AND_AES = new byte[] {
369           10, 107, 10, 55, 8, 2, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1,
370           0, 42, 16, -86, 16, 55, -8, -85, -47, -77, -36, -127, 44, -10, -44, -63, 115,
371           -111, 26, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
372           23, 24, 25, 26, 27, 28, 56, 5, 18, 48, -110, 23, -67, 122, -118, 96, -4,
373           32, -113, -104, -107, -16, 76, 37, -61, -67, -63, 90, 38, 96, -47, -105, 56, -34,
374           50, -30, 82, 25, 100, 36, 69, 50, 68, 60, 38, 96, -108, -49, -73, -10, -62,
375           -76, -45, -105, -86, 93, 28, 34, 18, 70, 48, 68, 2, 33, 0, -87, -103, 11,
376           -70, 34, 33, -41, 90, -83, -74, 19, -13, 127, -43, -116, -32, 88, -13, 125, -122,
377           56, -21, 79, 47, 101, 89, -80, -43, 102, 92, 4, -15, 2, 31, 109, -69, 35,
378           21, 44, -27, -77, 32, 17, -90, -68, 113, 55, -24, -122, 40, 81, 51, 0, -84,
379           -29, -12, -26, 73, 105, -32, 116, -28, 84, -116, -117 };
380       TEST_VECTOR_HMAC_AND_AES_SAME_KEYS = new byte[] {
381           10, 91, 10, 55, 8, 1, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1,
382           0, 42, 16, -110, 48, 67, 67, -31, 24, -42, 13, -44, -109, 6, 113, 34, -70,
383           121, 6, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
384           23, 24, 25, 26, 27, 28, 56, 5, 18, 32, -44, -102, -16, 123, 113, -75, 88,
385           -33, 118, 25, 60, -65, 109, 26, -70, -123, 58, -114, 126, 8, 106, -28, 65, -38,
386           -4, 68, -78, -91, 49, -13, 22, -122, 18, 32, 20, -120, -113, -76, 85, -35, -53,
387           37, -18, 66, -38, 32, 10, 30, 89, 112, -39, -27, 24, 93, -36, -100, -127, -79,
388           94, -7, -19, -41, -47, -29, 1, 12 };
389       TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS = new byte[] {
390           10, 107, 10, 55, 8, 1, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1,
391           0, 42, 16, -96, -7, 39, 79, -37, 40, 1, -30, 97, 0, 123, -7, -124, -75,
392           -127, -18, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
393           23, 24, 25, 26, 27, 28, 56, 5, 18, 48, 90, 40, -48, -113, 84, -32, 47,
394           98, 54, -128, 127, 115, 32, 87, -86, 4, -26, 99, 9, -88, 13, 77, 127, 114,
395           -48, -117, -94, 96, -86, -105, -123, 11, 116, -69, -83, -110, 3, -10, 0, -34, 72,
396           10, -58, 3, -119, -94, 23, -114, 18, 32, -25, -126, 95, 125, -110, -62, -36, -78,
397           97, 72, -54, -114, 97, -68, -46, 107, 53, 55, -57, 88, 127, -20, -23, 80, -9,
398           -91, 115, 42, 24, 49, -76, -111 };
399     } catch (Exception e) {
400       throw new RuntimeException(e);
401     }
402   }
403 }
404