001/*
002 * $HeadURL: file:///opt/dev/not-yet-commons-ssl-SVN-repo/tags/commons-ssl-0.3.17/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
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.*;
039import java.security.GeneralSecurityException;
040import java.security.MessageDigest;
041import java.security.NoSuchAlgorithmException;
042import java.security.SecureRandom;
043import 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 */
073public 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}