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