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