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