001    /*
002     * $HeadURL: http://juliusdavies.ca/svn/not-yet-commons-ssl/tags/commons-ssl-0.3.9/src/java/org/apache/commons/ssl/OpenSSL.java $
003     * $Revision: 121 $
004     * $Date: 2007-11-13 21:26:57 -0800 (Tue, 13 Nov 2007) $
005     *
006     * ====================================================================
007     * Licensed to the Apache Software Foundation (ASF) under one
008     * or more contributor license agreements.  See the NOTICE file
009     * distributed with this work for additional information
010     * regarding copyright ownership.  The ASF licenses this file
011     * to you under the Apache License, Version 2.0 (the
012     * "License"); you may not use this file except in compliance
013     * with the License.  You may obtain a copy of the License at
014     *
015     *   http://www.apache.org/licenses/LICENSE-2.0
016     *
017     * Unless required by applicable law or agreed to in writing,
018     * software distributed under the License is distributed on an
019     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
020     * KIND, either express or implied.  See the License for the
021     * specific language governing permissions and limitations
022     * under the License.
023     * ====================================================================
024     *
025     * This software consists of voluntary contributions made by many
026     * individuals on behalf of the Apache Software Foundation.  For more
027     * information on the Apache Software Foundation, please see
028     * <http://www.apache.org/>.
029     *
030     */
031    
032    package org.apache.commons.ssl;
033    
034    import org.apache.commons.ssl.util.Hex;
035    
036    import javax.crypto.Cipher;
037    import javax.crypto.CipherInputStream;
038    import java.io.ByteArrayInputStream;
039    import java.io.FileInputStream;
040    import java.io.IOException;
041    import java.io.InputStream;
042    import java.security.GeneralSecurityException;
043    import java.security.MessageDigest;
044    import java.security.NoSuchAlgorithmException;
045    import java.security.SecureRandom;
046    import java.util.StringTokenizer;
047    
048    /**
049     * Class for encrypting or decrypting data with a password (PBE - password
050     * based encryption).  Compatible with "openssl enc" unix utility.  An OpenSSL
051     * compatible cipher name must be specified along with the password (try "man enc" on a
052     * unix box to see what's possible).  Some examples:
053     * <ul><li>des, des3, des-ede3-cbc
054     * <li>aes128, aes192, aes256, aes-256-cbc
055     * <li>rc2, rc4, bf</ul>
056     * <pre>
057     * <em style="color: green;">// Encrypt!</em>
058     * byte[] encryptedData = OpenSSL.encrypt( "des3", password, data );
059     * </pre>
060     * <p/>
061     * If you want to specify a raw key and iv directly (without using PBE), use
062     * the methods that take byte[] key, byte[] iv.  Those byte[] arrays can be
063     * the raw binary, or they can be ascii (hex representation: '0' - 'F').  If
064     * you want to use PBE to derive the key and iv, then use the methods that
065     * take char[] password.
066     * <p/>
067     * This class is able to decrypt files encrypted with "openssl" unix utility.
068     * <p/>
069     * The "openssl" unix utility is able to decrypt files encrypted by this class.
070     * <p/>
071     * This class is also able to encrypt and decrypt its own files.
072     *
073     * @author <a href="mailto:juliusdavies@cucbc.com">juliusdavies@gmail.com</a>
074     * @since 18-Oct-2007
075     */
076    public class OpenSSL {
077    
078    
079        /**
080         * Decrypts data using a password and an OpenSSL compatible cipher
081         * name.
082         *
083         * @param cipher    The OpenSSL compatible cipher to use (try "man enc" on a
084         *                  unix box to see what's possible).  Some examples:
085         *                  <ul><li>des, des3, des-ede3-cbc
086         *                  <li>aes128, aes192, aes256, aes-256-cbc
087         *                  <li>rc2, rc4, bf</ul>
088         * @param pwd       password to use for this PBE decryption
089         * @param encrypted byte array to decrypt.  Can be raw, or base64.
090         * @return decrypted bytes
091         * @throws IOException              problems with encrypted bytes (unlikely!)
092         * @throws GeneralSecurityException problems decrypting
093         */
094        public static byte[] decrypt(String cipher, char[] pwd, byte[] encrypted)
095            throws IOException, GeneralSecurityException {
096            ByteArrayInputStream in = new ByteArrayInputStream(encrypted);
097            InputStream decrypted = decrypt(cipher, pwd, in);
098            return Util.streamToBytes(decrypted);
099        }
100    
101        /**
102         * Decrypts data using a password and an OpenSSL compatible cipher
103         * name.
104         *
105         * @param cipher    The OpenSSL compatible cipher to use (try "man enc" on a
106         *                  unix box to see what's possible).  Some examples:
107         *                  <ul><li>des, des3, des-ede3-cbc
108         *                  <li>aes128, aes192, aes256, aes-256-cbc
109         *                  <li>rc2, rc4, bf</ul>
110         * @param pwd       password to use for this PBE decryption
111         * @param encrypted InputStream to decrypt.  Can be raw, or base64.
112         * @return decrypted bytes as an InputStream
113         * @throws IOException              problems with InputStream
114         * @throws GeneralSecurityException problems decrypting
115         */
116        public static InputStream decrypt(String cipher, char[] pwd,
117                                          InputStream encrypted)
118            throws IOException, GeneralSecurityException {
119            CipherInfo cipherInfo = lookup(cipher);
120            boolean salted = false;
121    
122            // First 16 bytes of raw binary will hopefully be OpenSSL's
123            // "Salted__[8 bytes of hex]" thing.  Might be in Base64, though.
124            byte[] saltLine = Util.streamToBytes(encrypted, 16);
125            if (saltLine.length <= 0) {
126                throw new IOException("encrypted InputStream is empty");
127            }
128            String firstEightBytes = "";
129            if (saltLine.length >= 8) {
130                firstEightBytes = new String(saltLine, 0, 8);
131            }
132            if ("SALTED__".equalsIgnoreCase(firstEightBytes)) {
133                salted = true;
134            } else {
135                // Maybe the reason we didn't find the salt is because we're in
136                // base64.
137                if (Base64.isArrayByteBase64(saltLine)) {
138                    InputStream head = new ByteArrayInputStream(saltLine);
139                    // Need to put that 16 byte "saltLine" back into the Stream.
140                    encrypted = new ComboInputStream(head, encrypted);
141                    encrypted = new Base64InputStream(encrypted, true);
142                    saltLine = Util.streamToBytes(encrypted, 16);
143    
144                    if (saltLine.length >= 8) {
145                        firstEightBytes = new String(saltLine, 0, 8);
146                    }
147                    if ("SALTED__".equalsIgnoreCase(firstEightBytes)) {
148                        salted = true;
149                    }
150                }
151            }
152    
153            byte[] salt = null;
154            if (salted) {
155                salt = new byte[8];
156                System.arraycopy(saltLine, 8, salt, 0, 8);
157            } else {
158                // Encrypted data wasn't salted.  Need to put the "saltLine" we
159                // extracted back into the stream.
160                InputStream head = new ByteArrayInputStream(saltLine);
161                encrypted = new ComboInputStream(head, encrypted);
162            }
163    
164            int keySize = cipherInfo.keySize;
165            int ivSize = cipherInfo.ivSize;
166            boolean des2 = cipherInfo.des2;
167            DerivedKey dk = deriveKey(pwd, salt, keySize, ivSize, des2);
168            Cipher c = PKCS8Key.generateCipher(
169                cipherInfo.javaCipher, cipherInfo.blockMode, dk, des2, null, true
170            );
171    
172            return new CipherInputStream(encrypted, c);
173        }
174    
175        /**
176         * Encrypts data using a password and an OpenSSL compatible cipher
177         * name.
178         *
179         * @param cipher The OpenSSL compatible cipher to use (try "man enc" on a
180         *               unix box to see what's possible).  Some examples:
181         *               <ul><li>des, des3, des-ede3-cbc
182         *               <li>aes128, aes192, aes256, aes-256-cbc
183         *               <li>rc2, rc4, bf</ul>
184         * @param pwd    password to use for this PBE encryption
185         * @param data   byte array to encrypt
186         * @return encrypted bytes as an array in base64.  First 16 bytes include the
187         *         special OpenSSL "Salted__" info encoded into base64.
188         * @throws IOException              problems with the data byte array
189         * @throws GeneralSecurityException problems encrypting
190         */
191        public static byte[] encrypt(String cipher, char[] pwd, byte[] data)
192            throws IOException, GeneralSecurityException {
193            // base64 is the default output format.
194            return encrypt(cipher, pwd, data, true);
195        }
196    
197        /**
198         * Encrypts data using a password and an OpenSSL compatible cipher
199         * name.
200         *
201         * @param cipher The OpenSSL compatible cipher to use (try "man enc" on a
202         *               unix box to see what's possible).  Some examples:
203         *               <ul><li>des, des3, des-ede3-cbc
204         *               <li>aes128, aes192, aes256, aes-256-cbc
205         *               <li>rc2, rc4, bf</ul>
206         * @param pwd    password to use for this PBE encryption
207         * @param data   InputStream to encrypt
208         * @return encrypted bytes as an InputStream.  First 16 bytes include the
209         *         special OpenSSL "Salted__" info encoded into base64.
210         * @throws IOException              problems with the data InputStream
211         * @throws GeneralSecurityException problems encrypting
212         */
213        public static InputStream encrypt(String cipher, char[] pwd,
214                                          InputStream data)
215            throws IOException, GeneralSecurityException {
216            // base64 is the default output format.
217            return encrypt(cipher, pwd, data, true);
218        }
219    
220        /**
221         * Encrypts data using a password and an OpenSSL compatible cipher
222         * name.
223         *
224         * @param cipher   The OpenSSL compatible cipher to use (try "man enc" on a
225         *                 unix box to see what's possible).  Some examples:
226         *                 <ul><li>des, des3, des-ede3-cbc
227         *                 <li>aes128, aes192, aes256, aes-256-cbc
228         *                 <li>rc2, rc4, bf</ul>
229         * @param pwd      password to use for this PBE encryption
230         * @param data     byte array to encrypt
231         * @param toBase64 true if resulting InputStream should contain base64,
232         *                 <br>false if InputStream should contain raw binary.
233         * @return encrypted bytes as an array.  First 16 bytes include the
234         *         special OpenSSL "Salted__" info.
235         * @throws IOException              problems with the data byte array
236         * @throws GeneralSecurityException problems encrypting
237         */
238        public static byte[] encrypt(String cipher, char[] pwd, byte[] data,
239                                     boolean toBase64)
240            throws IOException, GeneralSecurityException {
241            // we use a salt by default.
242            return encrypt(cipher, pwd, data, toBase64, true);
243        }
244    
245        /**
246         * Encrypts data using a password and an OpenSSL compatible cipher
247         * name.
248         *
249         * @param cipher   The OpenSSL compatible cipher to use (try "man enc" on a
250         *                 unix box to see what's possible).  Some examples:
251         *                 <ul><li>des, des3, des-ede3-cbc
252         *                 <li>aes128, aes192, aes256, aes-256-cbc
253         *                 <li>rc2, rc4, bf</ul>
254         * @param pwd      password to use for this PBE encryption
255         * @param data     InputStream to encrypt
256         * @param toBase64 true if resulting InputStream should contain base64,
257         *                 <br>false if InputStream should contain raw binary.
258         * @return encrypted bytes as an InputStream.  First 16 bytes include the
259         *         special OpenSSL "Salted__" info.
260         * @throws IOException              problems with the data InputStream
261         * @throws GeneralSecurityException problems encrypting
262         */
263        public static InputStream encrypt(String cipher, char[] pwd,
264                                          InputStream data, boolean toBase64)
265            throws IOException, GeneralSecurityException {
266            // we use a salt by default.
267            return encrypt(cipher, pwd, data, toBase64, true);
268        }
269    
270        /**
271         * Encrypts data using a password and an OpenSSL compatible cipher
272         * name.
273         *
274         * @param cipher   The OpenSSL compatible cipher to use (try "man enc" on a
275         *                 unix box to see what's possible).  Some examples:
276         *                 <ul><li>des, des3, des-ede3-cbc
277         *                 <li>aes128, aes192, aes256, aes-256-cbc
278         *                 <li>rc2, rc4, bf</ul>
279         * @param pwd      password to use for this PBE encryption
280         * @param data     byte array to encrypt
281         * @param toBase64 true if resulting InputStream should contain base64,
282         *                 <br>false if InputStream should contain raw binary.
283         * @param useSalt  true if a salt should be used to derive the key.
284         *                 <br>false otherwise.  (Best security practises
285         *                 always recommend using a salt!).
286         * @return encrypted bytes as an array.  First 16 bytes include the
287         *         special OpenSSL "Salted__" info if <code>useSalt</code> is true.
288         * @throws IOException              problems with the data InputStream
289         * @throws GeneralSecurityException problems encrypting
290         */
291        public static byte[] encrypt(String cipher, char[] pwd, byte[] data,
292                                     boolean toBase64, boolean useSalt)
293            throws IOException, GeneralSecurityException {
294            ByteArrayInputStream in = new ByteArrayInputStream(data);
295            InputStream encrypted = encrypt(cipher, pwd, in, toBase64, useSalt);
296            return Util.streamToBytes(encrypted);
297        }
298    
299        /**
300         * Encrypts data using a password and an OpenSSL compatible cipher
301         * name.
302         *
303         * @param cipher   The OpenSSL compatible cipher to use (try "man enc" on a
304         *                 unix box to see what's possible).  Some examples:
305         *                 <ul><li>des, des3, des-ede3-cbc
306         *                 <li>aes128, aes192, aes256, aes-256-cbc
307         *                 <li>rc2, rc4, bf</ul>
308         * @param pwd      password to use for this PBE encryption
309         * @param data     InputStream to encrypt
310         * @param toBase64 true if resulting InputStream should contain base64,
311         *                 <br>false if InputStream should contain raw binary.
312         * @param useSalt  true if a salt should be used to derive the key.
313         *                 <br>false otherwise.  (Best security practises
314         *                 always recommend using a salt!).
315         * @return encrypted bytes as an InputStream.  First 16 bytes include the
316         *         special OpenSSL "Salted__" info if <code>useSalt</code> is true.
317         * @throws IOException              problems with the data InputStream
318         * @throws GeneralSecurityException problems encrypting
319         */
320        public static InputStream encrypt(String cipher, char[] pwd,
321                                          InputStream data, boolean toBase64,
322                                          boolean useSalt)
323            throws IOException, GeneralSecurityException {
324            CipherInfo cipherInfo = lookup(cipher);
325            byte[] salt = null;
326            if (useSalt) {
327                SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
328                salt = new byte[8];
329                rand.nextBytes(salt);
330            }
331    
332            int keySize = cipherInfo.keySize;
333            int ivSize = cipherInfo.ivSize;
334            boolean des2 = cipherInfo.des2;
335            DerivedKey dk = deriveKey(pwd, salt, keySize, ivSize, des2);
336            Cipher c = PKCS8Key.generateCipher(
337                cipherInfo.javaCipher, cipherInfo.blockMode, dk, des2, null, false
338            );
339    
340            InputStream cipherStream = new CipherInputStream(data, c);
341    
342            if (useSalt) {
343                byte[] saltLine = new byte[16];
344                byte[] salted = "Salted__".getBytes();
345                System.arraycopy(salted, 0, saltLine, 0, salted.length);
346                System.arraycopy(salt, 0, saltLine, salted.length, salt.length);
347                InputStream head = new ByteArrayInputStream(saltLine);
348                cipherStream = new ComboInputStream(head, cipherStream);
349            }
350            if (toBase64) {
351                cipherStream = new Base64InputStream(cipherStream, false);
352            }
353            return cipherStream;
354        }
355    
356    
357        public static byte[] decrypt(String cipher, byte[] key, byte[] iv,
358                                     byte[] encrypted)
359            throws IOException, GeneralSecurityException {
360            ByteArrayInputStream in = new ByteArrayInputStream(encrypted);
361            InputStream decrypted = decrypt(cipher, key, iv, in);
362            return Util.streamToBytes(decrypted);
363        }
364    
365        public static InputStream decrypt(String cipher, byte[] key, byte[] iv,
366                                          InputStream encrypted)
367            throws IOException, GeneralSecurityException {
368            CipherInfo cipherInfo = lookup(cipher);
369            byte[] firstLine = Util.streamToBytes(encrypted, 16);
370            if (Base64.isArrayByteBase64(firstLine)) {
371                InputStream head = new ByteArrayInputStream(firstLine);
372                // Need to put that 16 byte "firstLine" back into the Stream.
373                encrypted = new ComboInputStream(head, encrypted);
374                encrypted = new Base64InputStream(encrypted, true);
375            } else {
376                // Encrypted data wasn't base64.  Need to put the "firstLine" we
377                // extracted back into the stream.
378                InputStream head = new ByteArrayInputStream(firstLine);
379                encrypted = new ComboInputStream(head, encrypted);
380            }
381    
382            int keySize = cipherInfo.keySize;
383            int ivSize = cipherInfo.ivSize;
384            if (key.length == keySize / 4) // Looks like key is in hex
385            {
386                key = Hex.decode(key);
387            }
388            if (iv.length == ivSize / 4) // Looks like IV is in hex
389            {
390                iv = Hex.decode(iv);
391            }
392            DerivedKey dk = new DerivedKey(key, iv);
393            Cipher c = PKCS8Key.generateCipher(cipherInfo.javaCipher,
394                cipherInfo.blockMode,
395                dk, cipherInfo.des2, null, true);
396            return new CipherInputStream(encrypted, c);
397        }
398    
399        public static byte[] encrypt(String cipher, byte[] key, byte[] iv,
400                                     byte[] data)
401            throws IOException, GeneralSecurityException {
402            return encrypt(cipher, key, iv, data, true);
403        }
404    
405        public static byte[] encrypt(String cipher, byte[] key, byte[] iv,
406                                     byte[] data, boolean toBase64)
407            throws IOException, GeneralSecurityException {
408            ByteArrayInputStream in = new ByteArrayInputStream(data);
409            InputStream encrypted = encrypt(cipher, key, iv, in, toBase64);
410            return Util.streamToBytes(encrypted);
411        }
412    
413    
414        public static InputStream encrypt(String cipher, byte[] key, byte[] iv,
415                                          InputStream data)
416            throws IOException, GeneralSecurityException {
417            return encrypt(cipher, key, iv, data, true);
418        }
419    
420        public static InputStream encrypt(String cipher, byte[] key, byte[] iv,
421                                          InputStream data, boolean toBase64)
422            throws IOException, GeneralSecurityException {
423            CipherInfo cipherInfo = lookup(cipher);
424            int keySize = cipherInfo.keySize;
425            int ivSize = cipherInfo.ivSize;
426            if (key.length == keySize / 4) {
427                key = Hex.decode(key);
428            }
429            if (iv.length == ivSize / 4) {
430                iv = Hex.decode(iv);
431            }
432            DerivedKey dk = new DerivedKey(key, iv);
433            Cipher c = PKCS8Key.generateCipher(cipherInfo.javaCipher,
434                cipherInfo.blockMode,
435                dk, cipherInfo.des2, null, false);
436    
437            InputStream cipherStream = new CipherInputStream(data, c);
438            if (toBase64) {
439                cipherStream = new Base64InputStream(cipherStream, false);
440            }
441            return cipherStream;
442        }
443    
444    
445        public static DerivedKey deriveKey(char[] password, byte[] salt,
446                                           int keySize, boolean des2)
447            throws NoSuchAlgorithmException {
448            return deriveKey(password, salt, keySize, 0, des2);
449        }
450    
451        public static DerivedKey deriveKey(char[] password, byte[] salt,
452                                           int keySize, int ivSize, boolean des2)
453            throws NoSuchAlgorithmException {
454            if (des2) {
455                keySize = 128;
456            }
457            MessageDigest md = MessageDigest.getInstance("MD5");
458            byte[] pwdAsBytes = new byte[password.length];
459            for (int i = 0; i < password.length; i++) {
460                pwdAsBytes[i] = (byte) password[i];
461            }
462    
463            md.reset();
464            byte[] keyAndIv = new byte[(keySize / 8) + (ivSize / 8)];
465            if (salt == null || salt.length == 0) {
466                // Unsalted!  Bad idea!
467                salt = null;
468            }
469            byte[] result;
470            int currentPos = 0;
471            while (currentPos < keyAndIv.length) {
472                md.update(pwdAsBytes);
473                if (salt != null) {
474                    // First 8 bytes of salt ONLY!  That wasn't obvious to me
475                    // when using AES encrypted private keys in "Traditional
476                    // SSLeay Format".
477                    //
478                    // Example:
479                    // DEK-Info: AES-128-CBC,8DA91D5A71988E3D4431D9C2C009F249
480                    //
481                    // Only the first 8 bytes are salt, but the whole thing is
482                    // re-used again later as the IV.  MUCH gnashing of teeth!
483                    md.update(salt, 0, 8);
484                }
485                result = md.digest();
486                int stillNeed = keyAndIv.length - currentPos;
487                // Digest gave us more than we need.  Let's truncate it.
488                if (result.length > stillNeed) {
489                    byte[] b = new byte[stillNeed];
490                    System.arraycopy(result, 0, b, 0, b.length);
491                    result = b;
492                }
493                System.arraycopy(result, 0, keyAndIv, currentPos, result.length);
494                currentPos += result.length;
495                if (currentPos < keyAndIv.length) {
496                    // Next round starts with a hash of the hash.
497                    md.reset();
498                    md.update(result);
499                }
500            }
501            if (des2) {
502                keySize = 192;
503                byte[] buf = new byte[keyAndIv.length + 8];
504                // Make space where 3rd key needs to go (16th - 24th bytes):
505                System.arraycopy(keyAndIv, 0, buf, 0, 16);
506                if (ivSize > 0) {
507                    System.arraycopy(keyAndIv, 16, buf, 24, keyAndIv.length - 16);
508                }
509                keyAndIv = buf;
510                // copy first 8 bytes into last 8 bytes to create 2DES key.
511                System.arraycopy(keyAndIv, 0, keyAndIv, 16, 8);
512            }
513            if (ivSize == 0) {
514                // if ivSize == 0, then "keyAndIv" array is actually all key.
515    
516                // Must be "Traditional SSLeay Format" encrypted private key in
517                // PEM.  The "salt" in its entirety (not just first 8 bytes) will
518                // probably be re-used later as the IV (initialization vector).
519                return new DerivedKey(keyAndIv, salt);
520            } else {
521                byte[] key = new byte[keySize / 8];
522                byte[] iv = new byte[ivSize / 8];
523                System.arraycopy(keyAndIv, 0, key, 0, key.length);
524                System.arraycopy(keyAndIv, key.length, iv, 0, iv.length);
525                return new DerivedKey(key, iv);
526            }
527        }
528    
529    
530        public static class CipherInfo {
531            public final String javaCipher;
532            public final String blockMode;
533            public final int keySize;
534            public final int ivSize;
535            public final boolean des2;
536    
537            public CipherInfo(String javaCipher, String blockMode, int keySize,
538                              int ivSize, boolean des2) {
539                this.javaCipher = javaCipher;
540                this.blockMode = blockMode;
541                this.keySize = keySize;
542                this.ivSize = ivSize;
543                this.des2 = des2;
544            }
545    
546            public String toString() {
547                return javaCipher + "/" + blockMode + " " + keySize + "bit  des2=" + des2;
548            }
549        }
550    
551        /**
552         * Converts the way OpenSSL names its ciphers into a Java-friendly naming.
553         *
554         * @param openSSLCipher OpenSSL cipher name, e.g. "des3" or "des-ede3-cbc".
555         *                      Try "man enc" on a unix box to see what's possible.
556         * @return CipherInfo object with the Java-friendly cipher information.
557         */
558        public static CipherInfo lookup(String openSSLCipher) {
559            openSSLCipher = openSSLCipher.trim();
560            if (openSSLCipher.charAt(0) == '-') {
561                openSSLCipher = openSSLCipher.substring(1);
562            }
563            String javaCipher = openSSLCipher.toUpperCase();
564            String blockMode = "CBC";
565            int keySize = -1;
566            int ivSize = 64;
567            boolean des2 = false;
568    
569    
570            StringTokenizer st = new StringTokenizer(openSSLCipher, "-");
571            if (st.hasMoreTokens()) {
572                javaCipher = st.nextToken().toUpperCase();
573                if (st.hasMoreTokens()) {
574                    // Is this the middle token?  Or the last token?
575                    String tok = st.nextToken();
576                    if (st.hasMoreTokens()) {
577                        try {
578                            keySize = Integer.parseInt(tok);
579                        }
580                        catch (NumberFormatException nfe) {
581                            // I guess 2nd token isn't an integer
582                            String upper = tok.toUpperCase();
583                            if (upper.startsWith("EDE3")) {
584                                javaCipher = "DESede";
585                            } else if (upper.startsWith("EDE")) {
586                                javaCipher = "DESede";
587                                des2 = true;
588                            }
589                        }
590                        blockMode = st.nextToken().toUpperCase();
591                    } else {
592                        try {
593                            keySize = Integer.parseInt(tok);
594                        }
595                        catch (NumberFormatException nfe) {
596                            // It's the last token, so must be mode (usually "CBC").
597                            blockMode = tok.toUpperCase();
598                            if (blockMode.startsWith("EDE3")) {
599                                javaCipher = "DESede";
600                                blockMode = "ECB";
601                            } else if (blockMode.startsWith("EDE")) {
602                                javaCipher = "DESede";
603                                blockMode = "ECB";
604                                des2 = true;
605                            }
606                        }
607                    }
608                }
609            }
610            if (javaCipher.startsWith("BF")) {
611                javaCipher = "Blowfish";
612            } else if (javaCipher.startsWith("TWOFISH")) {
613                javaCipher = "Twofish";
614                ivSize = 128;
615            } else if (javaCipher.startsWith("IDEA")) {
616                javaCipher = "IDEA";
617            } else if (javaCipher.startsWith("CAST6")) {
618                javaCipher = "CAST6";
619                ivSize = 128;
620            } else if (javaCipher.startsWith("CAST")) {
621                javaCipher = "CAST5";
622            } else if (javaCipher.startsWith("GOST")) {
623                keySize = 256;
624            } else if (javaCipher.startsWith("DESX")) {
625                javaCipher = "DESX";
626            } else if ("DES3".equals(javaCipher)) {
627                javaCipher = "DESede";
628            } else if ("DES2".equals(javaCipher)) {
629                javaCipher = "DESede";
630                des2 = true;
631            } else if (javaCipher.startsWith("RIJNDAEL")) {
632                javaCipher = "Rijndael";
633                ivSize = 128;
634            } else if (javaCipher.startsWith("SEED")) {
635                javaCipher = "SEED";
636                ivSize = 128;
637            } else if (javaCipher.startsWith("SERPENT")) {
638                javaCipher = "Serpent";
639                ivSize = 128;
640            } else if (javaCipher.startsWith("Skipjack")) {
641                javaCipher = "Skipjack";
642                ivSize = 128;
643            } else if (javaCipher.startsWith("RC6")) {
644                javaCipher = "RC6";
645                ivSize = 128;
646            } else if (javaCipher.startsWith("TEA")) {
647                javaCipher = "TEA";
648            } else if (javaCipher.startsWith("XTEA")) {
649                javaCipher = "XTEA";
650            } else if (javaCipher.startsWith("AES")) {
651                if (javaCipher.startsWith("AES128")) {
652                    keySize = 128;
653                } else if (javaCipher.startsWith("AES192")) {
654                    keySize = 192;
655                } else if (javaCipher.startsWith("AES256")) {
656                    keySize = 256;
657                }
658                javaCipher = "AES";
659                ivSize = 128;
660            } else if (javaCipher.startsWith("CAMELLIA")) {
661                if (javaCipher.startsWith("CAMELLIA128")) {
662                    keySize = 128;
663                } else if (javaCipher.startsWith("CAMELLIA192")) {
664                    keySize = 192;
665                } else if (javaCipher.startsWith("CAMELLIA256")) {
666                    keySize = 256;
667                }
668                javaCipher = "CAMELLIA";
669                ivSize = 128;
670            }
671            if (keySize == -1) {
672                if (javaCipher.startsWith("DESede")) {
673                    keySize = 192;
674                } else if (javaCipher.startsWith("DES")) {
675                    keySize = 64;
676                } else {
677                    // RC2, RC4, RC5 and Blowfish ?
678                    keySize = 128;
679                }
680            }
681            return new CipherInfo(javaCipher, blockMode, keySize, ivSize, des2);
682        }
683    
684    
685        /**
686         * @param args command line arguments: [password] [cipher] [file-to-decrypt]
687         *             <br>[cipher] == OpenSSL cipher name, e.g. "des3" or "des-ede3-cbc".
688         *             Try "man enc" on a unix box to see what's possible.
689         * @throws IOException              problems with the [file-to-decrypt]
690         * @throws GeneralSecurityException decryption problems
691         */
692        public static void main(String[] args)
693            throws IOException, GeneralSecurityException {
694            if (args.length < 3) {
695                System.out.println(Version.versionString());
696                System.out.println("Pure-java utility to decrypt files previously encrypted by \'openssl enc\'");
697                System.out.println();
698                System.out.println("Usage:  java -cp commons-ssl.jar org.apache.commons.ssl.OpenSSL [args]");
699                System.out.println("        [args]   == [password] [cipher] [file-to-decrypt]");
700                System.out.println("        [cipher] == des, des3, des-ede3-cbc, aes256, rc2, rc4, bf, bf-cbc, etc...");
701                System.out.println("                    Try 'man enc' on a unix box to see what's possible.");
702                System.out.println();
703                System.out.println("This utility can handle base64 or raw, salted or unsalted.");
704                System.out.println();
705                System.exit(1);
706            }
707            char[] password = args[0].toCharArray();
708    
709            InputStream in = new FileInputStream(args[2]);
710            in = decrypt(args[1], password, in);
711    
712            // in = encrypt( args[ 1 ], pwdAsBytes, in, true );
713    
714            Util.pipeStream(in, System.out, false);
715            byte[] output = Util.streamToBytes(in);
716            System.out.write(output);
717            System.out.flush();
718        }
719    
720    }