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