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