    001/*
    002 * $HeadURL: http://juliusdavies.ca/svn/not-yet-commons-ssl/tags/commons-ssl-0.3.15/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}