Update AAF Version 1.0.0
[aaf/cadi.git] / core / src / main / java / com / att / cadi / Symm.java
1 /*******************************************************************************\r
2  * ============LICENSE_START====================================================\r
3  * * org.onap.aaf\r
4  * * ===========================================================================\r
5  * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.\r
6  * * ===========================================================================\r
7  * * Licensed under the Apache License, Version 2.0 (the "License");\r
8  * * you may not use this file except in compliance with the License.\r
9  * * You may obtain a copy of the License at\r
10  * * \r
11  *  *      http://www.apache.org/licenses/LICENSE-2.0\r
12  * * \r
13  *  * Unless required by applicable law or agreed to in writing, software\r
14  * * distributed under the License is distributed on an "AS IS" BASIS,\r
15  * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
16  * * See the License for the specific language governing permissions and\r
17  * * limitations under the License.\r
18  * * ============LICENSE_END====================================================\r
19  * *\r
20  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
21  * *\r
22  ******************************************************************************/\r
23 package com.att.cadi;\r
24 \r
25 import java.io.ByteArrayInputStream;\r
26 import java.io.ByteArrayOutputStream;\r
27 import java.io.DataInputStream;\r
28 import java.io.DataOutputStream;\r
29 import java.io.File;\r
30 import java.io.FileInputStream;\r
31 import java.io.IOException;\r
32 import java.io.InputStream;\r
33 import java.io.OutputStream;\r
34 import java.security.SecureRandom;\r
35 import java.util.ArrayList;\r
36 import java.util.Random;\r
37 \r
38 import javax.crypto.CipherInputStream;\r
39 import javax.crypto.CipherOutputStream;\r
40 \r
41 import com.att.cadi.Access.Level;\r
42 import com.att.cadi.config.Config;\r
43 \r
44 /**\r
45  * Key Conversion, primarily "Base64"\r
46  * \r
47  * Base64 is required for "Basic Authorization", which is an important part of the overall CADI Package.\r
48  * \r
49  * Note: This author found that there is not a "standard" library for Base64 conversion within Java.  \r
50  * The source code implementations available elsewhere were surprisingly inefficient, requiring, for \r
51  * instance, multiple string creation, on a transaction pass.  Integrating other packages that might be\r
52  * efficient enough would put undue Jar File Dependencies given this Framework should have none-but-Java \r
53  * dependencies.\r
54  * \r
55  * The essential algorithm is good for a symmetrical key system, as Base64 is really just\r
56  * a symmetrical key that everyone knows the values.  \r
57  * \r
58  * This code is quite fast, taking about .016 ms for encrypting, decrypting and even .08 for key \r
59  * generation. The speed quality, especially of key generation makes this a candidate for a short term token \r
60  * used for identity.\r
61  * \r
62  * It may be used to easily avoid placing Clear-Text passwords in configurations, etc. and contains \r
63  * supporting functions such as 2048 keyfile generation (see keygen).  This keyfile should, of course, \r
64  * be set to "400" (Unix) and protected as any other mechanism requires. \r
65  * \r
66  * However, this algorithm has not been tested against hackers.  Until such a time, utilize more tested\r
67  * packages to protect Data, especially sensitive data at rest (long term). \r
68  *\r
69  */\r
70 public class Symm {\r
71         private static final byte[] DOUBLE_EQ = new byte[] {'=','='}; \r
72         public static final String ENC = "enc:";\r
73         private static final SecureRandom random = new SecureRandom();\r
74         \r
75         public final char[] codeset;\r
76         private final int splitLinesAt;\r
77         private final String encoding;\r
78         private final Convert convert;\r
79         private final boolean endEquals;\r
80         //Note: AES Encryption is not Thread Safe.  It is Synchronized\r
81         private static AES aes = null;  // only initialized from File, and only if needed for Passwords\r
82         \r
83         /**\r
84          * This is the standard base64 Key Set.\r
85          * RFC 2045\r
86          */\r
87         public static final Symm base64 = new Symm(\r
88                         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray()\r
89                         ,76, Config.UTF_8,true);\r
90 \r
91         public static final Symm base64noSplit = new Symm(\r
92                         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray()\r
93                         ,Integer.MAX_VALUE, Config.UTF_8,true);\r
94 \r
95         /**\r
96          * This is the standard base64 set suitable for URLs and Filenames\r
97          * RFC 4648\r
98          */\r
99         public static final Symm base64url = new Symm(\r
100                         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toCharArray()\r
101                         ,76, Config.UTF_8,true);\r
102 \r
103         /**\r
104          * A Password set, using US-ASCII\r
105          * RFC 4648\r
106          */\r
107         public static final Symm encrypt = new Symm(base64url.codeset,1024, "US-ASCII", false);\r
108 \r
109         /**\r
110          * A typical set of Password Chars\r
111          * Note, this is too large to fit into the algorithm. Only use with PassGen\r
112          */\r
113         private static char passChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+!@#$%^&*(){}[]?:;,.".toCharArray();\r
114                         \r
115 \r
116 \r
117         /**\r
118          * Use this to create special case Case Sets and/or Line breaks\r
119          * \r
120          * If you don't know why you need this, use the Singleton Method\r
121          * \r
122          * @param codeset\r
123          * @param split\r
124          */\r
125         public Symm(char[] codeset, int split, String charset, boolean useEndEquals) {\r
126                 this.codeset = codeset;\r
127                 splitLinesAt = split;\r
128                 encoding = charset;\r
129                 endEquals = useEndEquals;\r
130                 char prev = 0, curr=0, first = 0;\r
131                 int offset=Integer.SIZE; // something that's out of range for integer array\r
132                 \r
133                 // There can be time efficiencies gained when the underlying keyset consists mainly of ordered \r
134                 // data (i.e. abcde...).  Therefore, we'll quickly analyze the keyset.  If it proves to have\r
135                 // too much entropy, the "Unordered" algorithm, which is faster in such cases is used.\r
136                 ArrayList<int[]> la = new ArrayList<int[]>();\r
137                 for(int i=0;i<codeset.length;++i) {\r
138                         curr = codeset[i];\r
139                         if(prev+1==curr) { // is next character in set\r
140                                 prev = curr;\r
141                         } else {\r
142                                 if(offset!=Integer.SIZE) { // add previous range \r
143                                         la.add(new int[]{first,prev,offset});\r
144                                 }\r
145                                 first = prev = curr;\r
146                                 offset = curr-i;\r
147                         }\r
148                 }\r
149                 la.add(new int[]{first,curr,offset});\r
150                 if(la.size()>codeset.length/3) {\r
151                         convert = new Unordered(codeset);\r
152                 } else { // too random to get speed enhancement from range algorithm\r
153                         int[][] range = new int[la.size()][];\r
154                         la.toArray(range);\r
155                         convert = new Ordered(range);\r
156                 }\r
157         }\r
158         \r
159         public Symm copy(int lines) {\r
160                 return new Symm(codeset,lines,encoding,endEquals);\r
161         }\r
162         \r
163         // Only used by keygen, which is intentionally randomized. Therefore, always use unordered\r
164         private  Symm(char[] codeset, Symm parent) {\r
165                 this.codeset = codeset;\r
166                 splitLinesAt = parent.splitLinesAt;\r
167                 endEquals = parent.endEquals;\r
168                 encoding = parent.encoding;\r
169                 convert = new Unordered(codeset);\r
170         }\r
171 \r
172         /**\r
173          * Obtain the base64() behavior of this class, for use in standard BASIC AUTH mechanism, etc.\r
174          * @return\r
175          */\r
176         @Deprecated\r
177         public static final Symm base64() {\r
178                 return base64;\r
179         }\r
180 \r
181         /**\r
182          * Obtain the base64() behavior of this class, for use in standard BASIC AUTH mechanism, etc.  \r
183          * No Line Splitting\r
184          * @return\r
185          */\r
186         @Deprecated\r
187         public static final Symm base64noSplit() {\r
188                 return base64noSplit;\r
189         }\r
190 \r
191         /**\r
192          * Obtain the base64 "URL" behavior of this class, for use in File Names, etc. (no "/")\r
193          */\r
194         @Deprecated\r
195         public static final Symm base64url() {\r
196                 return base64url;\r
197         }\r
198 \r
199         /**\r
200          * Obtain a special ASCII version for Scripting, with base set of base64url use in File Names, etc. (no "/")\r
201          */\r
202         public static final Symm baseCrypt() {\r
203                 return encrypt;\r
204         }\r
205 \r
206         /*\r
207          *  Note: AES Encryption is NOT thread-safe.  Must surround entire use with synchronized\r
208          */\r
209         private synchronized void exec(AESExec exec) throws IOException {\r
210                 if(aes == null) {\r
211                         try {\r
212                                 byte[] bytes = new byte[AES.AES_KEY_SIZE/8];\r
213                                 int offset = (Math.abs(codeset[0])+47)%(codeset.length-bytes.length);\r
214                                 for(int i=0;i<bytes.length;++i) {\r
215                                         bytes[i] = (byte)codeset[i+offset];\r
216                                 }\r
217                                 aes = new AES(bytes,0,bytes.length);\r
218                         } catch (Exception e) {\r
219                                 throw new IOException(e);\r
220                         }\r
221                 }\r
222                 exec.exec(aes);\r
223         }\r
224         \r
225         private static interface AESExec {\r
226                 public void exec(AES aes) throws IOException;\r
227         }\r
228         \r
229     public byte[] encode(byte[] toEncrypt) throws IOException {\r
230                 ByteArrayOutputStream baos = new ByteArrayOutputStream((int)(toEncrypt.length*1.25));\r
231                 encode(new ByteArrayInputStream(toEncrypt),baos);\r
232                 return baos.toByteArray();\r
233         }\r
234 \r
235     public byte[] decode(byte[] encrypted) throws IOException {\r
236                 ByteArrayOutputStream baos = new ByteArrayOutputStream((int)(encrypted.length*1.25));\r
237                 decode(new ByteArrayInputStream(encrypted),baos);\r
238                 return baos.toByteArray();\r
239         }\r
240 \r
241         /**\r
242      *  Helper function for String API of "Encode"\r
243      *  use "getBytes" with appropriate char encoding, etc.\r
244      *  \r
245      * @param str\r
246      * @return\r
247      * @throws IOException\r
248      */\r
249     public String encode(String str) throws IOException {\r
250         byte[] array;\r
251         try { \r
252                 array = str.getBytes(encoding);\r
253         } catch (IOException e) {\r
254                 array = str.getBytes(); // take default\r
255         }\r
256         // Calculate expected size to avoid any buffer expansion copies within the ByteArrayOutput code\r
257         ByteArrayOutputStream baos = new ByteArrayOutputStream((int)(array.length*1.363)); // account for 4 bytes for 3 and a byte or two more\r
258         \r
259         encode(new ByteArrayInputStream(array),baos);\r
260         return baos.toString(encoding);\r
261     }\r
262     \r
263     /**\r
264      * Helper function for the String API of "Decode"\r
265      * use "getBytes" with appropriate char encoding, etc.\r
266      * @param str\r
267      * @return\r
268      * @throws IOException\r
269      */\r
270     public String decode(String str) throws IOException {\r
271         byte[] array;\r
272         try { \r
273                 array = str.getBytes(encoding);\r
274         } catch (IOException e) {\r
275                 array = str.getBytes(); // take default\r
276         }\r
277         // Calculate expected size to avoid any buffer expansion copies within the ByteArrayOutput code\r
278         ByteArrayOutputStream baos = new ByteArrayOutputStream((int)(array.length*.76)); // Decoding is 3 bytes for 4.  Allocate slightly more than 3/4s\r
279         decode(new ByteArrayInputStream(array), baos);\r
280         return baos.toString(encoding);\r
281         }\r
282 \r
283         /**\r
284      * Convenience Function\r
285      * \r
286      * encode String into InputStream and call encode(InputStream, OutputStream)\r
287      * \r
288      * @param string\r
289      * @param out\r
290      * @throws IOException\r
291      */\r
292         public void encode(String string, OutputStream out) throws IOException {\r
293                 encode(new ByteArrayInputStream(string.getBytes()),out);\r
294         }\r
295 \r
296         /**\r
297          * Convenience Function\r
298          * \r
299          * encode String into InputStream and call decode(InputStream, OutputStream)\r
300          * \r
301          * @param string\r
302          * @param out\r
303          * @throws IOException\r
304          */\r
305         public void decode(String string, OutputStream out) throws IOException {\r
306                 decode(new ByteArrayInputStream(string.getBytes()),out);\r
307         }\r
308 \r
309     public void encode(InputStream is, OutputStream os, byte[] prefix) throws IOException {\r
310         os.write(prefix);\r
311         encode(is,os);\r
312     }\r
313 \r
314         /** \r
315      * encode InputStream onto Output Stream\r
316      * \r
317      * @param is\r
318      * @param estimate\r
319      * @return\r
320      * @throws IOException\r
321      */\r
322     public void encode(InputStream is, OutputStream os) throws IOException {\r
323         // StringBuilder sb = new StringBuilder((int)(estimate*1.255)); // try to get the right size of StringBuilder from start.. slightly more than 1.25 times \r
324         int prev=0;\r
325         int read, idx=0, line=0;\r
326         boolean go;\r
327         do {\r
328                 read = is.read();\r
329                 if(go = read>=0) {\r
330                         if(line>=splitLinesAt) {\r
331                                 os.write('\n');\r
332                                 line = 0;\r
333                         }\r
334                         switch(++idx) { // 1 based reading, slightly faster ++\r
335                                 case 1: // ptr is the first 6 bits of read\r
336                                         os.write(codeset[read>>2]);\r
337                                         prev = read;\r
338                                         break;\r
339                                 case 2: // ptr is the last 2 bits of prev followed by the first 4 bits of read\r
340                                         os.write(codeset[((prev & 0x03)<<4) | (read>>4)]);\r
341                                         prev = read;\r
342                                         break;\r
343                                 default: //(3+) \r
344                                                 // Char 1 is last 4 bits of prev plus the first 2 bits of read\r
345                                             // Char 2 is the last 6 bits of read\r
346                                         os.write(codeset[(((prev & 0xF)<<2) | (read>>6))]);\r
347                                         if(line==splitLinesAt) { // deal with line splitting for two characters\r
348                                                 os.write('\n');\r
349                                                 line=0;\r
350                                         }\r
351                                         os.write(codeset[(read & 0x3F)]);\r
352                                         ++line;\r
353                                         idx = 0;\r
354                                         prev = 0;\r
355                         }\r
356                         ++line;\r
357                 } else { // deal with any remaining bits from Prev, then pad\r
358                         switch(idx) {\r
359                                 case 1: // just the last 2 bits of prev\r
360                                         os.write(codeset[(prev & 0x03)<<4]);\r
361                                         if(endEquals)os.write(DOUBLE_EQ);\r
362                                         break;\r
363                                 case 2: // just the last 4 bits of prev\r
364                                         os.write(codeset[(prev & 0xF)<<2]);\r
365                                         if(endEquals)os.write('=');\r
366                                         break;\r
367                         }\r
368                         idx = 0;\r
369                 }\r
370                 \r
371         } while(go);\r
372     }\r
373 \r
374     public void decode(InputStream is, OutputStream os, int skip) throws IOException {\r
375         is.skip(skip);\r
376         decode(is,os);\r
377     }\r
378 \r
379     /**\r
380          * Decode InputStream onto OutputStream\r
381          * @param is\r
382          * @param os\r
383          * @throws IOException\r
384          */\r
385     public void decode(InputStream is, OutputStream os) throws IOException {\r
386            int read, idx=0;\r
387            int prev=0, index;\r
388                 while((read = is.read())>=0) {\r
389                         index = convert.convert(read);\r
390                         if(index>=0) {\r
391                         switch(++idx) { // 1 based cases, slightly faster ++\r
392                                 case 1: // index goes into first 6 bits of prev\r
393                                         prev = index<<2; \r
394                                         break;\r
395                                 case 2: // write second 2 bits of into prev, write byte, last 4 bits go into prev\r
396                                         os.write((byte)(prev|(index>>4)));\r
397                                         prev = index<<4;\r
398                                         break;\r
399                                 case 3: // first 4 bits of index goes into prev, write byte, last 2 bits go into prev\r
400                                         os.write((byte)(prev|(index>>2)));\r
401                                         prev = index<<6;\r
402                                         break;\r
403                                 default: // (3+) | prev and last six of index\r
404                                         os.write((byte)(prev|(index&0x3F)));\r
405                                         idx = prev = 0;\r
406                         }\r
407                         }\r
408                 };\r
409                 os.flush();\r
410    }\r
411    \r
412    /**\r
413     * Interface to allow this class to choose which algorithm to find index of character in Key\r
414     *\r
415     */\r
416    private interface Convert {\r
417            public int convert(int read) throws IOException;\r
418    }\r
419 \r
420    /**\r
421     * Ordered uses a range of orders to compare against, rather than requiring the investigation\r
422     * of every character needed.\r
423     *\r
424     */\r
425    private static final class Ordered implements Convert {\r
426            private int[][] range;\r
427            public Ordered(int[][] range) {\r
428                    this.range = range;\r
429            }\r
430            public int convert(int read) throws IOException {\r
431                    switch(read) {\r
432                            case -1: \r
433                            case '=':\r
434                            case '\n': \r
435                                    return -1;\r
436                    }\r
437                    for(int i=0;i<range.length;++i) {\r
438                            if(read >= range[i][0] && read<=range[i][1]) {\r
439                                    return read-range[i][2];\r
440                            }\r
441                    }\r
442                    throw new IOException("Unacceptable Character in Stream");\r
443            }\r
444    }\r
445    \r
446    /**\r
447     * Unordered, i.e. the key is purposely randomized, simply has to investigate each character\r
448     * until we find a match.\r
449     *\r
450     */\r
451    private static final class Unordered implements Convert {\r
452            private char[] codec;\r
453            public Unordered(char[] codec) {\r
454                    this.codec = codec;\r
455            }\r
456            public int convert(int read) throws IOException {\r
457                    switch(read) {\r
458                            case -1: \r
459                            case '=':\r
460                            case '\n': \r
461                                    return -1;\r
462                    }\r
463                    for(int i=0;i<codec.length;++i) {\r
464                            if(codec[i]==read)return i;\r
465                    }\r
466                   // don't give clue in Encryption mode\r
467                   throw new IOException("Unacceptable Character in Stream");\r
468            }\r
469    }\r
470 \r
471    /**\r
472     * Generate a 2048 based Key from which we extract our code base\r
473     * \r
474     * @return\r
475     * @throws IOException\r
476     */\r
477    public byte[] keygen() throws IOException {\r
478                 byte inkey[] = new byte[0x600];\r
479                 new SecureRandom().nextBytes(inkey);\r
480                 ByteArrayOutputStream baos = new ByteArrayOutputStream(0x800);\r
481                 base64url.encode(new ByteArrayInputStream(inkey), baos);\r
482                 return baos.toByteArray();\r
483    }\r
484    \r
485    // A class allowing us to be less predictable about significant digits (i.e. not picking them up from the\r
486    // beginning, and not picking them up in an ordered row.  Gives a nice 2048 with no visible patterns.\r
487    private class Obtain {\r
488            private int last;\r
489            private int skip;\r
490            private int length;\r
491            private byte[] key;\r
492   \r
493            private Obtain(Symm b64, byte[] key) {\r
494                    skip = Math.abs(key[key.length-13]%key.length);\r
495                    if((key.length&0x1) == (skip&0x1)) { // if both are odd or both are even\r
496                            ++skip;\r
497                    }\r
498                    length = b64.codeset.length;\r
499                    last = 17+length%59; // never start at beginning\r
500                    this.key = key;\r
501            }\r
502            \r
503            private int next() {\r
504                    return Math.abs(key[(++last*skip)%key.length])%length;\r
505            }\r
506    };\r
507   \r
508    /**\r
509     * Obtain a Symm from "keyfile" (Config.KEYFILE) property\r
510     * \r
511     * @param acesss\r
512     * @return\r
513     */\r
514    public static Symm obtain(Access access) {\r
515                 Symm symm = Symm.baseCrypt();\r
516 \r
517                 String keyfile = access.getProperty(Config.CADI_KEYFILE,null);\r
518                 if(keyfile!=null) {\r
519                         File file = new File(keyfile);\r
520                         try {\r
521                                 access.log(Level.INIT, Config.CADI_KEYFILE,"points to",file.getCanonicalPath());\r
522                         } catch (IOException e1) {\r
523                                 access.log(Level.INIT, Config.CADI_KEYFILE,"points to",file.getAbsolutePath());\r
524                         }\r
525                         if(file.exists()) {\r
526                                 try {\r
527                                         FileInputStream fis = new FileInputStream(file);\r
528                                         try {\r
529                                                 symm = Symm.obtain(fis);\r
530                                         } finally {\r
531                                                 try {\r
532                                                    fis.close();\r
533                                                 } catch (IOException e) {\r
534                                                 }\r
535                                         }\r
536                                 } catch (IOException e) {\r
537                                         access.log(e, "Cannot load keyfile");\r
538                                 }\r
539                         }\r
540                 }\r
541                 return symm;\r
542    }\r
543   /**\r
544    *  Create a new random key \r
545    */\r
546   public Symm obtain() throws IOException {\r
547                 byte inkey[] = new byte[0x800];\r
548                 new SecureRandom().nextBytes(inkey);\r
549                 return obtain(inkey);\r
550   }\r
551   \r
552   /**\r
553    * Obtain a Symm from 2048 key from a String\r
554    * \r
555    * @param key\r
556    * @return\r
557    * @throws IOException\r
558    */\r
559   public static Symm obtain(String key) throws IOException {\r
560           return obtain(new ByteArrayInputStream(key.getBytes()));\r
561   }\r
562   \r
563   /**\r
564    * Obtain a Symm from 2048 key from a Stream\r
565    * \r
566    * @param is\r
567    * @return\r
568    * @throws IOException\r
569    */\r
570   public static Symm obtain(InputStream is) throws IOException {\r
571           ByteArrayOutputStream baos = new ByteArrayOutputStream();\r
572           try {\r
573                   base64url.decode(is, baos);\r
574           } catch (IOException e) {\r
575                   // don't give clue\r
576                   throw new IOException("Invalid Key");\r
577           }\r
578           byte[] bkey = baos.toByteArray();\r
579           if(bkey.length<0x88) { // 2048 bit key\r
580                   throw new IOException("Invalid key");\r
581           }\r
582           return baseCrypt().obtain(bkey);\r
583   }\r
584 \r
585   /**\r
586    * Convenience for picking up Keyfile\r
587    * \r
588    * @param f\r
589    * @return\r
590    * @throws IOException\r
591    */\r
592   public static Symm obtain(File f) throws IOException {\r
593           FileInputStream fis = new FileInputStream(f);\r
594           try {\r
595                   return obtain(fis);\r
596           } finally {\r
597                   fis.close();\r
598           }\r
599   }\r
600   /**\r
601    * Decrypt into a String\r
602    *\r
603    *  Convenience method\r
604    * \r
605    * @param password\r
606    * @return\r
607    * @throws IOException\r
608    */\r
609   public String enpass(String password) throws IOException {\r
610           ByteArrayOutputStream baos = new ByteArrayOutputStream();\r
611           enpass(password,baos);\r
612           return new String(baos.toByteArray());\r
613   }\r
614 \r
615   /**\r
616    * Create an encrypted password, making sure that even short passwords have a minimum length.\r
617    * \r
618    * @param password\r
619    * @param os\r
620    * @throws IOException\r
621    */\r
622   public void enpass(final String password, final OutputStream os) throws IOException {\r
623                 final ByteArrayOutputStream baos = new ByteArrayOutputStream();\r
624                 DataOutputStream dos = new DataOutputStream(baos);\r
625                 byte[] bytes = password.getBytes();\r
626                 if(this.getClass().getSimpleName().startsWith("base64")) { // don't expose randomization\r
627                         dos.write(bytes);\r
628                 } else {\r
629                         \r
630                         Random r = new SecureRandom();\r
631                         int start = 0;\r
632                         byte b;\r
633                         for(int i=0;i<3;++i) {\r
634                                 dos.writeByte(b=(byte)r.nextInt());\r
635                                 start+=Math.abs(b);\r
636                         }\r
637                         start%=0x7;\r
638                         for(int i=0;i<start;++i) {\r
639                                 dos.writeByte(r.nextInt());\r
640                         }\r
641                         dos.writeInt((int)System.currentTimeMillis());\r
642                         int minlength = Math.min(0x9,bytes.length);\r
643                         dos.writeByte(minlength); // expect truncation\r
644                         if(bytes.length<0x9) {\r
645                                 for(int i=0;i<bytes.length;++i) {\r
646                                         dos.writeByte(r.nextInt());\r
647                                         dos.writeByte(bytes[i]);\r
648                                 }\r
649                                 // make sure it's long enough\r
650                                 for(int i=bytes.length;i<0x9;++i) {\r
651                                         dos.writeByte(r.nextInt());\r
652                                 }\r
653                         } else {\r
654                                 dos.write(bytes);\r
655                         }\r
656                 }\r
657                 \r
658                 // 7/21/2016 jg add AES Encryption to the mix\r
659                 exec(new AESExec() {\r
660                         @Override\r
661                         public void exec(AES aes) throws IOException {\r
662                                 CipherInputStream cis = aes.inputStream(new ByteArrayInputStream(baos.toByteArray()), true);\r
663                                 try {\r
664                                         encode(cis,os);\r
665                                 } finally {\r
666                                         os.flush();\r
667                                         cis.close();\r
668                                 }\r
669                         }\r
670                 });\r
671                 synchronized(ENC) {\r
672                 }\r
673         }\r
674 \r
675   /**\r
676    * Decrypt a password into a String\r
677    * \r
678    * Convenience method\r
679    * \r
680    * @param password\r
681    * @return\r
682    * @throws IOException\r
683    */\r
684   public String depass(String password) throws IOException {\r
685           if(password==null)return null;\r
686           ByteArrayOutputStream baos = new ByteArrayOutputStream();\r
687           depass(password,baos);\r
688           return new String(baos.toByteArray());\r
689   }\r
690   \r
691   /**\r
692    * Decrypt a password\r
693    * \r
694    * Skip Symm.ENC\r
695    * \r
696    * @param password\r
697    * @param os\r
698    * @return\r
699    * @throws IOException\r
700    */\r
701   public long depass(final String password, final OutputStream os) throws IOException {\r
702           int offset = password.startsWith(ENC)?4:0;\r
703           final ByteArrayOutputStream baos = new ByteArrayOutputStream();\r
704           final ByteArrayInputStream bais =  new ByteArrayInputStream(password.getBytes(),offset,password.length()-offset);\r
705           exec(new AESExec() {\r
706                 @Override\r
707                 public void exec(AES aes) throws IOException {\r
708                           CipherOutputStream cos = aes.outputStream(baos, false);\r
709                           decode(bais,cos);\r
710                           cos.close(); // flush\r
711                 }\r
712           });\r
713           byte[] bytes = baos.toByteArray();\r
714           DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));\r
715           long time;\r
716           if(this.getClass().getSimpleName().startsWith("base64")) { // don't expose randomization\r
717                   os.write(bytes);\r
718                   time = 0L;\r
719           } else {\r
720                   int start=0;\r
721                   for(int i=0;i<3;++i) {\r
722                           start+=Math.abs(dis.readByte());\r
723                   }\r
724                   start%=0x7;\r
725                   for(int i=0;i<start;++i) {\r
726                           dis.readByte();\r
727                   }\r
728                   time = (dis.readInt() & 0xFFFF)|(System.currentTimeMillis()&0xFFFF0000);\r
729                   int minlength = dis.readByte();\r
730                   if(minlength<0x9){\r
731                         DataOutputStream dos = new DataOutputStream(os);\r
732                         for(int i=0;i<minlength;++i) {\r
733                                 dis.readByte();\r
734                                 dos.writeByte(dis.readByte());\r
735                         }\r
736                   } else {\r
737                           int pre =((Byte.SIZE*3+Integer.SIZE+Byte.SIZE)/Byte.SIZE)+start; \r
738                           os.write(bytes, pre, bytes.length-pre);\r
739                   }\r
740           }\r
741           return time;\r
742   }\r
743 \r
744   public static String randomGen(int numBytes) {\r
745           return randomGen(passChars,numBytes);  \r
746   }\r
747   \r
748   public static String randomGen(char[] chars ,int numBytes) {\r
749             int rint;\r
750             StringBuilder sb = new StringBuilder(numBytes);\r
751             for(int i=0;i<numBytes;++i) {\r
752                 rint = random.nextInt(chars.length);\r
753                 sb.append(chars[rint]);\r
754             }\r
755             return sb.toString();\r
756   }\r
757   // Internal mechanism for helping to randomize placement of characters within a Symm codeset\r
758   // Based on an incoming data stream (originally created randomly, but can be recreated within \r
759   // 2048 key), go after a particular place in the new codeset.  If that codeset spot is used, then move\r
760   // right or left (depending on iteration) to find the next available slot.  In this way, key generation \r
761   // is speeded up by only enacting N iterations, but adds a spreading effect of the random number stream, so that keyset is also\r
762   // shuffled for a good spread. It is, however, repeatable, given the same number set, allowing for \r
763   // quick recreation when the official stream is actually obtained.\r
764   public Symm obtain(byte[] key) throws IOException {\r
765           try {\r
766                 byte[] bytes = new byte[AES.AES_KEY_SIZE/8];\r
767                 int offset = (Math.abs(key[(47%key.length)])+137)%(key.length-bytes.length);\r
768                 for(int i=0;i<bytes.length;++i) {\r
769                         bytes[i] = key[i+offset];\r
770                 }\r
771 \r
772                 aes = new AES(bytes,0,bytes.length);\r
773           } catch (Exception e) {\r
774                   throw new IOException(e);\r
775           }\r
776                 int filled = codeset.length;\r
777                 char[] seq = new char[filled];\r
778                 int end = filled--;\r
779                 \r
780                 boolean right = true;\r
781                 int index;\r
782                 Obtain o = new Obtain(this,key);\r
783                 \r
784                 while(filled>=0) {\r
785                         index = o.next();\r
786                         if(index<0 || index>=codeset.length) {\r
787                                 System.out.println("uh, oh");\r
788                         }\r
789                         if(right) { // alternate going left or right to find the next open slot (keeps it from taking too long to hit something) \r
790                                 for(int j=index;j<end;++j) {\r
791                                         if(seq[j]==0) {\r
792                                                 seq[j]=codeset[filled];\r
793                                                 --filled;\r
794                                                 break;\r
795                                         }\r
796                                 }\r
797                                 right = false;\r
798                         } else {\r
799                                 for(int j=index;j>=0;--j) {\r
800                                         if(seq[j]==0) {\r
801                                                 seq[j]=codeset[filled];\r
802                                                 --filled;\r
803                                                 break;\r
804                                         }\r
805                                 }\r
806                                 right = true;\r
807                         }\r
808                 }\r
809                 return new Symm(seq,this);\r
810         }\r
811 }\r