Collection syntax change because of Sonar
[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                                    return -1;
488                    }
489                    for(int i=0;i<codec.length;++i) {
490                            if(codec[i]==read)return i;
491                    }
492                   // don't give clue in Encryption mode
493                   throw new IOException("Unacceptable Character in Stream");
494            }
495    }
496
497    /**
498     * Generate a 2048 based Key from which we extract our code base
499     * 
500     * @return
501     * @throws IOException
502     */
503    public static byte[] keygen() throws IOException {
504                 byte inkey[] = new byte[0x600];
505                 new SecureRandom().nextBytes(inkey);
506                 ByteArrayOutputStream baos = new ByteArrayOutputStream(0x800);
507                 base64url.encode(new ByteArrayInputStream(inkey), baos);
508                 return baos.toByteArray();
509    }
510    
511    // A class allowing us to be less predictable about significant digits (i.e. not picking them up from the
512    // beginning, and not picking them up in an ordered row.  Gives a nice 2048 with no visible patterns.
513    private class Obtain {
514            private int last;
515            private int skip;
516            private int length;
517            private byte[] key;
518   
519            private Obtain(Symm b64, byte[] key) {
520                    skip = Math.abs(key[key.length-13]%key.length);
521                    if((key.length&0x1) == (skip&0x1)) { // if both are odd or both are even
522                            ++skip;
523                    }
524                    length = b64.codeset.length;
525                    last = 17+length%59; // never start at beginning
526                    this.key = key;
527            }
528            
529            private int next() {
530                    return Math.abs(key[(++last*skip)%key.length])%length;
531            }
532    };
533   
534    /**
535     * Obtain a Symm from "keyfile" (Config.KEYFILE) property
536     * 
537     * @param acesss
538     * @return
539  * @throws IOException 
540  * @throws CadiException 
541     */
542    public static Symm obtain(Access access) throws CadiException {
543                 String keyfile = access.getProperty(Config.CADI_KEYFILE,null);
544                 if(keyfile!=null) {
545                         Symm symm = Symm.baseCrypt();
546
547                         File file = new File(keyfile);
548                         try {
549                                 access.log(Level.INIT, Config.CADI_KEYFILE,"points to",file.getCanonicalPath());
550                         } catch (IOException e1) {
551                                 access.log(Level.INIT, Config.CADI_KEYFILE,"points to",file.getAbsolutePath());
552                         }
553                         if(file.exists()) {
554                                 try {
555                                         FileInputStream fis = new FileInputStream(file);
556                                         try {
557                                                 symm = Symm.obtain(fis);
558                                         } finally {
559                                                 try {
560                                                    fis.close();
561                                                 } catch (IOException e) {
562                                                 }
563                                         }
564                                 } catch (IOException e) {
565                                         access.log(e, "Cannot load keyfile");
566                                 }
567                         } else {
568                                 String filename;
569                                 try {
570                                         filename = file.getCanonicalPath();
571                                 } catch (IOException e) {
572                                         filename = file.getAbsolutePath();
573                                 }
574                                 throw new CadiException("ERROR: " + filename + " does not exist!");
575                         }
576                         return symm;
577                 } else {
578                         try {
579                                 return internalOnly();
580                         } catch (IOException e) {
581                                 throw new CadiException(e);
582                         }
583                 }
584    }
585   /**
586    *  Create a new random key 
587    */
588   public Symm obtain() throws IOException {
589                 byte inkey[] = new byte[0x800];
590                 new SecureRandom().nextBytes(inkey);
591                 return obtain(inkey);
592   }
593   
594   /**
595    * Obtain a Symm from 2048 key from a String
596    * 
597    * @param key
598    * @return
599    * @throws IOException
600    */
601   public static Symm obtain(String key) throws IOException {
602           return obtain(new ByteArrayInputStream(key.getBytes()));
603   }
604   
605   /**
606    * Obtain a Symm from 2048 key from a Stream
607    * 
608    * @param is
609    * @return
610    * @throws IOException
611    */
612   public static Symm obtain(InputStream is) throws IOException {
613           ByteArrayOutputStream baos = new ByteArrayOutputStream();
614           try {
615                   base64url.decode(is, baos);
616           } catch (IOException e) {
617                   // don't give clue
618                   throw new IOException("Invalid Key");
619           }
620           byte[] bkey = baos.toByteArray();
621           if(bkey.length<0x88) { // 2048 bit key
622                   throw new IOException("Invalid key");
623           }
624           return baseCrypt().obtain(bkey);
625   }
626
627   /**
628    * Convenience for picking up Keyfile
629    * 
630    * @param f
631    * @return
632    * @throws IOException
633    */
634   public static Symm obtain(File f) throws IOException {
635           FileInputStream fis = new FileInputStream(f);
636           try {
637                   return obtain(fis);
638           } finally {
639                   fis.close();
640           }
641   }
642   /**
643    * Decrypt into a String
644    *
645    *  Convenience method
646    * 
647    * @param password
648    * @return
649    * @throws IOException
650    */
651   public String enpass(String password) throws IOException {
652           ByteArrayOutputStream baos = new ByteArrayOutputStream();
653           enpass(password,baos);
654           return new String(baos.toByteArray());
655   }
656
657   /**
658    * Create an encrypted password, making sure that even short passwords have a minimum length.
659    * 
660    * @param password
661    * @param os
662    * @throws IOException
663    */
664   public void enpass(final String password, final OutputStream os) throws IOException {
665                 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
666                 DataOutputStream dos = new DataOutputStream(baos);
667                 byte[] bytes = password.getBytes();
668                 if(this.getClass().getSimpleName().startsWith("base64")) { // don't expose randomization
669                         dos.write(bytes);
670                 } else {
671                         
672                         Random r = new SecureRandom();
673                         int start = 0;
674                         byte b;
675                         for(int i=0;i<3;++i) {
676                                 dos.writeByte(b=(byte)r.nextInt());
677                                 start+=Math.abs(b);
678                         }
679                         start%=0x7;
680                         for(int i=0;i<start;++i) {
681                                 dos.writeByte(r.nextInt());
682                         }
683                         dos.writeInt((int)System.currentTimeMillis());
684                         int minlength = Math.min(0x9,bytes.length);
685                         dos.writeByte(minlength); // expect truncation
686                         if(bytes.length<0x9) {
687                                 for(int i=0;i<bytes.length;++i) {
688                                         dos.writeByte(r.nextInt());
689                                         dos.writeByte(bytes[i]);
690                                 }
691                                 // make sure it's long enough
692                                 for(int i=bytes.length;i<0x9;++i) {
693                                         dos.writeByte(r.nextInt());
694                                 }
695                         } else {
696                                 dos.write(bytes);
697                         }
698                 }
699                 
700                 // 7/21/2016 Jonathan add AES Encryption to the mix
701                 try {
702                         exec(new SyncExec<Void>() {
703                                 @Override
704                                 public Void exec(Encryption enc) throws Exception {
705                                         CipherInputStream cis = enc.inputStream(new ByteArrayInputStream(baos.toByteArray()), true);
706                                         try {
707                                                 encode(cis,os);
708                                         } finally {
709                                                 os.flush();
710                                                 cis.close();
711                                         }
712                                         return null;
713                                 }
714                         });
715                 } catch (IOException e) {
716                         throw e;
717                 } catch (Exception e) {
718                         throw new IOException(e);
719                 }
720         }
721
722   /**
723    * Decrypt a password into a String
724    * 
725    * Convenience method
726    * 
727    * @param password
728    * @return
729    * @throws IOException
730    */
731   public String depass(String password) throws IOException {
732           if(password==null)return null;
733           ByteArrayOutputStream baos = new ByteArrayOutputStream();
734           depass(password,baos);
735           return new String(baos.toByteArray());
736   }
737   
738   /**
739    * Decrypt a password
740    * 
741    * Skip Symm.ENC
742    * 
743    * @param password
744    * @param os
745    * @return
746    * @throws IOException
747    */
748   public long depass(final String password, final OutputStream os) throws IOException {
749           int offset = password.startsWith(ENC)?4:0;
750           final ByteArrayOutputStream baos = new ByteArrayOutputStream();
751           final ByteArrayInputStream bais =  new ByteArrayInputStream(password.getBytes(),offset,password.length()-offset);
752           try {
753                 exec(new SyncExec<Void>() {
754                         @Override
755                         public Void exec(Encryption enc) throws IOException {
756                                   CipherOutputStream cos = enc.outputStream(baos, false);
757                                   decode(bais,cos);
758                                   cos.close(); // flush
759                                   return null;
760                         }
761                   });
762                 } catch (IOException e) {
763                         throw e;
764                 } catch (Exception e) {
765                         throw new IOException(e);
766                 }
767
768           byte[] bytes = baos.toByteArray();
769           DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
770           long time;
771           if(this.getClass().getSimpleName().startsWith("base64")) { // don't expose randomization
772                   os.write(bytes);
773                   time = 0L;
774           } else {
775                   int start=0;
776                   for(int i=0;i<3;++i) {
777                           start+=Math.abs(dis.readByte());
778                   }
779                   start%=0x7;
780                   for(int i=0;i<start;++i) {
781                           dis.readByte();
782                   }
783                   time = (dis.readInt() & 0xFFFF)|(System.currentTimeMillis()&0xFFFF0000);
784                   int minlength = dis.readByte();
785                   if(minlength<0x9){
786                         DataOutputStream dos = new DataOutputStream(os);
787                         for(int i=0;i<minlength;++i) {
788                                 dis.readByte();
789                                 dos.writeByte(dis.readByte());
790                         }
791                   } else {
792                           int pre =((Byte.SIZE*3+Integer.SIZE+Byte.SIZE)/Byte.SIZE)+start; 
793                           os.write(bytes, pre, bytes.length-pre);
794                   }
795           }
796           return time;
797   }
798
799   public static String randomGen(int numBytes) {
800           return randomGen(passChars,numBytes);  
801   }
802   
803   public static String randomGen(char[] chars ,int numBytes) {
804             int rint;
805             StringBuilder sb = new StringBuilder(numBytes);
806             for(int i=0;i<numBytes;++i) {
807                 rint = random.nextInt(chars.length);
808                 sb.append(chars[rint]);
809             }
810             return sb.toString();
811   }
812   // Internal mechanism for helping to randomize placement of characters within a Symm codeset
813   // Based on an incoming data stream (originally created randomly, but can be recreated within 
814   // 2048 key), go after a particular place in the new codeset.  If that codeset spot is used, then move
815   // right or left (depending on iteration) to find the next available slot.  In this way, key generation 
816   // is speeded up by only enacting N iterations, but adds a spreading effect of the random number stream, so that keyset is also
817   // shuffled for a good spread. It is, however, repeatable, given the same number set, allowing for 
818   // quick recreation when the official stream is actually obtained.
819   public Symm obtain(byte[] key) throws IOException {
820           int filled = codeset.length;
821           char[] seq = new char[filled];
822           int end = filled--;
823
824           boolean right = true;
825           int index;
826           Obtain o = new Obtain(this,key);
827
828           while(filled>=0) {
829                   index = o.next();
830                   if(index<0 || index>=codeset.length) {
831                           System.out.println("uh, oh");
832                   }
833                   if(right) { // alternate going left or right to find the next open slot (keeps it from taking too long to hit something) 
834                           for(int j=index;j<end;++j) {
835                                   if(seq[j]==0) {
836                                           seq[j]=codeset[filled];
837                                           --filled;
838                                           break;
839                                   }
840                           }
841                           right = false;
842                   } else {
843                           for(int j=index;j>=0;--j) {
844                                   if(seq[j]==0) {
845                                           seq[j]=codeset[filled];
846                                           --filled;
847                                           break;
848                                   }
849                           }
850                           right = true;
851                   }
852           }
853           Symm newSymm = new Symm(seq,this);
854           // Set the KeyBytes
855           try {
856                   newSymm.keyBytes = new byte[AES.AES_KEY_SIZE/8];
857                   int offset = (Math.abs(key[(47%key.length)])+137)%(key.length-newSymm.keyBytes.length);
858                   for(int i=0;i<newSymm.keyBytes.length;++i) {
859                           newSymm.keyBytes[i] = key[i+offset];
860                   }
861           } catch (Exception e) {
862                   throw new IOException(e);
863           }
864
865           return newSymm;
866   }
867   
868   /** 
869    * This Symm is generated for internal JVM use.  It has no external keyfile, but can be used
870    * for securing Memory, as it remains the same ONLY of the current JVM
871    * @return
872  * @throws IOException 
873    */
874   public static synchronized Symm internalOnly() throws IOException {
875           if(internalOnly==null) {
876                   ByteArrayInputStream baos = new ByteArrayInputStream(keygen());
877                   try {
878                           internalOnly = Symm.obtain(baos);
879                   } finally {
880                           baos.close();
881                   }
882           }
883           return internalOnly;
884   }
885 }