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
032package org.apache.commons.ssl;
033
034import org.apache.commons.ssl.util.Hex;
035
036import javax.crypto.Cipher;
037import javax.crypto.CipherInputStream;
038import java.io.ByteArrayInputStream;
039import java.io.FileInputStream;
040import java.io.IOException;
041import java.io.InputStream;
042import java.security.GeneralSecurityException;
043import java.security.MessageDigest;
044import java.security.NoSuchAlgorithmException;
045import java.security.SecureRandom;
046import 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 */
076public 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}