package railo.runtime.crypt;

import java.util.Random;

//package maddany.crypto;



/*   Coding by maddany@madloutre.org

 *   01-01-2000

 *

 *   This program is free software; you can redistribute it and/or modify

 *   it under the terms of the GNU General Public License as published by

 *   the Free Software Foundation; either version 2 of the License, or

 *   (at your option) any later version.

 *

 *   This program is distributed in the hope that it will be useful,

 *   but WITHOUT ANY WARRANTY; without even the implied warranty of

 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

 *   GNU General Public License for more details.

 *

 *   You should have received a copy of the GNU General Public License

 *   along with this program; if not, write to the Free Software

 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

 *

 */








public final class BlowfishEasy {





  // the Blowfish CBC instance

  BlowfishCBC m_bfish;



  // one random generator for all simple callers...

  static Random m_rndGen;



  // ...and created early

  static {

    m_rndGen = new Random();

  };







  /**

    * constructor to set up a string as the key (oversized password will be cut)

    * @param sPassword the password (treated as a real unicode array)

    */

  public BlowfishEasy(String sPassword) {



    // hash down the password to a 160bit key

    SHA1 hasher = new SHA1();

    hasher.update(sPassword);

    hasher.finalize();



    // setup the encryptor (use a dummy IV)

    m_bfish = new BlowfishCBC(hasher.getDigest(), 0);

    hasher.clear();

  };







  /**

    * encrypts a string (treated in UNICODE) using the

    * standard Java random generator, which isn't that

    * great for creating IVs

    * @param sPlainText string to encrypt

    * @return encrypted string in binhex format

    */

  public String encryptString(String sPlainText) {



    // get the IV

    long lCBCIV;

    synchronized (m_rndGen) {

      lCBCIV = m_rndGen.nextLong();

    }



    // map the call;

    return encStr(sPlainText, lCBCIV);

  };







  /**

    * encrypts a string (treated in UNICODE)

    * @param sPlainText string to encrypt

    * @param rndGen random generator (usually a java.security.SecureRandom instance)

    * @return encrypted string in binhex format

    */

  public String encryptString(String sPlainText,

                              Random rndGen) {



    // get the IV

    long lCBCIV = rndGen.nextLong();



    // map the call;

    return encStr(sPlainText, lCBCIV);

  };







  // internal routine for string encryption



  private String encStr(String sPlainText,

                        long lNewCBCIV) {



    // allocate the buffer (align to the next 8 byte border plus padding)

    int nStrLen = sPlainText.length();

    byte[] buf = new byte [((nStrLen << 1) & 0xfffffff8) + 8];



    // copy all bytes of the string into the buffer (use network byte order)

    int nI;

    int nPos = 0;

    for (nI = 0; nI < nStrLen; nI++) {

      char cActChar = sPlainText.charAt(nI);

      buf[nPos++] = (byte) ((cActChar >> 8) & 0x0ff);

      buf[nPos++] = (byte) (cActChar & 0x0ff) ;

    };



    // pad the rest with the PKCS5 scheme

    byte bPadVal = (byte)(buf.length - (nStrLen << 1));

    while (nPos < buf.length)

      buf[nPos++] = bPadVal;



    // create the encryptor

    m_bfish.setCBCIV(lNewCBCIV);



    // encrypt the buffer

    m_bfish.encrypt(buf);



    // return the binhex string

    byte[] newCBCIV = new byte[BlowfishECB.BLOCKSIZE];

    BinConverter.longToByteArray(lNewCBCIV,

                                 newCBCIV,

                                 0);

    return BinConverter.bytesToBinHex(newCBCIV,

                                      0,

                                      BlowfishECB.BLOCKSIZE) +

           BinConverter.bytesToBinHex(buf,

                                      0,

                                      buf.length);

  };





  /**

    * decrypts a hexbin string (handling is case sensitive)

    * @param sCipherText hexbin string to decrypt

    * @return decrypted string (null equals an error)

    */

  public String decryptString(String sCipherText) {



    // get the number of estimated bytes in the string (cut off broken blocks)

    int nLen = (sCipherText.length() >> 1) & ~7;



    // does the given stuff make sense (at least the CBC IV)?

    if (nLen < BlowfishECB.BLOCKSIZE)

      return null;



    // get the CBC IV

    byte[] cbciv = new byte[BlowfishECB.BLOCKSIZE];

    int nNumOfBytes = BinConverter.binHexToBytes(sCipherText,

                                                 cbciv,

                                                 0,

                                                 0,

                                                 BlowfishECB.BLOCKSIZE);

    if (nNumOfBytes < BlowfishECB.BLOCKSIZE)

      return null;

    // (got it)

    m_bfish.setCBCIV(cbciv);



    // something left to decrypt?

    nLen -= BlowfishECB.BLOCKSIZE;

    if (nLen == 0)

      return "";



    // get all data bytes now

    byte[] buf = new byte[nLen];

    nNumOfBytes = BinConverter.binHexToBytes(sCipherText,

                                             buf,

                                             BlowfishECB.BLOCKSIZE * 2,

                                             0,

                                             nLen);



    // we cannot accept broken binhex sequences due to padding

    // and decryption

    if (nNumOfBytes < nLen)

      return null;



    // decrypt the buffer

    m_bfish.decrypt(buf);



    // get the last padding byte

    int nPadByte = buf[buf.length - 1] & 0x0ff;

    // ( try to get all information if the padding doesn't seem to be correct)

    if ((nPadByte > 8) || (nPadByte < 0))

      nPadByte = 0;



    // calculate the real size of this message

    nNumOfBytes -= nPadByte;

    if (nNumOfBytes < 0)

      return "";



    // success

    return BinConverter.byteArrayToUNCString(buf, 0, nNumOfBytes);

  };





  /**

    * destroys (clears) the encryption engine,

    * after that the instance is not valid anymore

    */

  public void destroy() {

    m_bfish.cleanUp();

  };

};


