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