All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.xml.security.utils.Base64 Maven / Gradle / Ivy

Go to download

Apache XML Security for Java supports XML-Signature Syntax and Processing, W3C Recommendation 12 February 2002, and XML Encryption Syntax and Processing, W3C Recommendation 10 December 2002. As of version 1.4, the library supports the standard Java API JSR-105: XML Digital Signature APIs.

There is a newer version: 4.0.2
Show newest version
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.xml.security.utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;

import org.apache.xml.security.exceptions.Base64DecodingException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

/**
 * Implementation of MIME's Base64 encoding and decoding conversions.
 * Optimized code. (raw version taken from oreilly.jonathan.util, 
 * and currently org.apache.xerces.ds.util.Base64)
 *
 * @author Raul Benito(Of the xerces copy, and little adaptations).
 * @author Anli Shundi
 * @author Christian Geuer-Pollmann
 * @see RFC 2045
 * @see org.apache.xml.security.transforms.implementations.TransformBase64Decode
 */
public class Base64 {

    /** Field BASE64DEFAULTLENGTH */
    public static final int BASE64DEFAULTLENGTH = 76;
    
    private static final int BASELENGTH = 255;
    private static final int LOOKUPLENGTH = 64;
    private static final int TWENTYFOURBITGROUP = 24;
    private static final int EIGHTBIT = 8;
    private static final int SIXTEENBIT = 16;
    private static final int FOURBYTE = 4;
    private static final int SIGN = -128;
    private static final char PAD = '=';   
    private static final byte [] base64Alphabet = new byte[BASELENGTH];
    private static final char [] lookUpBase64Alphabet = new char[LOOKUPLENGTH];

    static {
        for (int i = 0; i < BASELENGTH; i++) {
            base64Alphabet[i] = -1;
        }
        for (int i = 'Z'; i >= 'A'; i--) {
            base64Alphabet[i] = (byte) (i - 'A');
        }
        for (int i = 'z'; i>= 'a'; i--) {
            base64Alphabet[i] = (byte) (i - 'a' + 26);
        }

        for (int i = '9'; i >= '0'; i--) {
            base64Alphabet[i] = (byte) (i - '0' + 52);
        }

        base64Alphabet['+'] = 62;
        base64Alphabet['/'] = 63;

        for (int i = 0; i <= 25; i++) {
            lookUpBase64Alphabet[i] = (char)('A' + i);
        }

        for (int i = 26,  j = 0; i <= 51; i++, j++) {
            lookUpBase64Alphabet[i] = (char)('a' + j);
        }

        for (int i = 52,  j = 0; i <= 61; i++, j++) {
            lookUpBase64Alphabet[i] = (char)('0' + j);
        }
        lookUpBase64Alphabet[62] = '+';
        lookUpBase64Alphabet[63] = '/';
    }

    private Base64() {
        // we don't allow instantiation
    }

    /**
     * Returns a byte-array representation of a {@link BigInteger}.
     * No sign-bit is output.
     *
     * N.B.: {@link BigInteger}'s toByteArray
     * returns eventually longer arrays because of the leading sign-bit.
     *
     * @param big BigInteger to be converted
     * @param bitlen int the desired length in bits of the representation
     * @return a byte array with bitlen bits of big
     */
    static final byte[] getBytes(BigInteger big, int bitlen) {

        //round bitlen
        bitlen = ((bitlen + 7) >> 3) << 3;

        if (bitlen < big.bitLength()) {
            throw new IllegalArgumentException(I18n.translate("utils.Base64.IllegalBitlength"));
        }

        byte[] bigBytes = big.toByteArray();

        if (big.bitLength() % 8 != 0
            && big.bitLength() / 8 + 1 == bitlen / 8) {
            return bigBytes;
        }

        // some copying needed
        int startSrc = 0;    // no need to skip anything
        int bigLen = bigBytes.length;    //valid length of the string

        if (big.bitLength() % 8 == 0) {    // correct values
            startSrc = 1;    // skip sign bit

            bigLen--;    // valid length of the string
        }

        int startDst = bitlen / 8 - bigLen;    //pad with leading nulls
        byte[] resizedBytes = new byte[bitlen / 8];

        System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, bigLen);

