6879539: enable empty password support for pkcs12 keystore
authorweijun
Fri, 10 Feb 2012 11:41:22 +0800
changeset 11835 c9e7cfc908b3
parent 11834 0de27a1b7946
child 11836 4b82a1c142cf
6879539: enable empty password support for pkcs12 keystore Reviewed-by: vinnie, weijun Contributed-by: Florian Weimer <fweimer@bfk.de>
jdk/src/share/classes/com/sun/crypto/provider/PBEKey.java
jdk/src/share/classes/com/sun/crypto/provider/PKCS12PBECipherCore.java
jdk/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java
jdk/test/sun/security/pkcs12/Bug6415637.java
--- a/jdk/src/share/classes/com/sun/crypto/provider/PBEKey.java	Thu Feb 09 16:38:36 2012 +0000
+++ b/jdk/src/share/classes/com/sun/crypto/provider/PBEKey.java	Fri Feb 10 11:41:22 2012 +0800
@@ -55,9 +55,12 @@
             // Should allow an empty password.
             passwd = new char[0];
         }
-        for (int i=0; i<passwd.length; i++) {
-            if ((passwd[i] < '\u0020') || (passwd[i] > '\u007E')) {
-                throw new InvalidKeySpecException("Password is not ASCII");
+        // Accept "\0" to signify "zero-length password with no terminator".
+        if (!(passwd.length == 1 && passwd[0] == 0)) {
+            for (int i=0; i<passwd.length; i++) {
+                if ((passwd[i] < '\u0020') || (passwd[i] > '\u007E')) {
+                    throw new InvalidKeySpecException("Password is not ASCII");
+                }
             }
         }
         this.key = new byte[passwd.length];
--- a/jdk/src/share/classes/com/sun/crypto/provider/PKCS12PBECipherCore.java	Thu Feb 09 16:38:36 2012 +0000
+++ b/jdk/src/share/classes/com/sun/crypto/provider/PKCS12PBECipherCore.java	Fri Feb 10 11:41:22 2012 +0800
@@ -60,11 +60,16 @@
 
     static byte[] derive(char[] chars, byte[] salt,
                          int ic, int n, int type) {
-        // Add in trailing NULL terminator.
+        // Add in trailing NULL terminator.  Special case:
+        // no terminator if password is "\0".
         int length = chars.length*2;
-        if (length != 0) {
+        if (length == 2 && chars[0] == 0) {
+            chars = new char[0];
+            length = 0;
+        } else {
             length += 2;
         }
+
         byte[] passwd = new byte[length];
         for (int i = 0, j = 0; i < chars.length; i++, j+=2) {
             passwd[j] = (byte) ((chars[i] >>> 8) & 0xFF);
@@ -133,6 +138,9 @@
     }
 
     private static void concat(byte[] src, byte[] dst, int start, int len) {
+        if (src.length == 0) {
+            return;
+        }
         int loop = len / src.length;
         int off, i;
         for (i = 0, off = 0; i < loop; i++, off += src.length)
--- a/jdk/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java	Thu Feb 09 16:38:36 2012 +0000
+++ b/jdk/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java	Fri Feb 10 11:41:22 2012 +0800
@@ -253,11 +253,25 @@
         }
 
         try {
-            // Use JCE
-            SecretKey skey = getPBEKey(password);
-            Cipher cipher = Cipher.getInstance(algOid.toString());
-            cipher.init(Cipher.DECRYPT_MODE, skey, algParams);
-            byte[] privateKeyInfo = cipher.doFinal(encryptedKey);
+            byte[] privateKeyInfo;
+            while (true) {
+                try {
+                    // Use JCE
+                    SecretKey skey = getPBEKey(password);
+                    Cipher cipher = Cipher.getInstance(algOid.toString());
+                    cipher.init(Cipher.DECRYPT_MODE, skey, algParams);
+                    privateKeyInfo = cipher.doFinal(encryptedKey);
+                    break;
+                } catch (Exception e) {
+                    if (password.length == 0) {
+                        // Retry using an empty password
+                        // without a NULL terminator.
+                        password = new char[1];
+                        continue;
+                    }
+                    throw e;
+                }
+            }
 
             PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(privateKeyInfo);
 
@@ -1251,16 +1265,24 @@
                 ObjectIdentifier algOid = in.getOID();
                 AlgorithmParameters algParams = parseAlgParameters(in);
 
-                try {
-                    // Use JCE
-                    SecretKey skey = getPBEKey(password);
-                    Cipher cipher = Cipher.getInstance(algOid.toString());
-                    cipher.init(Cipher.DECRYPT_MODE, skey, algParams);
-                    safeContentsData = cipher.doFinal(safeContentsData);
-
-                } catch (Exception e) {
-                    throw new IOException("failed to decrypt safe"
-                            + " contents entry: " + e, e);
+                while (true) {
+                    try {
+                        // Use JCE
+                        SecretKey skey = getPBEKey(password);
+                        Cipher cipher = Cipher.getInstance(algOid.toString());
+                        cipher.init(Cipher.DECRYPT_MODE, skey, algParams);
+                        safeContentsData = cipher.doFinal(safeContentsData);
+                        break;
+                    } catch (Exception e) {
+                        if (password.length == 0) {
+                            // Retry using an empty password
+                            // without a NULL terminator.
+                            password = new char[1];
+                            continue;
+                        }
+                        throw new IOException(
+                                "failed to decrypt safe contents entry: " + e, e);
+                    }
                 }
             } else {
                 throw new IOException("public key protected PKCS12" +
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/pkcs12/Bug6415637.java	Fri Feb 10 11:41:22 2012 +0800
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 6415637
+ * @summary Support PKCS#12 key stores protected with an empty password
+ */
+
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateCrtKey;
+
+public class Bug6415637 {
+
+    public static void main(String[] args) throws Exception {
+        check(WITH_NULL);
+        check(WITHOUT_NULL);
+    }
+
+    private static void check(String encodedBlob) throws Exception {
+        byte[] blob = new byte[encodedBlob.length() * 2];
+        for (int i = 0; i < blob.length; ) {
+            final char ch = encodedBlob.charAt(i / 2);
+            blob[i++] = (byte) (ch >> 8);
+            blob[i++] = (byte) ch;
+        }
+        KeyStore store = KeyStore.getInstance("PKCS12");
+        store.load(new ByteArrayInputStream(blob), new char[0]);
+        if (!store.aliases().nextElement().equals("test"))
+            throw new Exception("test alias not found");
+        KeyStore.PrivateKeyEntry e =
+            (KeyStore.PrivateKeyEntry) store.getEntry("test",
+                    new KeyStore.PasswordProtection(new char[0]));
+        X509Certificate cert = (X509Certificate) e.getCertificateChain()[0];
+        if (!cert.getSubjectDN().toString().equals("CN=Test Key"))
+            throw new Exception("invalid certificate subject DN");
+        RSAPrivateCrtKey key = (RSAPrivateCrtKey) e.getPrivateKey();
+        if (!key.getPublicExponent().equals(BigInteger.valueOf(65537)))
+            throw new Exception("invalid public exponent");
+    }
+
+    private static final String WITH_NULL =
+        "\u3082\u097c\u0201\u0330\u8209\u3606\u092a\u8648\u86f7\u0d01" +
+        "\u0701\ua082\u0927\u0482\u0923\u3082\u091f\u3082\u0564\u0609" +
+        "\u2a86\u4886\uf70d\u0107\u01a0\u8205\u5504\u8205\u5130\u8205" +
+        "\u4d30\u8205\u4906\u0b2a\u8648\u86f7\u0d01\u0c0a\u0102\ua082" +
+        "\u04fa\u3082\u04f6\u3028\u060a\u2a86\u4886\uf70d\u010c\u0103" +
+        "\u301a\u0414\u8317\ucfd6\u89ab\uc03b\u79d6\u4c45\u1b10\uc3bd" +
+        "\u3923\ub806\u0202\u0400\u0482\u04c8\ud51d\uf953\ud79e\u92c6" +
+        "\u83c0\u92dc\ucd05\u69ab\ucc1b\ud538\u2ed9\u7796\ua426\uc14e" +
+        "\udcbf\ue541\u40be\ud264\u3b5b\uf51f\u8e1a\u892f\u2813\ucdd6" +
+        "\uf72e\uef55\u35ef\u4620\ude18\ued5e\ufae0\ubed4\uf84e\u276b" +
+        "\u3596\uc33f\ub251\ue617\u6e00\ua80f\u6c82\u4acd\u7303\u26be" +
+        "\uffb5\u1e49\u5fb1\uf87c\ua873\ue60a\u7415\u655c\u39f1\ucf16" +
+        "\u8f5c\u85c6\u4100\u4130\u565b\u649a\u60d6\u6054\u868d\u7267" +
+        "\u97a8\u8492\uc5a0\udb5e\u2880\udf55\ub0ee\ua641\u8224\u76d2" +
+        "\u9b1e\u2a67\u1e32\ue1fc\u0a77\u435b\u669f\ued00\u6c30\u963f" +
+        "\u7ee3\uc5c8\u198f\u8ede\u30e1\u015d\u1195\uc850\u3371\ub9e5" +
+        "\u6968\u84c3\ub0e4\u22b7\u2a08\u4a9d\u9166\ua9ba\ud945\u0529" +
+        "\ue1e7\u8aba\ub4ef\u7445\udc9a\ucf73\ud77b\ufafe\ue1df\u3180" +
+        "\u9585\ued73\uca40\u06b0\ufdee\u95ba\u1aa3\ubd67\ua5c1\u84b4" +
+        "\u4b50\uc1e1\u4547\ud837\u21bc\uac0d\u0a65\uebb5\u7281\ud9bc" +
+        "\u2e2c\ua9bc\u7714\u0fc0\uab41\uce09\ud5e8\u5f8c\uc35d\uba6e" +
+        "\u98a2\u95c3\u87ff\uba8c\u056a\udc9f\uf254\u3d38\uf40a\u77dd" +
+        "\u4e30\u01de\ubef7\ud288\ue59c\ua143\ub30b\ud0ae\u63b9\u138b" +
+        "\uc793\u3474\u18ca\udeed\u78d9\u2ae8\u63cc\ua5d1\u6779\u0229" +
+        "\u7b72\ucfd4\ueecb\ue167\u08c0\u7556\u181d\u8d62\uc401\ub092" +
+        "\u8cf5\ued71\uf29f\u843e\u13e1\u7e7b\uf589\u0329\u92bd\ud0e3" +
+        "\u8dcc\u7541\uf195\ueef2\u3f3a\ueb01\uf5b0\u1869\u2216\uf351" +
+        "\u488d\udffb\u6243\u1121\u9447\u8a3a\u006f\u008c\ue2b3\ued31" +
+        "\u7f57\u6492\ue02f\u6f68\u387d\u58c0\uaadf\u2ee3\uf304\u3de9" +
+        "\u9741\u47fa\udde8\ufe8a\u679a\u597d\u8c7c\u9c71\u570e\u1dbd" +
+        "\ud555\ue853\uff63\u0fcb\u4b28\u3691\u33c8\uc31f\uc510\u6cba" +
+        "\ud92c\u6462\u733a\u739e\uc792\ud861\u743e\u3bd3\u006b\u2276" +
+        "\u9fb3\u0a31\u1eb3\ub97e\u4a80\uc076\uaabc\u35a0\u678d\u17c3" +
+        "\ua225\ua77c\u7d9c\uef2d\u83e2\u6996\uba70\uf6f8\u79a1\u9399" +
+        "\uc86b\u1cc5\ub2a5\u02c1\ud676\ua274\u4933\u6c60\u6832\ub0be" +
+        "\u5354\u5af9\uae23\u0963\u722d\u9ad2\u4461\ub768\u2068\u0ccb" +
+        "\u94fd\u88ac\u0f58\u3bc0\u212d\u30c8\u8860\uf7c9\u1dde\ub6b4" +
+        "\u3549\u5bcd\ucf83\u9420\u3a40\u16ad\uc4d7\ucd87\ue73a\ue1c7" +
+        "\u21df\u7f4f\u8659\u9f79\u5b36\uf206\uac66\uc9f3\u6336\u164d" +
+        "\u9046\uf4d5\u285d\ufcd8\ubd55\u1fb9\ua533\u9101\u1e87\uc7b0" +
+        "\u64e9\u3817\u216c\u8d41\uba51\u743a\uc74e\ue4ab\u2820\u972f" +
+        "\ue191\u85b4\u0ea7\ud896\u23cf\u7df5\u1653\ua9f3\ub724\ucbc9" +
+        "\u9738\ud2f8\u464e\ucf12\u99b8\u64e0\uf03b\u8d02\u85a8\uab52" +
+        "\u8da3\uea34\ube99\ue5f8\u2b38\ub082\u399d\ue61e\u64a1\u7f90" +
+        "\u26e9\ueb74\u6107\ufe2f\u82ca\u87a5\u3028\u8e1f\ue859\u61d4" +
+        "\ud26d\u23a9\uaadc\u02a7\u8ab2\u43d4\uf6b9\udf7a\u8935\u45a4" +
+        "\ufad6\ue7e4\u92b7\u35d7\u1044\u8ed3\u74ef\uaaa9\u713f\u6ebc" +
+        "\u1158\u5e5c\u7522\ufe17\ua515\u59a1\u75dd\ue7ac\uafd9\u16a9" +
+        "\u190e\u18fc\uc041\ufc9e\u3e16\u60c4\ufe51\u6d53\ufa52\u4c08" +
+        "\uce2a\ue546\u017b\ud96b\ube18\u8cb9\udd50\ued40\u14b0\u7da1" +
+        "\u2f2c\ubf9d\uc7c7\u1b73\ua155\ucaf8\ue54d\uebb0\u160a\ubd64" +
+        "\u5ef7\ue1cf\u4633\u86c1\ubc91\u839d\ub148\u9f31\uf2b1\ud133" +
+        "\u168f\u9374\u4667\u6aa9\u0482\ua2a6\ub5c0\ud9b7\ua070\uf6bd" +
+        "\u16fe\u0f41\u986b\u3d33\u7cb9\u291d\u24f0\u704a\uc946\u10a2" +
+        "\udbcf\u6c5f\u5a83\u5507\u036e\ube9f\uf60a\u9da8\u72dd\u23c9" +
+        "\u8878\udd67\uf486\u1384\u751f\u4694\uee3c\udc2e\ud5d7\ud99a" +
+        "\u5ee2\u5455\ub82d\u1837\u336d\u5724\u635b\ubd0b\u2e7c\u92be" +
+        "\u2110\u9c0e\u1662\u43f6\u62ae\u32e3\uaea4\u1cc5\uadc0\u7511" +
+        "\u6ad4\u0228\ue399\u5741\u2050\ue31a\u7dc8\uf6db\u67bb\u994a" +
+        "\u5b5a\uaac6\u2210\u95b0\u462e\u0684\u335e\uac36\u7ab9\uab1e" +
+        "\u0b75\u0f05\u74c5\ufcb3\ua0a5\ube7e\u45f8\u92d5\u3399\u7dd6" +
+        "\uf96e\u7e01\u7823\u6690\u231c\u4c47\u2d10\u7e7f\u5eb8\u70dd" +
+        "\u98d2\u6204\u3a92\u3990\u502b\u7cdb\u952a\ufa97\uea3b\ud990" +
+        "\u436f\uf33a\u070d\u2aff\u7497\u2591\u37e4\ua590\ue7ba\u2c1e" +
+        "\u53d9\u73fa\udc53\u944f\ua3a5\u5093\u33a4\uf080\u1193\u37f2" +
+        "\u7642\ub033\u7f90\u9b44\uff89\ue6ef\u81be\u9e6e\u68a4\u5a00" +
+        "\u9232\u4372\u40aa\u2748\u229d\u534d\u316b\u6e89\ufcb7\uff2e" +
+        "\ub654\u1649\ucb13\u3c28\u4940\u43aa\uc07d\u247c\u313c\u3017" +
+        "\u0609\u2a86\u4886\uf70d\u0109\u1431\u0a1e\u0800\u7400\u6500" +
+        "\u7300\u7430\u2106\u092a\u8648\u86f7\u0d01\u0915\u3114\u0412" +
+        "\u5469\u6d65\u2031\u3330\u3833\u3132\u3234\u3236\u3437\u3082" +
+        "\u03b3\u0609\u2a86\u4886\uf70d\u0107\u06a0\u8203\ua430\u8203" +
+        "\ua002\u0100\u3082\u0399\u0609\u2a86\u4886\uf70d\u0107\u0130" +
+        "\u2806\u0a2a\u8648\u86f7\u0d01\u0c01\u0630\u1a04\u14de\ud8d8" +
+        "\ua792\uf9d9\u6875\ua51d\u98ec\udf03\uc2b6\u5100\u8a02\u0204" +
+        "\u0080\u8203\u6074\ub909\u3c60\ua522\ue4ac\u0f60\u2396\u7baa" +
+        "\ud208\ub76c\u89a5\ue4ef\u205d\u2062\u4a5b\ua684\uceae\u01b9" +
+        "\u1e7a\u6e03\ud996\u555a\u615b\uba70\u406f\u80a9\u901e\ua947" +
+        "\u5b8f\u73f3\udea3\ud8b1\u9782\uac87\u231a\udcd2\u3ef0\u3a17" +
+        "\u4092\u509f\u0e79\u4cd7\u8516\u5111\uebe1\u86e0\uc548\u5ffc" +
+        "\u9a99\u11ed\uef13\u17af\u2707\u8984\u8770\u7064\u1943\u1dd3" +
+        "\u45cf\u9f80\u65f8\u9b3e\u1f70\u6bd0\uc726\u5506\ufb20\u6bdc" +
+        "\uba8c\u0b19\ucd01\ud0f0\u7040\udf63\u48a5\udf5f\u6559\u1b33" +
+        "\ubdae\u8183\uc13f\ued10\ud6dc\ud0f0\u6a7f\ubc36\uc7ca\u320f" +
+        "\u50b8\ud422\ufd99\u8843\u65e8\ue201\u843b\u64ee\ub891\u3ba2" +
+        "\uecae\ufda0\u72d6\u8394\u2551\ufc44\u3778\u27c3\u061a\u6d3b" +
+        "\ubd80\ue010\u06df\u39e7\u3d6a\u5ae2\u93fa\u4de4\u938f\u6f27" +
+        "\ufd39\u4380\u60da\uf215\u79d4\uf6f1\ua02f\u959a\ua0ea\u1c38" +
+        "\u80e3\u2744\u7506\u54b3\u77ad\u18ce\ucfec\u555e\u7bbe\u2e2f" +
+        "\u9900\ub2ef\ua5b9\ubdf0\u5e15\ua681\u92c7\u4f86\u2e1a\ub893" +
+        "\u01fc\u01d2\ub674\uff19\u04c3\ua1a0\u2cea\u72e0\ua8f1\u1358" +
+        "\ube79\u7caa\u269d\u728a\ue435\u37bd\u6495\uc106\u8830\u9b17" +
+        "\ue16d\uef78\uae2b\u5313\u1c96\uc0ee\u3098\ud743\ucd1c\u7407" +
+        "\uf4f9\uee72\ub95e\u31e7\u6435\u0173\u0336\u93c5\u8a1b\u05b4" +
+        "\u4359\uc4be\ud92b\u8d21\u83a9\u32b7\u6433\ua9bc\u27c2\ud842" +
+        "\ua4f2\u81c5\ua86c\u2fd2\uba30\u53bd\uc277\u659f\u203b\u60e5" +
+        "\u37f7\u0984\u31c2\u838a\u2107\u5840\u6411\u1b8d\u044e\ub0b6" +
+        "\uf558\ue6d3\u62bb\u5464\uf83a\u4d5b\uf153\u9e18\ua353\ubd05" +
+        "\uf204\ud543\u037d\ue5aa\u473a\ueb13\uac19\u0494\ua08e\u76c3" +
+        "\ufbd7\u9f1c\u8ca9\u57ad\ud218\uc018\u67ac\u0ae9\ub559\ufe38" +
+        "\u5641\uec0c\ue0ee\u606c\u1989\ue5a2\uff09\u8c61\u1386\ueb51" +
+        "\u7cbd\u95cd\u80c5\u3532\u8605\u596d\u4cfd\u7797\u1e82\ud2fe" +
+        "\uad6b\ua16e\ub6cf\u8fce\ud5a9\u207f\u1d0a\udabe\uc3a6\u5633" +
+        "\u2023\u925f\u809f\uee7c\u5362\u5fd9\u8dfc\u6b5f\uc95b\u0ae9" +
+        "\u7b26\u9e5b\u97e8\u9d6e\uaf91\u6d1a\u1d19\ufc27\u0815\uccbc" +
+        "\u83d4\u2ce2\ue06e\u21a1\u88da\u09af\u9671\uc510\uac23\u398d" +
+        "\ubea2\ua9a1\uf0d3\u490d\ub94b\u7ff7\u6636\ub1fa\u9b10\u1be3" +
+        "\u179b\u6a8a\u4a6c\ude1f\u5da7\u7c02\u96ec\u70ac\u5045\udd2c" +
+        "\u9f6d\uc37d\u5ba6\u4895\ue142\u0db9\uf2dc\uba2e\ud054\ud33e" +
+        "\u1ed9\u144b\u5d85\u9156\u3a90\ue8cd\u0a01\u67f5\ua81b\u4f56" +
+        "\u99dd\u4950\ua551\uacdb\udf31\u1f05\u7169\u3231\u0071\u80ec" +
+        "\ua4e9\ud74e\u62cf\u8931\u11f1\uc925\u0319\uabd4\ufb86\u73c2" +
+        "\u1479\u005b\uf05d\u4f8d\u44e4\u942b\ud338\ud05d\u2b3b\uf6f5" +
+        "\udc0d\uf741\u798b\ud8e9\u36a5\u577b\u8a95\ud773\uffcb\u17b3" +
+        "\u7174\u9616\u9b5e\ua577\u983c\u6e7a\u6cc8\u4a04\u042b\u503e" +
+        "\ud744\ub65e\ue5de\ufa24\u8c71\u1127\ud47f\ud290\ufd4c\u5cbb" +
+        "\u0e21\u77fd\u6553\ub82b\ucb49\u41e7\u8e3d\u4539\u925d\u6ba9" +
+        "\uae47\u391c\ua79e\ub6e2\u7142\u7cb3\u02f5\u6495\u7a85\u2dea" +
+        "\u787b\u22b7\u6ec2\uea8d\uf930\u3d30\u2130\u0906\u052b\u0e03" +
+        "\u021a\u0500\u0414\ubfef\u99f5\u0bb0\uc9b3\uf96a\ue267\u6bc0" +
+        "\u0202\u6d78\ub923\u0414\u5500\u095a\u2a04\u2d7e\u708d\u9779" +
+        "\u9bdb\u2c4f\u82f2\uf89f\u0202\u0400";
+
+    private static final String WITHOUT_NULL =
+        "\u3082\u097c\u0201\u0330\u8209\u3606\u092a\u8648\u86f7\u0d01" +
+        "\u0701\ua082\u0927\u0482\u0923\u3082\u091f\u3082\u0564\u0609" +
+        "\u2a86\u4886\uf70d\u0107\u01a0\u8205\u5504\u8205\u5130\u8205" +
+        "\u4d30\u8205\u4906\u0b2a\u8648\u86f7\u0d01\u0c0a\u0102\ua082" +
+        "\u04fa\u3082\u04f6\u3028\u060a\u2a86\u4886\uf70d\u010c\u0103" +
+        "\u301a\u0414\ud258\ubbe7\ub641\ud196\u4969\u3c88\u70f1\u8c97" +
+        "\u95b1\u8bf3\u0202\u0400\u0482\u04c8\u096a\u4686\uf519\u61da" +
+        "\u1b3b\uebfd\u89b1\u044b\u3bd8\u79a7\ud022\ud880\ud173\ucde1" +
+        "\ud2c1\u2c5d\u8ebb\u6bd4\u46db\ub90b\u04b9\ub091\ud1f3\ud468" +
+        "\u3e93\u2c88\uca5a\u1c54\u5342\u1eca\u8565\ubbbd\ua022\u1ead" +
+        "\ud0bb\u1a8c\u69cf\uf0f4\ucbfb\u488a\ube99\uf190\ue01c\ud87d" +
+        "\u78ca\u9e5c\u82f9\u76ad\u811f\u37d0\u272b\u0481\u500c\u0a27" +
+        "\u08d3\ub637\u3e39\u6db1\ubcba\ue354\u6924\ua9d5\u3555\u20d6" +
+        "\u4c6b\u3189\u5f91\u382c\uf351\u4de2\ubade\u2a14\uea84\u16b6" +
+        "\uf7f7\u36de\ubba6\ue952\u5f5d\u8243\u2318\ucf3d\u8ac8\u33d3" +
+        "\u706c\ue3db\u6619\u7935\u7300\u89b3\u0bcd\uca9f\u0333\ua450" +
+        "\u1be1\u3e42\ub465\uced5\ub055\u5843\uf40f\ua0f2\u6fea\u94fa" +
+        "\ua51e\u4b5d\u93c9\ucb2e\u977e\uafd9\u2a2f\u784b\u0320\u5550" +
+        "\u273f\u469f\uc42b\u2ce7\uedea\u4e0d\u54a5\u1a25\u4fac\ue346" +
+        "\u2102\u7ab6\uea86\u554f\u7706\u8a80\uf6dd\u04f8\u3b37\u005a" +
+        "\u4562\u2ef8\u59f9\u32b7\u31c0\ue7dc\ucbde\ue0e1\u2fd9\u0960" +
+        "\u3e7a\ub4e5\u2a58\u1e2b\uef14\u9a44\u5444\u806d\uc475\u12ab" +
+        "\ucc3d\ua03b\ubd52\ubf1c\ua9a6\u58aa\uee8b\u96c2\ud0c9\ua029" +
+        "\u1db4\ub118\u4807\uecaa\ue182\uabb7\ud9ed\u66c5\u2c80\uc6a6" +
+        "\u3f54\ubc73\u2632\ue1b0\u0d74\u001c\u5740\uc74b\ufadb\u25b4" +
+        "\ua10e\u3191\u69e6\u0861\u452b\u955c\uac56\ud3c4\u86b7\u45f8" +
+        "\u777a\uc336\u8cc7\ud471\u76b6\u11d8\ueb84\u14e4\uf44f\uc9ff" +
+        "\u8929\u0d84\ubcfe\u8cc2\u9d07\u94e6\u1cf9\u19b5\u773a\u012d" +
+        "\u0453\u4ff3\u40f2\ub144\ufc80\u571c\u0e13\uf890\u9fed\u2045" +
+        "\u7baf\ufd88\u4920\u2b86\u491d\uecf8\ua5d9\u1e12\u48c7\u2c84" +
+        "\u3fbe\u4df5\u11ce\u7b81\u83fc\u3efa\u697d\u1f3d\u8d81\u01b0" +
+        "\u0bf1\u9012\u697f\u3b25\u3574\u5286\udded\u5be0\u7e92\u0a02" +
+        "\ua486\ud19b\ue0b5\ua05c\u5ac2\u0ad5\u0d04\ua763\ub5c8\uf7e6" +
+        "\u6e77\u2df3\ub9e7\uda30\ufccb\u7642\u5dc1\udf1f\uc922\uff69" +
+        "\u4471\u4749\u937a\ud77d\u7c0d\u917c\uf2ef\u122c\u13b2\u8943" +
+        "\u33aa\uad59\u86e8\u21c4\ueaa0\uf200\ue5f3\u6da0\ue8ef\uce7e" +
+        "\u37b2\u3ddf\u0480\u08fc\uf89a\ud927\u3f5b\u75d3\ubdfe\u6ebd" +
+        "\ufab1\u9f54\u1c20\u625b\u1391\u2af0\u43ba\u4395\udf22\u299e" +
+        "\uf3bf\u7750\u5f68\u0120\u0ee0\u6960\ud939\u621f\uf845\u0025" +
+        "\ue33c\u7ed9\ueadf\u0005\u6306\u7274\u5e67\ucf7a\uf3c6\u7371" +
+        "\u487b\u79d7\u2142\ubc1a\ubfe4\u3536\u15db\ufe23\u4352\u6321" +
+        "\u329d\uc251\u84c8\ufc0e\uc0ca\u5be6\uf530\u0177\ud9cb\ud132" +
+        "\uf752\u3f26\uda90\ud9cf\u2e46\u3e09\u5d9a\u6902\udb3e\ub06c" +
+        "\u722d\uf498\u3e93\u6cae\u43b5\u535a\u1cd1\uf0b2\u8d80\u9e53" +
+        "\ue02e\uf782\u01ce\u5063\u73d1\u5571\uf0e7\ufa22\u7e48\u0c31" +
+        "\u4642\u29fd\udcab\ue8d4\u7a77\u0880\u4855\u88c7\u7aa5\u0d9c" +
+        "\uf8b7\uc91c\u127d\u2dd7\ude53\u9d3f\u132b\u965c\ubc80\udd97" +
+        "\u87bf\ua0e8\ub2a2\u4e1f\u98fd\u72f3\u16ea\uc415\u5be3\ue8df" +
+        "\u5681\u1f11\u4e3a\uac5d\u1684\u6602\ueb14\u0a96\ufcef\uaebf" +
+        "\u1f2e\ud1a9\u435c\uf4e5\ub6b4\uaae2\u8244\u96a4\u0d3a\u752f" +
+        "\uce21\u1bc9\u219e\uf17b\ud95e\ucd12\u1b0a\ucb85\ub0cd\u4ecb" +
+        "\u6bb4\u5f7c\u2a93\ubb24\u9d7c\u6822\u80cd\u3f54\u78ad\u4fde" +
+        "\ud57f\uec1d\ub54c\u0d78\u5946\u84c1\ua9ad\u0dea\u0292\ub279" +
+        "\u1c76\u817e\ub910\ub1fa\ub1c0\u839d\u9eca\u6f83\u8211\u4112" +
+        "\u440c\u4fbd\u6ef2\u897d\udfa9\ude9e\u1aef\u0f21\u26fb\uaca4" +
+        "\u637e\ub072\u264f\ud24d\u9357\uc801\u0b84\u2d34\ueddf\u6063" +
+        "\udc5d\u90dd\u5c62\ufb48\u8c5e\u7c4e\u3bdb\ub590\u7a75\udbd1" +
+        "\udd78\uc8be\u5915\u7c8b\u8874\u578d\u3116\ub65a\uab8e\ud2ef" +
+        "\u5d35\ubf8b\u2828\u8983\ua790\uedcf\u9698\ue023\u5786\u627b" +
+        "\u9037\u1db7\u900e\u1f45\u0001\u7cf8\u14fd\ue437\u0dfd\ucacc" +
+        "\u5edf\u1742\u7f6e\u612a\ud57d\udca0\u73a4\ud601\uc7f0\uca0e" +
+        "\u5a44\u00b4\u233a\u84f2\u95b5\u5f16\uc291\u04fb\u369d\u6b99" +
+        "\ue127\u493f\u66be\u86e9\u9672\u2849\u64a7\u851b\ue420\u8491" +
+        "\ueb07\u6563\uc753\uc28e\ucad5\uec05\u6920\u8955\u5605\u25f6" +
+        "\u6193\ubee5\u7a1a\ub73d\ucc27\uc8ce\u7179\u57c1\u7a2a\u37c1" +
+        "\ua6c7\u2d8a\u4025\uc97d\u8c9d\u7b4b\u1ad5\uc6d7\u50fc\u246d" +
+        "\u91a9\ua55d\u677a\udc83\u04c7\u3e14\u9950\u420c\udf02\u749b" +
+        "\ude88\u5459\u2074\ua4ae\ud12d\uaf60\uba98\u630d\u313c\u3017" +
+        "\u0609\u2a86\u4886\uf70d\u0109\u1431\u0a1e\u0800\u7400\u6500" +
+        "\u7300\u7430\u2106\u092a\u8648\u86f7\u0d01\u0915\u3114\u0412" +
+        "\u5469\u6d65\u2031\u3330\u3833\u3132\u3431\u3138\u3238\u3082" +
+        "\u03b3\u0609\u2a86\u4886\uf70d\u0107\u06a0\u8203\ua430\u8203" +
+        "\ua002\u0100\u3082\u0399\u0609\u2a86\u4886\uf70d\u0107\u0130" +
+        "\u2806\u0a2a\u8648\u86f7\u0d01\u0c01\u0630\u1a04\u14af\ud4f5" +
+        "\u0ff4\u0ede\u0da0\u6cc5\ufd9d\u3502\uae5e\u4cef\u3102\u0204" +
+        "\u0080\u8203\u6028\ua7e6\u088b\u56b6\uf453\u9747\u68ec\uc064" +
+        "\u2254\u693f\u25c5\uaa39\u3d87\uc97c\uc558\u5194\u7553\ude3a" +
+        "\u4575\u9d85\ud843\u2bd0\ua2e8\u244f\u8593\uac84\u54b4\ubdc6" +
+        "\ucea6\uba1a\ud3da\ua510\uee9d\uaf31\ub5c2\u3329\u0fed\u0e08" +
+        "\u426b\u46fe\udcc5\u0979\ua9ed\u3123\u9a50\ud222\u3fc0\u771a" +
+        "\u6f55\u9664\ud56f\u6b03\u6020\u78a4\u63b2\ue35e\u0816\u43a7" +
+        "\u1909\u52e1\u8183\u1b8d\u9f5b\u19e4\uad73\u8461\ucc86\u3b49" +
+        "\u322e\ue9d9\u3c66\uea22\u091e\u6621\ua8bf\u0169\u72d0\u535e" +
+        "\u77dc\u1002\ubded\u7a91\u6cee\u58fa\uc295\uae8e\ue009\uabe9" +
+        "\u6638\ucaea\u8bbf\uca27\udef5\u2881\u72ec\u8aa5\u582b\u9d6e" +
+        "\u26bb\u3c70\u8bd6\uf5ec\u34ae\ua967\u5bb1\u22cb\u4b74\u0e50" +
+        "\u5062\uc6f7\u7cb4\u58a3\uf43d\u57c0\u9654\u2f9c\u9308\u4546" +
+        "\u6f4a\u37fe\u8d5d\u1465\u8621\u4cd8\u68d6\u0456\u96a4\ud3e2" +
+        "\u76d1\u2675\u7654\u7649\u10e9\u9d0e\u8b04\uffb6\u020a\u2eb4" +
+        "\uf24f\u150e\u7f0d\uf41b\u2c76\u538f\uc2df\u79dc\u0472\u1119" +
+        "\uc148\ue2e8\u1820\ucd45\u08a7\u6bcd\u6eb0\ubd0a\ufff4\uec28" +
+        "\u819b\u2adb\uefc8\ue8f7\ue233\u6535\uc938\u9771\u3681\u87cf" +
+        "\u3a24\u4c71\ue1df\u3e19\u259c\uae5b\u27ed\u8a67\uf3e6\u7af0" +
+        "\u48e1\uc542\uc471\ud8f4\ue317\u46e9\u0b4f\uec45\ua1d3\u2b88" +
+        "\u8a22\udda1\u7c1a\u273c\ua0f7\u8bac\u3771\u28d2\u6ef8\u28d2" +
+        "\ud83c\u196f\ue3fd\u9c79\u4305\u01b8\u3490\u0a91\ue4f3\uebc6" +
+        "\u25a2\u7dd2\u72db\u7531\ucfca\u432f\u2beb\uc649\uf9c5\uc533" +
+        "\u9f3a\ua611\u935c\ubca6\ud293\u54d6\u0dd1\u0aff\u82fb\u2d69" +
+        "\u3da0\u3b33\u0986\u45b3\u3353\ub968\u7348\u454f\u9117\ub3dc" +
+        "\ud7af\u06ca\ua34a\u9357\ue22f\uad3d\u4c76\ub386\ua8d7\u2a90" +
+        "\u6d17\u9321\u7b00\u21e4\u1994\u9d18\u6439\u04c8\u8282\ub269" +
+        "\uf786\u75c6\ua505\u983a\ua075\uffa0\ud662\u6ae5\ub126\u96d1" +
+        "\u9e5e\u346b\ub7ee\ub0a3\u4ee8\ud204\u77ec\u2325\u5da8\ua326" +
+        "\ua018\u0fd8\ue50e\u93cc\ucc40\u2d89\u2ffb\u54e0\u091a\u19fd" +
+        "\u45d7\uc0ab\u77a2\u66ae\u794b\u6644\u21c3\ud782\u1e9e\u53e5" +
+        "\u782e\u55e8\ud44e\u93e8\u379e\u5aa8\u353b\u95de\u7bc1\ucaf3" +
+        "\u5223\ub5e9\uacbb\ub86b\u6014\u0626\ue7ad\ufd93\ue43a\ud864" +
+        "\u1e6d\u14b2\ua12a\u94c5\u2ed9\ua7f7\u14f4\u0cbd\uca3b\u7c21" +
+        "\ua85a\uf834\u6c99\ue1aa\u3832\u2515\u8170\u3c93\u7def\u94fe" +
+        "\u9c3d\u4ab0\u73ed\u6c72\u8b94\ua407\uc719\uad1e\u6306\u4167" +
+        "\u921e\uae53\u3fd4\uf569\u6f0b\u82b0\u0ca6\ud61f\ud526\u23c9" +
+        "\u168d\u4baf\ucc4f\ud8a2\uc64a\ud649\u55e3\u7019\u8f20\u680c" +
+        "\u5581\u2cb1\ub3a4\u3e37\u5fd3\ua3ca\uc115\u979c\uf910\u3797" +
+        "\u05cb\u51d6\u74a4\uc5c0\u597b\uf27f\ud5e2\ue8ac\u4f3d\uc0c3" +
+        "\u9594\u7799\u6876\ub1a3\u059a\uff03\uc2ee\uc8c2\uf224\u3720" +
+        "\u9177\uabdb\u9202\u18d8\uffbe\u0516\u2a76\uedb5\ufe9e\u6d65" +
+        "\u4c35\ue4cb\u75aa\u02be\ud24c\ua482\ufc67\ue4f9\u70c7\u3567" +
+        "\ufc3f\uaa89\ue80a\u6507\u0a65\u4e18\uf919\u071d\u423c\u1756" +
+        "\u30e5\u37f3\u19b3\u10fb\u6c30\u3d30\u2130\u0906\u052b\u0e03" +
+        "\u021a\u0500\u0414\ufd05\u4444\ud347\u673c\u6da4\udb7c\u0733" +
+        "\ud7bf\ud263\uc6b2\u0414\udd17\u155e\u2d4c\u25cb\ua028\u1a23" +
+        "\ub8b0\uf6be\u925f\ude3a\u0202\u0400";
+
+}