        return resizedBytes;
    }

    /**
     * Encode in Base64 the given {@link BigInteger}.
     *
     * @param big
     * @return String with Base64 encoding
     */
    public static final String encode(BigInteger big) {
        return encode(getBytes(big, big.bitLength()));
    }

    /**
     * Returns a byte-array representation of a {@link BigInteger}.
     * No sign-bit is output.
     *
     * N.B.: {@link BigInteger}'s toByteArray
     * returns eventually longer arrays because of the leading sign-bit.
     *
     * @param big BigInteger to be converted
     * @param bitlen int the desired length in bits of the representation
     * @return a byte array with bitlen bits of big
     */
    public static final byte[] encode(BigInteger big, int bitlen) {

        //round bitlen
        bitlen = ((bitlen + 7) >> 3) << 3;

        if (bitlen < big.bitLength()) {
            throw new IllegalArgumentException(I18n.translate("utils.Base64.IllegalBitlength"));
        }

        byte[] bigBytes = big.toByteArray();

        if (big.bitLength() % 8 != 0
            && big.bitLength() / 8 + 1 == bitlen / 8) {
            return bigBytes;
        }

        // some copying needed
        int startSrc = 0;    // no need to skip anything
        int bigLen = bigBytes.length;    //valid length of the string

        if (big.bitLength() % 8 == 0) {    // correct values
            startSrc = 1;    // skip sign bit

            bigLen--;    // valid length of the string
        }

        int startDst = bitlen / 8 - bigLen;    //pad with leading nulls
        byte[] resizedBytes = new byte[bitlen / 8];

        System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, bigLen);

        return resizedBytes;
    }

    /**
     * Method decodeBigIntegerFromElement
     *
     * @param element
     * @return the biginteger obtained from the node
     * @throws Base64DecodingException
     */
    public static final BigInteger decodeBigIntegerFromElement(Element element) 
        throws Base64DecodingException {
        return new BigInteger(1, Base64.decode(element));
    }

    /**
     * Decode a base 64 string into a {@link BigInteger}
     * @param base64str Base 64 encoded string.
     * @return a decoded BigInteger
     * @throws Base64DecodingException
     */
    public static BigInteger decodeBigIntegerFromString(String base64str)
            throws Base64DecodingException {
        return new BigInteger(1, Base64.decode(base64str));
    }

    /**
     * This method takes an (empty) Element and a BigInteger and adds the
     * base64 encoded BigInteger to the Element.
     *
     * @param element
     * @param biginteger
     */
    public static final void fillElementWithBigInteger(Element element, BigInteger biginteger) {

        String encodedInt = encode(biginteger);

        if (!XMLUtils.ignoreLineBreaks() && encodedInt.length() > BASE64DEFAULTLENGTH) {
            encodedInt = "\n" + encodedInt + "\n";
        }

        Document doc = element.getOwnerDocument();
        Text text = doc.createTextNode(encodedInt);

        element.appendChild(text);
    }

    /**
     * Method decode
     *
     * Takes the Text children of the Element and interprets
     * them as input for the Base64.decode() function.
     *
     * @param element
     * @return the byte obtained of the decoding the element
     * $todo$ not tested yet
     * @throws Base64DecodingException
     */
    public static final byte[] decode(Element element) throws Base64DecodingException {

        Node sibling = element.getFirstChild();
        StringBuilder sb = new StringBuilder();

        while (sibling != null) {
            if (sibling.getNodeType() == Node.TEXT_NODE) {
                Text t = (Text) sibling;

                sb.append(t.getData());
            }
            sibling = sibling.getNextSibling();
        }

        return decode(sb.toString());
    }

    /**
     * Method encodeToElement
     *
     * @param doc
     * @param localName
     * @param bytes
     * @return an Element with the base64 encoded in the text.
     *
     */
    public static final Element encodeToElement(Document doc, String localName, byte[] bytes) {
        Element el = XMLUtils.createElementInSignatureSpace(doc, localName);
        Text text = doc.createTextNode(encode(bytes));

        el.appendChild(text);

        return el;
    }

    /**
     * Method decode
     *
     * @param base64
     * @return the UTF bytes of the base64
     * @throws Base64DecodingException
     *
     */
    public static final byte[] decode(byte[] base64) throws Base64DecodingException  {   	   
        return decodeInternal(base64, -1);
    }

    /**
     * Encode a byte array and fold lines at the standard 76th character unless 
     * ignore line breaks property is set.
     *
     * @param binaryData byte[] to be base64 encoded
     * @return the String with encoded data
     */
    public static final String encode(byte[] binaryData) {
        return XMLUtils.ignoreLineBreaks()
            ? encode(binaryData, Integer.MAX_VALUE) 
            : encode(binaryData, BASE64DEFAULTLENGTH);
    }

    /**
     * Base64 decode the lines from the reader and return an InputStream
     * with the bytes.
     *
     * @param reader
     * @return InputStream with the decoded bytes
     * @exception IOException passes what the reader throws
     * @throws IOException
     * @throws Base64DecodingException
     */
    public static final byte[] decode(BufferedReader reader)
        throws IOException, Base64DecodingException {

        byte[] retBytes = null;
        UnsyncByteArrayOutputStream baos = new UnsyncByteArrayOutputStream();
        try {
            String line;
            while (null != (line = reader.readLine())) {
                byte[] bytes = decode(line);
                baos.write(bytes);
            }
            retBytes = baos.toByteArray();
        } finally {
            baos.close();
        }

        return retBytes;
    }

    protected static final boolean isWhiteSpace(byte octet) {
        return octet == 0x20 || octet == 0xd || octet == 0xa || octet == 0x9;
    }

    protected static final boolean isPad(byte octet) {
        return octet == PAD;
    }

    /**
     * Encodes hex octets into Base64
     *
     * @param binaryData Array containing binaryData
     * @return Encoded Base64 array
     */
    /**
     * Encode a byte array in Base64 format and return an optionally
     * wrapped line.
     *
     * @param binaryData byte[] data to be encoded
     * @param length int length of wrapped lines; No wrapping if less than 4.
     * @return a String with encoded data
     */
    public static final String  encode(byte[] binaryData, int length) {
        if (length < 4) {
            length = Integer.MAX_VALUE;
        }

        if (binaryData == null) {
            return null;
        }

        int lengthDataBits = binaryData.length * EIGHTBIT;
        if (lengthDataBits == 0) {
            return "";
        }

        int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
        int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
        int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
        int quartesPerLine = length / 4;
        int numberLines = (numberQuartet - 1) / quartesPerLine;
        char encodedData[] = null;

        encodedData = new char[numberQuartet * 4 + numberLines];

        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
        int encodedIndex = 0;
        int dataIndex = 0;
        int i = 0;

        for (int line = 0; line < numberLines; line++) {
            for (int quartet = 0; quartet < 19; quartet++) {
                b1 = binaryData[dataIndex++];
                b2 = binaryData[dataIndex++];
                b3 = binaryData[dataIndex++];

                l  = (byte)(b2 & 0x0f);
                k  = (byte)(b1 & 0x03);

                byte val1 = ((b1 & SIGN) == 0) ? (byte)(b1 >> 2): (byte)((b1) >> 2 ^ 0xc0);

                byte val2 = ((b2 & SIGN) == 0) ? (byte)(b2 >> 4) : (byte)((b2) >> 4 ^ 0xf0);
                byte val3 = ((b3 & SIGN) == 0) ? (byte)(b3 >> 6) : (byte)((b3) >> 6 ^ 0xfc);


                encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
                encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
                encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
                encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];

                i++;
            }           
            encodedData[encodedIndex++] = 0xa;           
        }

        for (; i < numberTriplets; i++) {
            b1 = binaryData[dataIndex++];
            b2 = binaryData[dataIndex++];
            b3 = binaryData[dataIndex++];

            l  = (byte)(b2 & 0x0f);
            k  = (byte)(b1 & 0x03);

            byte val1 = ((b1 & SIGN) == 0) ? (byte)(b1 >> 2) : (byte)((b1) >> 2 ^ 0xc0);

            byte val2 = ((b2 & SIGN) == 0) ? (byte)(b2 >> 4) : (byte)((b2) >> 4 ^ 0xf0);
            byte val3 = ((b3 & SIGN) == 0) ? (byte)(b3 >> 6) : (byte)((b3) >> 6 ^ 0xfc);
    
    
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
        }

        // form integral number of 6-bit groups
        if (fewerThan24bits == EIGHTBIT) {
            b1 = binaryData[dataIndex];
            k = (byte) (b1 &0x03);
            byte val1 = ((b1 & SIGN) == 0) ? (byte)(b1 >> 2):(byte)((b1) >> 2 ^ 0xc0);
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
            encodedData[encodedIndex++] = PAD;
            encodedData[encodedIndex++] = PAD;
        } else if (fewerThan24bits == SIXTEENBIT) {
            b1 = binaryData[dataIndex];
            b2 = binaryData[dataIndex +1 ];
            l = ( byte ) (b2 & 0x0f);
            k = ( byte ) (b1 & 0x03);

            byte val1 = ((b1 & SIGN) == 0) ? (byte)(b1 >> 2) : (byte)((b1) >> 2 ^ 0xc0);
            byte val2 = ((b2 & SIGN) == 0) ? (byte)(b2 >> 4) : (byte)((b2) >> 4 ^ 0xf0);
    
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
            encodedData[encodedIndex++] = PAD;
        }

        //encodedData[encodedIndex] = 0xa;

        return new String(encodedData);
    }

    /**
     * Decodes Base64 data into octets
     *
     * @param encoded String containing base64 encoded data
     * @return byte array containing the decoded data
     * @throws Base64DecodingException if there is a problem decoding the data
     */
    public static final byte[] decode(String encoded) throws Base64DecodingException {
        if (encoded == null) {
            return null;
        }
        byte[] bytes = new byte[encoded.length()];
        int len = getBytesInternal(encoded, bytes);
        return decodeInternal(bytes, len);
    }

    protected static final int getBytesInternal(String s, byte[] result) {
        int length = s.length();

        int newSize = 0;
        for (int i = 0; i < length; i++) {
            byte dataS = (byte)s.charAt(i);
            if (!isWhiteSpace(dataS)) {
                result[newSize++] = dataS;
            }
        }
        return newSize;
    }

    protected static final byte[] decodeInternal(byte[] base64Data, int len) 
        throws Base64DecodingException {
        // remove white spaces
        if (len == -1) {
            len = removeWhiteSpace(base64Data);
        }

        if (len % FOURBYTE != 0) {
            throw new Base64DecodingException("decoding.divisible.four");
            //should be divisible by four
        }

        int numberQuadruple = len / FOURBYTE;

        if (numberQuadruple == 0) {
            return new byte[0];
        }

        byte decodedData[] = null;
        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;

        int i = 0;
        int encodedIndex = 0;
        int dataIndex = 0;

        //decodedData = new byte[ (numberQuadruple)*3];
        dataIndex = (numberQuadruple - 1) * 4;
        encodedIndex = (numberQuadruple - 1) * 3;
        //first last bits.
        b1 = base64Alphabet[base64Data[dataIndex++]];
        b2 = base64Alphabet[base64Data[dataIndex++]];
        if (b1 == -1 || b2 == -1) {
             //if found "no data" just return null
            throw new Base64DecodingException("decoding.general");
        }


        byte d3, d4;
        b3 = base64Alphabet[d3 = base64Data[dataIndex++]];
        b4 = base64Alphabet[d4 = base64Data[dataIndex++]];
        if (b3 == -1 || b4 == -1) {
            //Check if they are PAD characters
            if (isPad(d3) && isPad(d4)) {               //Two PAD e.g. 3c[Pad][Pad]
                if ((b2 & 0xf) != 0) { //last 4 bits should be zero
                    throw new Base64DecodingException("decoding.general");
                }
                decodedData = new byte[encodedIndex + 1];                
                decodedData[encodedIndex]   = (byte)(b1 << 2 | b2 >> 4) ;                
            } else if (!isPad(d3) && isPad(d4)) {               //One PAD  e.g. 3cQ[Pad]                
                if ((b3 & 0x3) != 0) { //last 2 bits should be zero
                    throw new Base64DecodingException("decoding.general");
                }
                decodedData = new byte[encodedIndex + 2];                
                decodedData[encodedIndex++] = (byte)(b1 << 2 | b2 >> 4);
                decodedData[encodedIndex] = (byte)(((b2 & 0xf) << 4) |((b3 >> 2) & 0xf));                
            } else {
                //an error  like "3c[Pad]r", "3cdX", "3cXd", "3cXX" where X is non data
                throw new Base64DecodingException("decoding.general");
            }
        } else {
            //No PAD e.g 3cQl
            decodedData = new byte[encodedIndex+3];
            decodedData[encodedIndex++] = (byte)(b1 << 2 | b2 >> 4) ;
            decodedData[encodedIndex++] = (byte)(((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
            decodedData[encodedIndex++] = (byte)(b3 << 6 | b4);
        }
        encodedIndex = 0;
        dataIndex = 0;
        //the begin
        for (i = numberQuadruple - 1; i > 0; i--) {
            b1 = base64Alphabet[base64Data[dataIndex++]];
            b2 = base64Alphabet[base64Data[dataIndex++]];
            b3 = base64Alphabet[base64Data[dataIndex++]];
            b4 = base64Alphabet[base64Data[dataIndex++]];

            if (b1 == -1 || b2 == -1 || b3 == -1 || b4 == -1) {
                //if found "no data" just return null   
                throw new Base64DecodingException("decoding.general");
            }

            decodedData[encodedIndex++] = (byte)(b1 << 2 | b2 >> 4) ;
            decodedData[encodedIndex++] = (byte)(((b2 & 0xf) << 4) |((b3 >> 2) & 0xf));
            decodedData[encodedIndex++] = (byte)(b3 << 6 | b4 );
        }            
        return decodedData;
    }

    /**
     * Decodes Base64 data into outputstream
     *
     * @param base64Data String containing Base64 data
     * @param os the outputstream
     * @throws IOException
     * @throws Base64DecodingException
     */
    public static final void decode(String base64Data, OutputStream os) 
        throws Base64DecodingException, IOException {
        byte[] bytes = new byte[base64Data.length()];
        int len = getBytesInternal(base64Data, bytes);
        decode(bytes, os, len);
    }

    /**
     * Decodes Base64 data into outputstream
     *
     * @param base64Data Byte array containing Base64 data
     * @param os the outputstream
     * @throws IOException
     * @throws Base64DecodingException
     */
    public static final void decode(byte[] base64Data, OutputStream os) 
        throws Base64DecodingException, IOException {	    
        decode(base64Data, os, -1);
    }

    protected static final void decode(byte[] base64Data, OutputStream os, int len) 
        throws Base64DecodingException, IOException {	    
        // remove white spaces
        if (len == -1) {
            len = removeWhiteSpace(base64Data);
        }

        if (len % FOURBYTE != 0) {
            throw new Base64DecodingException("decoding.divisible.four");
            //should be divisible by four
        }

        int numberQuadruple = len / FOURBYTE;

        if (numberQuadruple == 0) {
            return;
        }

        //byte decodedData[] = null;
        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;

        int i = 0;
        int dataIndex = 0;    

        //the begin
        for (i = numberQuadruple - 1; i > 0; i--) {
            b1 = base64Alphabet[base64Data[dataIndex++]];
            b2 = base64Alphabet[base64Data[dataIndex++]];
            b3 = base64Alphabet[base64Data[dataIndex++]];
            b4 = base64Alphabet[base64Data[dataIndex++]];
            if (b1 == -1 || b2 == -1 || b3 == -1 || b4 == -1) {
                //if found "no data" just return null
                throw new Base64DecodingException("decoding.general");
            }

            os.write((byte)(b1 << 2 | b2 >> 4));
            os.write((byte)(((b2 & 0xf) << 4 ) | ((b3 >> 2) & 0xf)));
            os.write((byte)(b3 << 6 | b4));
        }   
        b1 = base64Alphabet[base64Data[dataIndex++]];
        b2 = base64Alphabet[base64Data[dataIndex++]];

        //  first last bits.
        if (b1 == -1 || b2 == -1) {
            //if found "no data" just return null
            throw new Base64DecodingException("decoding.general");
        }

        byte d3, d4;
        b3 = base64Alphabet[d3 = base64Data[dataIndex++]];
        b4 = base64Alphabet[d4 = base64Data[dataIndex++]];
        if (b3 == -1 || b4 == -1) { //Check if they are PAD characters
            if (isPad(d3) && isPad(d4)) {               //Two PAD e.g. 3c[Pad][Pad]
                if ((b2 & 0xf) != 0) { //last 4 bits should be zero
                    throw new Base64DecodingException("decoding.general");
                }
                os.write((byte)(b1 << 2 | b2 >> 4));                
            } else if (!isPad(d3) && isPad(d4)) {               //One PAD  e.g. 3cQ[Pad]             
                if ((b3 & 0x3 ) != 0) { //last 2 bits should be zero
                    throw new Base64DecodingException("decoding.general");
                }
                os.write((byte)(b1 << 2 | b2 >> 4));
                os.write((byte)(((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)));                
            } else {
                //an error  like "3c[Pad]r", "3cdX", "3cXd", "3cXX" where X is non data
                throw new Base64DecodingException("decoding.general");
            }
        } else {
            //No PAD e.g 3cQl         
            os.write((byte)(b1 << 2 | b2 >> 4));
            os.write( (byte)(((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)));
            os.write((byte)(b3 << 6 | b4));
        }
    }

    /**
     * Decodes Base64 data into  outputstream
     *
     * @param is containing Base64 data
     * @param os the outputstream
     * @throws IOException
     * @throws Base64DecodingException
     */
    public static final void decode(InputStream is, OutputStream os) 
        throws Base64DecodingException, IOException {
        //byte decodedData[] = null;
        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;    

        int index = 0;
        byte[] data = new byte[4];
        int read;
        //the begin
        while ((read = is.read()) > 0) {
            byte readed = (byte)read;
            if (isWhiteSpace(readed)) {
                continue;
            }
            if (isPad(readed)) {
                data[index++] = readed;
                if (index == 3) {
                    data[index++] = (byte)is.read();
                }
                break;   
            }

            if ((data[index++] = readed) == -1) {
                //if found "no data" just return null
                throw new Base64DecodingException("decoding.general");
            } 

            if (index != 4) {
                continue;
            }
            index = 0;
            b1 = base64Alphabet[data[0]];
            b2 = base64Alphabet[data[1]];
            b3 = base64Alphabet[data[2]];
            b4 = base64Alphabet[data[3]];

            os.write((byte)(b1 << 2 | b2 >> 4));
            os.write((byte)(((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)));
            os.write((byte)(b3 << 6 | b4));
        }       

        byte d1 = data[0], d2 = data[1], d3 = data[2], d4 = data[3];
        b1 = base64Alphabet[d1];
        b2 = base64Alphabet[d2];
        b3 = base64Alphabet[d3];
        b4 = base64Alphabet[d4];
        if (b3 == -1 || b4 == -1) { //Check if they are PAD characters
            if (isPad(d3) && isPad(d4)) {               //Two PAD e.g. 3c[Pad][Pad]
                if ((b2 & 0xf) != 0) { //last 4 bits should be zero
                    throw new Base64DecodingException("decoding.general");
                }
                os.write((byte)(b1 << 2 | b2 >> 4));                
            } else if (!isPad(d3) && isPad(d4)) {               //One PAD  e.g. 3cQ[Pad]
                b3 = base64Alphabet[d3];
                if ((b3 & 0x3) != 0) { //last 2 bits should be zero
                    throw new Base64DecodingException("decoding.general");
                }
                os.write((byte)(b1 << 2 | b2 >> 4));
                os.write((byte)(((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)));                
            } else {
                //an error  like "3c[Pad]r", "3cdX", "3cXd", "3cXX" where X is non data
                throw new Base64DecodingException("decoding.general");
            }
        } else {
            //No PAD e.g 3cQl         
            os.write((byte)(b1 << 2 | b2 >> 4));
            os.write((byte)(((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)));
            os.write((byte)(b3 << 6 | b4));
        }
    }

    /**
     * remove WhiteSpace from MIME containing encoded Base64 data.
     * 
     * @param data  the byte array of base64 data (with WS)
     * @return      the new length
     */
    protected static final int removeWhiteSpace(byte[] data) {
        if (data == null) {
            return 0;
        }

        // count characters that's not whitespace
        int newSize = 0;
        int len = data.length;
        for (int i = 0; i < len; i++) {
            byte dataS = data[i];
            if (!isWhiteSpace(dataS)) {
                data[newSize++] = dataS;
            }
        }
        return newSize;
    }
}