alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (DigestMD5Base.java)

This example Java source code file (DigestMD5Base.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

bytearrayoutputstream, crypto, digest-md5, empty_byte_array, error, illegalstateexception, invalid, kic, kis, log, logging, math, nosuchalgorithmexception, rc4, saslexception, security, skip, string, unsupportedencodingexception, util

The DigestMD5Base.java Java example source code

/*
 * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.security.sasl.digest;

import java.util.Map;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.math.BigInteger;
import java.util.Random;

import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.io.IOException;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import java.security.spec.KeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.InvalidAlgorithmParameterException;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.Mac;
import javax.crypto.SecretKeyFactory;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.DESedeKeySpec;

import javax.security.sasl.*;
import com.sun.security.sasl.util.AbstractSaslImpl;

import javax.security.auth.callback.CallbackHandler;

/**
 * Utility class for DIGEST-MD5 mechanism. Provides utility methods
 * and contains two inner classes which implement the SecurityCtx
 * interface. The inner classes provide the funtionality to allow
 * for quality-of-protection (QOP) with integrity checking and
 * privacy.
 *
 * @author Jonathan Bruce
 * @author Rosanna Lee
 */
abstract class DigestMD5Base extends AbstractSaslImpl {
    /* ------------------------- Constants ------------------------ */

    // Used for logging
    private static final String DI_CLASS_NAME = DigestIntegrity.class.getName();
    private static final String DP_CLASS_NAME = DigestPrivacy.class.getName();

    /* Constants - defined in RFC2831 */
    protected static final int MAX_CHALLENGE_LENGTH = 2048;
    protected static final int MAX_RESPONSE_LENGTH = 4096;
    protected static final int DEFAULT_MAXBUF = 65536;

    /* Supported ciphers for 'auth-conf' */
    protected static final int DES3 = 0;
    protected static final int RC4 = 1;
    protected static final int DES = 2;
    protected static final int RC4_56 = 3;
    protected static final int RC4_40 = 4;
    protected static final String[] CIPHER_TOKENS = { "3des",
                                                      "rc4",
                                                      "des",
                                                      "rc4-56",
                                                      "rc4-40" };
    private static final String[] JCE_CIPHER_NAME = {
        "DESede/CBC/NoPadding",
        "RC4",
        "DES/CBC/NoPadding",
    };

    /*
     * If QOP is set to 'auth-conf', a DIGEST-MD5 mechanism must have
     * support for the DES and Triple DES cipher algorithms (optionally,
     * support for RC4 [128/56/40 bit keys] ciphers) to provide for
     * confidentiality. See RFC 2831 for details. This implementation
     * provides support for DES, Triple DES and RC4 ciphers.
     *
     * The value of strength effects the strength of cipher used. The mappings
     * of 'high', 'medium', and 'low' give the following behaviour.
     *
     *  HIGH_STRENGTH   - Triple DES
     *                  - RC4 (128bit)
     *  MEDIUM_STRENGTH - DES
     *                  - RC4 (56bit)
     *  LOW_SRENGTH     - RC4 (40bit)
     */
    protected static final byte DES_3_STRENGTH = HIGH_STRENGTH;
    protected static final byte RC4_STRENGTH = HIGH_STRENGTH;
    protected static final byte DES_STRENGTH = MEDIUM_STRENGTH;
    protected static final byte RC4_56_STRENGTH = MEDIUM_STRENGTH;
    protected static final byte RC4_40_STRENGTH = LOW_STRENGTH;
    protected static final byte UNSET = (byte)0;
    protected static final byte[] CIPHER_MASKS = { DES_3_STRENGTH,
                                                   RC4_STRENGTH,
                                                   DES_STRENGTH,
                                                   RC4_56_STRENGTH,
                                                   RC4_40_STRENGTH };

    private static final String SECURITY_LAYER_MARKER =
        ":00000000000000000000000000000000";

    protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0];

    /* ------------------- Variable Fields ----------------------- */

    /* Used to track progress of authentication; step numbers from RFC 2831 */
    protected int step;

    /* Used to get username/password, choose realm for client */
    /* Used to obtain authorization, pw info, canonicalized authzid for server */
    protected CallbackHandler cbh;

    protected SecurityCtx secCtx;
    protected byte[] H_A1; // component of response-value

    protected byte[] nonce;         // server generated nonce

    /* Variables set when parsing directives in digest challenge/response. */
    protected String negotiatedStrength;
    protected String negotiatedCipher;
    protected String negotiatedQop;
    protected String negotiatedRealm;
    protected boolean useUTF8 = false;
    protected String encoding = "8859_1";  // default unless server specifies utf-8

    protected String digestUri;
    protected String authzid;       // authzid or canonicalized authzid

    /**
     * Constucts an instance of DigestMD5Base. Calls super constructor
     * to parse properties for mechanism.
     *
     * @param props A map of property/value pairs
     * @param className name of class to use for logging
     * @param firstStep number of first step in authentication state machine
     * @param digestUri digestUri used in authentication
     * @param cbh callback handler used to get info required for auth
     *
     * @throws SaslException If invalid value found in props.
     */
    protected DigestMD5Base(Map<String, ?> props, String className,
        int firstStep, String digestUri, CallbackHandler cbh)
        throws SaslException {
        super(props, className); // sets QOP, STENGTH and BUFFER_SIZE

        step = firstStep;
        this.digestUri = digestUri;
        this.cbh = cbh;
    }

    /**
     * Retrieves the SASL mechanism IANA name.
     *
     * @return The String "DIGEST-MD5"
     */
    public String getMechanismName() {
        return "DIGEST-MD5";
    }

    /**
     * Unwrap the incoming message using the wrap method of the secCtx object
     * instance.
     *
     * @param incoming The byte array containing the incoming bytes.
     * @param start The offset from which to read the byte array.
     * @param len The number of bytes to read from the offset.
     * @return The unwrapped message according to either the integrity or
     * privacy quality-of-protection specifications.
     * @throws SaslException if an error occurs when unwrapping the incoming
     * message
     */
    public byte[] unwrap(byte[] incoming, int start, int len) throws SaslException {
        if (!completed) {
            throw new IllegalStateException(
                "DIGEST-MD5 authentication not completed");
        }

        if (secCtx == null) {
            throw new IllegalStateException(
                "Neither integrity nor privacy was negotiated");
        }

        return (secCtx.unwrap(incoming, start, len));
    }

    /**
     * Wrap outgoing bytes using the wrap method of the secCtx object
     * instance.
     *
     * @param outgoing The byte array containing the outgoing bytes.
     * @param start The offset from which to read the byte array.
     * @param len The number of bytes to read from the offset.
     * @return The wrapped message according to either the integrity or
     * privacy quality-of-protection specifications.
     * @throws SaslException if an error occurs when wrapping the outgoing
     * message
     */
    public byte[] wrap(byte[] outgoing, int start, int len) throws SaslException {
        if (!completed) {
            throw new IllegalStateException(
                "DIGEST-MD5 authentication not completed");
        }

        if (secCtx == null) {
            throw new IllegalStateException(
                "Neither integrity nor privacy was negotiated");
        }

        return (secCtx.wrap(outgoing, start, len));
    }

    public void dispose() throws SaslException {
        if (secCtx != null) {
            secCtx = null;
        }
    }

    public Object getNegotiatedProperty(String propName) {
        if (completed) {
            if (propName.equals(Sasl.STRENGTH)) {
                return negotiatedStrength;
            } else if (propName.equals(Sasl.BOUND_SERVER_NAME)) {
                return digestUri.substring(digestUri.indexOf('/') + 1);
            } else {
                return super.getNegotiatedProperty(propName);
            }
        } else {
            throw new IllegalStateException(
                "DIGEST-MD5 authentication not completed");
        }
    }

    /* ----------------- Digest-MD5 utilities ---------------- */
    /**
     * Generate random-string used for digest-response.
     * This method uses Random to get random bytes and then
     * base64 encodes the bytes. Could also use binaryToHex() but this
     * is slightly faster and a more compact representation of the same info.
     * @return A non-null byte array containing the nonce value for the
     * digest challenge or response.
     * Could use SecureRandom to be more secure but it is very slow.
     */

    /** This array maps the characters to their 6 bit values */
    private final static char pem_array[] = {
        //       0   1   2   3   4   5   6   7
                'A','B','C','D','E','F','G','H', // 0
                'I','J','K','L','M','N','O','P', // 1
                'Q','R','S','T','U','V','W','X', // 2
                'Y','Z','a','b','c','d','e','f', // 3
                'g','h','i','j','k','l','m','n', // 4
                'o','p','q','r','s','t','u','v', // 5
                'w','x','y','z','0','1','2','3', // 6
                '4','5','6','7','8','9','+','/'  // 7
    };

    // Make sure that this is a multiple of 3
    private static final int RAW_NONCE_SIZE = 30;

    // Base 64 encoding turns each 3 bytes into 4
    private static final int ENCODED_NONCE_SIZE = RAW_NONCE_SIZE*4/3;

    protected static final byte[] generateNonce() {

        // SecureRandom random = new SecureRandom();
        Random random = new Random();
        byte[] randomData = new byte[RAW_NONCE_SIZE];
        random.nextBytes(randomData);

        byte[] nonce = new byte[ENCODED_NONCE_SIZE];

        // Base64-encode bytes
        byte a, b, c;
        int j = 0;
        for (int i = 0; i < randomData.length; i += 3) {
            a = randomData[i];
            b = randomData[i+1];
            c = randomData[i+2];
            nonce[j++] = (byte)(pem_array[(a >>> 2) & 0x3F]);
            nonce[j++] = (byte)(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
            nonce[j++] = (byte)(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
            nonce[j++] = (byte)(pem_array[c & 0x3F]);
        }

        return nonce;

        // %%% For testing using RFC 2831 example, uncomment the following 2 lines
        // System.out.println("!!!Using RFC 2831's cnonce for testing!!!");
        // return "OA6MHXh6VqTrRk".getBytes();
    }

    /**
     * Checks if a byte[] contains characters that must be quoted
     * and write the resulting, possibly escaped, characters to out.
     */
    protected static void writeQuotedStringValue(ByteArrayOutputStream out,
        byte[] buf) {

        int len = buf.length;
        byte ch;
        for (int i = 0; i < len; i++) {
            ch = buf[i];
            if (needEscape((char)ch)) {
                out.write('\\');
            }
            out.write(ch);
        }
    }

    // See Section 7.2 of RFC 2831; double-quote character is not allowed
    // unless escaped; also escape the escape character and CTL chars except LWS
    private static boolean needEscape(String str) {
        int len = str.length();
        for (int i = 0; i < len; i++) {
            if (needEscape(str.charAt(i))) {
                return true;
            }
        }
        return false;
    }

    // Determines whether a character needs to be escaped in a quoted string
    private static boolean needEscape(char ch) {
        return ch == '"' ||  // escape char
            ch == '\\' ||    // quote
            ch == 127 ||     // DEL

            // 0 <= ch <= 31 except CR, HT and LF
            (ch >= 0 && ch <= 31 && ch != 13 && ch != 9 && ch != 10);
    }

    protected static String quotedStringValue(String str) {
        if (needEscape(str)) {
            int len = str.length();
            char[] buf = new char[len+len];
            int j = 0;
            char ch;
            for (int i = 0; i < len; i++) {
                ch = str.charAt(i);
                if (needEscape(ch)) {
                    buf[j++] =  '\\';
                }
                buf[j++] = ch;
            }
            return new String(buf, 0, j);
        } else {
            return str;
        }
    }

    /**
     * Convert a byte array to hexadecimal string.
     *
     * @param a non-null byte array
     * @return a non-null String contain the HEX value
     */
    protected byte[] binaryToHex(byte[] digest) throws
    UnsupportedEncodingException {

        StringBuffer digestString = new StringBuffer();

        for (int i = 0; i < digest.length; i ++) {
            if ((digest[i] & 0x000000ff) < 0x10) {
                digestString.append("0"+
                    Integer.toHexString(digest[i] & 0x000000ff));
            } else {
                digestString.append(
                    Integer.toHexString(digest[i] & 0x000000ff));
            }
        }
        return digestString.toString().getBytes(encoding);
    }

    /**
     * Used to convert username-value, passwd or realm to 8859_1 encoding
     * if all chars in string are within the 8859_1 (Latin 1) encoding range.
     *
     * @param a non-null String
     * @return a non-nuill byte array containing the correct character encoding
     * for username, paswd or realm.
     */
    protected byte[] stringToByte_8859_1(String str) throws SaslException {

        char[] buffer = str.toCharArray();

        try {
            if (useUTF8) {
                for( int i = 0; i< buffer.length; i++ ) {
                    if( buffer[i] > '\u00FF' ) {
                        return str.getBytes("UTF8");
                    }
                }
            }
            return str.getBytes("8859_1");
        } catch (UnsupportedEncodingException e) {
            throw new SaslException(
                "cannot encode string in UTF8 or 8859-1 (Latin-1)", e);
        }
    }

    protected static byte[] getPlatformCiphers() {
        byte[] ciphers = new byte[CIPHER_TOKENS.length];

        for (int i = 0; i < JCE_CIPHER_NAME.length; i++) {
            try {
                // Checking whether the transformation is available from the
                // current installed providers.
                Cipher.getInstance(JCE_CIPHER_NAME[i]);

                logger.log(Level.FINE, "DIGEST01:Platform supports {0}", JCE_CIPHER_NAME[i]);
                ciphers[i] |= CIPHER_MASKS[i];
            } catch (NoSuchAlgorithmException e) {
                // no implementation found for requested algorithm.
            } catch (NoSuchPaddingException e) {
                // no implementation found for requested algorithm.
            }
        }

        if (ciphers[RC4] != UNSET) {
            ciphers[RC4_56] |= CIPHER_MASKS[RC4_56];
            ciphers[RC4_40] |= CIPHER_MASKS[RC4_40];
        }

        return ciphers;
    }

    /**
     * Assembles response-value for digest-response.
     *
     * @param authMethod "AUTHENTICATE" for client-generated response;
     *        "" for server-generated response
     * @return A non-null byte array containing the repsonse-value.
     * @throws NoSuchAlgorithmException if the platform does not have MD5
     * digest support.
     * @throws UnsupportedEncodingException if a an error occurs
     * encoding a string into either Latin-1 or UTF-8.
     * @throws IOException if an error occurs writing to the output
     * byte array buffer.
     */
    protected byte[] generateResponseValue(
        String authMethod,
        String digestUriValue,
        String qopValue,
        String usernameValue,
        String realmValue,
        char[] passwdValue,
        byte[] nonceValue,
        byte[] cNonceValue,
        int nonceCount,
        byte[] authzidValue
        ) throws NoSuchAlgorithmException,
            UnsupportedEncodingException,
            IOException {

        MessageDigest md5 = MessageDigest.getInstance("MD5");
        byte[] hexA1, hexA2;
        ByteArrayOutputStream A2, beginA1, A1, KD;

        // A2
        // --
        // A2 = { "AUTHENTICATE:", digest-uri-value,
        // [:00000000000000000000000000000000] }  // if auth-int or auth-conf
        //
        A2 = new ByteArrayOutputStream();
        A2.write((authMethod + ":" + digestUriValue).getBytes(encoding));
        if (qopValue.equals("auth-conf") ||
            qopValue.equals("auth-int")) {

            logger.log(Level.FINE, "DIGEST04:QOP: {0}", qopValue);

            A2.write(SECURITY_LAYER_MARKER.getBytes(encoding));
        }

        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "DIGEST05:A2: {0}", A2.toString());
        }

        md5.update(A2.toByteArray());
        byte[] digest = md5.digest();
        hexA2 = binaryToHex(digest);

        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "DIGEST06:HEX(H(A2)): {0}", new String(hexA2));
        }

        // A1
        // --
        // H(user-name : realm-value : passwd)
        //
        beginA1 = new ByteArrayOutputStream();
        beginA1.write(stringToByte_8859_1(usernameValue));
        beginA1.write(':');
        // if no realm, realm will be an empty string
        beginA1.write(stringToByte_8859_1(realmValue));
        beginA1.write(':');
        beginA1.write(stringToByte_8859_1(new String(passwdValue)));

        md5.update(beginA1.toByteArray());
        digest = md5.digest();

        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "DIGEST07:H({0}) = {1}",
                new Object[]{beginA1.toString(), new String(binaryToHex(digest))});
        }

        // A1
        // --
        // A1 = { H ( {user-name : realm-value : passwd } ),
        // : nonce-value, : cnonce-value : authzid-value
        //
        A1 = new ByteArrayOutputStream();
        A1.write(digest);
        A1.write(':');
        A1.write(nonceValue);
        A1.write(':');
        A1.write(cNonceValue);

        if (authzidValue != null) {
            A1.write(':');
            A1.write(authzidValue);
        }
        md5.update(A1.toByteArray());
        digest = md5.digest();
        H_A1 = digest; // Record H(A1). Use for integrity & privacy.
        hexA1 = binaryToHex(digest);

        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "DIGEST08:H(A1) = {0}", new String(hexA1));
        }

        //
        // H(k, : , s);
        //
        KD = new ByteArrayOutputStream();
        KD.write(hexA1);
        KD.write(':');
        KD.write(nonceValue);
        KD.write(':');
        KD.write(nonceCountToHex(nonceCount).getBytes(encoding));
        KD.write(':');
        KD.write(cNonceValue);
        KD.write(':');
        KD.write(qopValue.getBytes(encoding));
        KD.write(':');
        KD.write(hexA2);

        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "DIGEST09:KD: {0}", KD.toString());
        }

        md5.update(KD.toByteArray());
        digest = md5.digest();

        byte[] answer = binaryToHex(digest);

        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "DIGEST10:response-value: {0}",
                new String(answer));
        }
        return (answer);
    }

    /**
     * Takes 'nonceCount' value and returns HEX value of the value.
     *
     * @return A non-null String representing the current NONCE-COUNT
     */
    protected static String nonceCountToHex(int count) {

        String str = Integer.toHexString(count);
        StringBuffer pad = new StringBuffer();

        if (str.length() < 8) {
            for (int i = 0; i < 8-str.length(); i ++) {
                pad.append("0");
            }
        }

        return pad.toString() + str;
    }

    /**
     * Parses digest-challenge string, extracting each token
     * and value(s)
     *
     * @param buf A non-null digest-challenge string.
     * @param multipleAllowed true if multiple qop or realm or QOP directives
     *  are allowed.
     * @throws SaslException if the buf cannot be parsed according to RFC 2831
     */
    protected static byte[][] parseDirectives(byte[] buf,
        String[]keyTable, List<byte[]> realmChoices, int realmIndex) throws SaslException {

        byte[][] valueTable = new byte[keyTable.length][];

        ByteArrayOutputStream key = new ByteArrayOutputStream(10);
        ByteArrayOutputStream value = new ByteArrayOutputStream(10);
        boolean gettingKey = true;
        boolean gettingQuotedValue = false;
        boolean expectSeparator = false;
        byte bch;

        int i = skipLws(buf, 0);
        while (i < buf.length) {
            bch = buf[i];

            if (gettingKey) {
                if (bch == ',') {
                    if (key.size() != 0) {
                        throw new SaslException("Directive key contains a ',':" +
                            key);
                    }
                    // Empty element, skip separator and lws
                    i = skipLws(buf, i+1);

                } else if (bch == '=') {
                    if (key.size() == 0) {
                        throw new SaslException("Empty directive key");
                    }
                    gettingKey = false;      // Termination of key
                    i = skipLws(buf, i+1);   // Skip to next nonwhitespace

                    // Check whether value is quoted
                    if (i < buf.length) {
                        if (buf[i] == '"') {
                            gettingQuotedValue = true;
                            ++i; // Skip quote
                        }
                    } else {
                        throw new SaslException(
                            "Valueless directive found: " + key.toString());
                    }
                } else if (isLws(bch)) {
                    // LWS that occurs after key
                    i = skipLws(buf, i+1);

                    // Expecting '='
                    if (i < buf.length) {
                        if (buf[i] != '=') {
                            throw new SaslException("'=' expected after key: " +
                                key.toString());
                        }
                    } else {
                        throw new SaslException(
                            "'=' expected after key: " + key.toString());
                    }
                } else {
                    key.write(bch);    // Append to key
                    ++i;               // Advance
                }
            } else if (gettingQuotedValue) {
                // Getting a quoted value
                if (bch == '\\') {
                    // quoted-pair = "\" CHAR  ==> CHAR
                    ++i;       // Skip escape
                    if (i < buf.length) {
                        value.write(buf[i]);
                        ++i;   // Advance
                    } else {
                        // Trailing escape in a quoted value
                        throw new SaslException(
                            "Unmatched quote found for directive: "
                            + key.toString() + " with value: " + value.toString());
                    }
                } else if (bch == '"') {
                    // closing quote
                    ++i;  // Skip closing quote
                    gettingQuotedValue = false;
                    expectSeparator = true;
                } else {
                    value.write(bch);
                    ++i;  // Advance
                }

            } else if (isLws(bch) || bch == ',') {
                //  Value terminated

                extractDirective(key.toString(), value.toByteArray(),
                    keyTable, valueTable, realmChoices, realmIndex);
                key.reset();
                value.reset();
                gettingKey = true;
                gettingQuotedValue = expectSeparator = false;
                i = skipLws(buf, i+1);   // Skip separator and LWS

            } else if (expectSeparator) {
                throw new SaslException(
                    "Expecting comma or linear whitespace after quoted string: \""
                        + value.toString() + "\"");
            } else {
                value.write(bch);   // Unquoted value
                ++i;                // Advance
            }
        }

        if (gettingQuotedValue) {
            throw new SaslException(
                "Unmatched quote found for directive: " + key.toString() +
                " with value: " + value.toString());
        }

        // Get last pair
        if (key.size() > 0) {
            extractDirective(key.toString(), value.toByteArray(),
                keyTable, valueTable, realmChoices, realmIndex);
        }

        return valueTable;
    }

    // Is character a linear white space?
    // LWS            = [CRLF] 1*( SP | HT )
    // %%% Note that we're checking individual bytes instead of CRLF
    private static boolean isLws(byte b) {
        switch (b) {
        case 13:   // US-ASCII CR, carriage return
        case 10:   // US-ASCII LF, linefeed
        case 32:   // US-ASCII SP, space
        case 9:    // US-ASCII HT, horizontal-tab
            return true;
        }
        return false;
    }

    // Skip all linear white spaces
    private static int skipLws(byte[] buf, int start) {
        int i;
        for (i = start; i < buf.length; i++) {
            if (!isLws(buf[i])) {
                return i;
            }
        }
        return i;
    }

    /**
     * Processes directive/value pairs from the digest-challenge and
     * fill out the challengeVal array.
     *
     * @param key A non-null String challenge token name.
     * @param value A non-null String token value.
     * @throws SaslException if a either the key or the value is null
     */
    private static void  extractDirective(String key, byte[] value,
        String[] keyTable, byte[][] valueTable,
        List<byte[]> realmChoices, int realmIndex) throws SaslException {

        for (int i = 0; i < keyTable.length; i++) {
            if (key.equalsIgnoreCase(keyTable[i])) {
                if (valueTable[i] == null) {
                    valueTable[i] = value;
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "DIGEST11:Directive {0} = {1}",
                            new Object[]{
                                keyTable[i],
                                new String(valueTable[i])});
                    }
                } else if (realmChoices != null && i == realmIndex) {
                    // > 1 realm specified
                    if (realmChoices.isEmpty()) {
                        realmChoices.add(valueTable[i]); // add existing one
                    }
                    realmChoices.add(value);  // add new one
                } else {
                    throw new SaslException(
                        "DIGEST-MD5: peer sent more than one " +
                        key + " directive: " + new String(value));
                }

                break; // end search
            }
        }
     }


    /**
     * Implementation of the SecurityCtx interface allowing for messages
     * between the client and server to be integrity checked. After a
     * successful DIGEST-MD5 authentication, integtrity checking is invoked
     * if the SASL QOP (quality-of-protection) is set to 'auth-int'.
     * <p>
     * Further details on the integrity-protection mechanism can be found
     * at section 2.3 - Integrity protection in the
     * <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC2831 definition.
     *
     * @author Jonathan Bruce
     */
    class DigestIntegrity implements SecurityCtx {
        /* Used for generating integrity keys - specified in RFC 2831*/
        static final private String CLIENT_INT_MAGIC = "Digest session key to " +
            "client-to-server signing key magic constant";
        static final private String SVR_INT_MAGIC = "Digest session key to " +
            "server-to-client signing key magic constant";

        /* Key pairs for integrity checking */
        protected byte[] myKi;     // == Kic for client; == Kis for server
        protected byte[] peerKi;   // == Kis for client; == Kic for server

        protected int mySeqNum = 0;
        protected int peerSeqNum = 0;

        // outgoing messageType and sequenceNum
        protected final byte[] messageType = new byte[2];
        protected final byte[] sequenceNum = new byte[4];

        /**
         * Initializes DigestIntegrity implementation of SecurityCtx to
         * enable DIGEST-MD5 integrity checking.
         *
         * @throws SaslException if an error is encountered generating the
         * key-pairs for integrity checking.
         */
        DigestIntegrity(boolean clientMode) throws SaslException {
            /* Initialize magic strings */

            try {
                generateIntegrityKeyPair(clientMode);

            } catch (UnsupportedEncodingException e) {
                throw new SaslException(
                    "DIGEST-MD5: Error encoding strings into UTF-8", e);

            } catch (IOException e) {
                throw new SaslException("DIGEST-MD5: Error accessing buffers " +
                    "required to create integrity key pairs", e);

            } catch (NoSuchAlgorithmException e) {
                throw new SaslException("DIGEST-MD5: Unsupported digest " +
                    "algorithm used to create integrity key pairs", e);
            }

            /* Message type is a fixed value */
            intToNetworkByteOrder(1, messageType, 0, 2);
        }

        /**
         * Generate client-server, server-client key pairs for DIGEST-MD5
         * integrity checking.
         *
         * @throws UnsupportedEncodingException if the UTF-8 encoding is not
         * supported on the platform.
         * @throws IOException if an error occurs when writing to or from the
         * byte array output buffers.
         * @throws NoSuchAlgorithmException if the MD5 message digest algorithm
         * cannot loaded.
         */
        private void generateIntegrityKeyPair(boolean clientMode)
            throws UnsupportedEncodingException, IOException,
                NoSuchAlgorithmException {

            byte[] cimagic = CLIENT_INT_MAGIC.getBytes(encoding);
            byte[] simagic = SVR_INT_MAGIC.getBytes(encoding);

            MessageDigest md5 = MessageDigest.getInstance("MD5");

            // Both client-magic-keys and server-magic-keys are the same length
            byte[] keyBuffer = new byte[H_A1.length + cimagic.length];

            // Kic: Key for protecting msgs from client to server.
            System.arraycopy(H_A1, 0, keyBuffer, 0, H_A1.length);
            System.arraycopy(cimagic, 0, keyBuffer, H_A1.length, cimagic.length);
            md5.update(keyBuffer);
            byte[] Kic = md5.digest();

            // Kis: Key for protecting msgs from server to client
            // No need to recopy H_A1
            System.arraycopy(simagic, 0, keyBuffer, H_A1.length, simagic.length);

            md5.update(keyBuffer);
            byte[] Kis = md5.digest();

            if (logger.isLoggable(Level.FINER)) {
                traceOutput(DI_CLASS_NAME, "generateIntegrityKeyPair",
                    "DIGEST12:Kic: ", Kic);
                traceOutput(DI_CLASS_NAME, "generateIntegrityKeyPair",
                    "DIGEST13:Kis: ", Kis);
            }

            if (clientMode) {
                myKi = Kic;
                peerKi = Kis;
            } else {
                myKi = Kis;
                peerKi = Kic;
            }
        }

        /**
         * Append MAC onto outgoing message.
         *
         * @param outgoing A non-null byte array containing the outgoing message.
         * @param start The offset from which to read the byte array.
         * @param len The non-zero number of bytes for be read from the offset.
         * @return The message including the integrity MAC
         * @throws SaslException if an error is encountered converting a string
         * into a UTF-8 byte encoding, or if the MD5 message digest algorithm
         * cannot be found or if there is an error writing to the byte array
         * output buffers.
         */
        public byte[] wrap(byte[] outgoing, int start, int len)
            throws SaslException {

            if (len == 0) {
                return EMPTY_BYTE_ARRAY;
            }

            /* wrapped = message, MAC, message type, sequence number */
            byte[] wrapped = new byte[len+10+2+4];

            /* Start with message itself */
            System.arraycopy(outgoing, start, wrapped, 0, len);

            incrementSeqNum();

            /* Calculate MAC */
            byte[] mac = getHMAC(myKi, sequenceNum, outgoing, start, len);

            if (logger.isLoggable(Level.FINEST)) {
                traceOutput(DI_CLASS_NAME, "wrap", "DIGEST14:outgoing: ",
                    outgoing, start, len);
                traceOutput(DI_CLASS_NAME, "wrap", "DIGEST15:seqNum: ",
                    sequenceNum);
                traceOutput(DI_CLASS_NAME, "wrap", "DIGEST16:MAC: ", mac);
            }

            /* Add MAC[0..9] to message */
            System.arraycopy(mac, 0, wrapped, len, 10);

            /* Add message type [0..1] */
            System.arraycopy(messageType, 0, wrapped, len+10, 2);

            /* Add sequence number [0..3] */
            System.arraycopy(sequenceNum, 0, wrapped, len+12, 4);
            if (logger.isLoggable(Level.FINEST)) {
                traceOutput(DI_CLASS_NAME, "wrap", "DIGEST17:wrapped: ", wrapped);
            }
            return wrapped;
        }

        /**
         * Return verified message without MAC - only if the received MAC
         * and re-generated MAC are the same.
         *
         * @param incoming A non-null byte array containing the incoming
         * message.
         * @param start The offset from which to read the byte array.
         * @param len The non-zero number of bytes to read from the offset
         * position.
         * @return The verified message or null if integrity checking fails.
         * @throws SaslException if an error is encountered converting a string
         * into a UTF-8 byte encoding, or if the MD5 message digest algorithm
         * cannot be found or if there is an error writing to the byte array
         * output buffers
         */
        public byte[] unwrap(byte[] incoming, int start, int len)
            throws SaslException {

            if (len == 0) {
                return EMPTY_BYTE_ARRAY;
            }

            // shave off last 16 bytes of message
            byte[] mac = new byte[10];
            byte[] msg = new byte[len - 16];
            byte[] msgType = new byte[2];
            byte[] seqNum = new byte[4];

            /* Get Msg, MAC, msgType, sequenceNum */
            System.arraycopy(incoming, start, msg, 0, msg.length);
            System.arraycopy(incoming, start+msg.length, mac, 0, 10);
            System.arraycopy(incoming, start+msg.length+10, msgType, 0, 2);
            System.arraycopy(incoming, start+msg.length+12, seqNum, 0, 4);

            /* Calculate MAC to ensure integrity */
            byte[] expectedMac = getHMAC(peerKi, seqNum, msg, 0, msg.length);

            if (logger.isLoggable(Level.FINEST)) {
                traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST18:incoming: ",
                    msg);
                traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST19:MAC: ",
                    mac);
                traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST20:messageType: ",
                    msgType);
                traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST21:sequenceNum: ",
                    seqNum);
                traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST22:expectedMAC: ",
                    expectedMac);
            }

            /* First, compare MAC's before updating any of our state */
            if (!Arrays.equals(mac, expectedMac)) {
                //  Discard message and do not increment sequence number
                logger.log(Level.INFO, "DIGEST23:Unmatched MACs");
                return EMPTY_BYTE_ARRAY;
            }

            /* Ensure server-sequence numbers are correct */
            if (peerSeqNum != networkByteOrderToInt(seqNum, 0, 4)) {
                throw new SaslException("DIGEST-MD5: Out of order " +
                    "sequencing of messages from server. Got: " +
                    networkByteOrderToInt(seqNum, 0, 4) +
                    " Expected: " +     peerSeqNum);
            }

            if (!Arrays.equals(messageType, msgType)) {
                throw new SaslException("DIGEST-MD5: invalid message type: " +
                    networkByteOrderToInt(msgType, 0, 2));
            }

            // Increment sequence number and return message
            peerSeqNum++;
            return msg;
        }

        /**
         * Generates MAC to be appended onto out-going messages.
         *
         * @param Ki A non-null byte array containing the key for the digest
         * @param SeqNum A non-null byte array contain the sequence number
         * @param msg  The message to be digested
         * @param start The offset from which to read the msg byte array
         * @param len The non-zero number of bytes to be read from the offset
         * @return The MAC of a message.
         *
         * @throws SaslException if an error occurs when generating MAC.
         */
        protected byte[] getHMAC(byte[] Ki, byte[] seqnum, byte[] msg,
            int start, int len) throws SaslException {

            byte[] seqAndMsg = new byte[4+len];
            System.arraycopy(seqnum, 0, seqAndMsg, 0, 4);
            System.arraycopy(msg, start, seqAndMsg, 4, len);

            try {
                SecretKey keyKi = new SecretKeySpec(Ki, "HmacMD5");
                Mac m = Mac.getInstance("HmacMD5");
                m.init(keyKi);
                m.update(seqAndMsg);
                byte[] hMAC_MD5 = m.doFinal();

                /* First 10 bytes of HMAC_MD5 digest */
                byte macBuffer[] = new byte[10];
                System.arraycopy(hMAC_MD5, 0, macBuffer, 0, 10);

                return macBuffer;
            } catch (InvalidKeyException e) {
                throw new SaslException("DIGEST-MD5: Invalid bytes used for " +
                    "key of HMAC-MD5 hash.", e);
            } catch (NoSuchAlgorithmException e) {
                throw new SaslException("DIGEST-MD5: Error creating " +
                    "instance of MD5 digest algorithm", e);
            }
        }

        /**
         * Increment own sequence number and set answer in NBO sequenceNum field.
         */
        protected void incrementSeqNum() {
            intToNetworkByteOrder(mySeqNum++, sequenceNum, 0, 4);
        }
    }

    /**
     * Implementation of the SecurityCtx interface allowing for messages
     * between the client and server to be integrity checked and encrypted.
     * After a successful DIGEST-MD5 authentication, privacy is invoked if the
     * SASL QOP (quality-of-protection) is set to 'auth-conf'.
     * <p>
     * Further details on the integrity-protection mechanism can be found
     * at section 2.4 - Confidentiality protection in
     * <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC2831 definition.
     *
     * @author Jonathan Bruce
     */
    final class DigestPrivacy extends DigestIntegrity implements SecurityCtx {
        /* Used for generating privacy keys - specified in RFC 2831 */
        static final private String CLIENT_CONF_MAGIC =
            "Digest H(A1) to client-to-server sealing key magic constant";
        static final private String SVR_CONF_MAGIC =
            "Digest H(A1) to server-to-client sealing key magic constant";

        private Cipher encCipher;
        private Cipher decCipher;

        /**
         * Initializes the cipher object instances for encryption and decryption.
         *
         * @throws SaslException if an error occurs with the Key
         * initialization, or a string cannot be encoded into a byte array
         * using the UTF-8 encoding, or an error occurs when writing to a
         * byte array output buffers or the mechanism cannot load the MD5
         * message digest algorithm or invalid initialization parameters are
         * passed to the cipher object instances.
         */
        DigestPrivacy(boolean clientMode) throws SaslException {

            super(clientMode); // generate Kic, Kis keys for integrity-checking.

            try {
                generatePrivacyKeyPair(clientMode);

            } catch (SaslException e) {
                throw e;

            } catch (UnsupportedEncodingException e) {
                throw new SaslException(
                    "DIGEST-MD5: Error encoding string value into UTF-8", e);

            } catch (IOException e) {
                throw new SaslException("DIGEST-MD5: Error accessing " +
                    "buffers required to generate cipher keys", e);
            } catch (NoSuchAlgorithmException e) {
                throw new SaslException("DIGEST-MD5: Error creating " +
                    "instance of required cipher or digest", e);
            }
        }

        /**
         * Generates client-server and server-client keys to encrypt and
         * decrypt messages. Also generates IVs for DES ciphers.
         *
         * @throws IOException if an error occurs when writing to or from the
         * byte array output buffers.
         * @throws NoSuchAlgorithmException if the MD5 message digest algorithm
         * cannot loaded.
         * @throws UnsupportedEncodingException if an UTF-8 encoding is not
         * supported on the platform.
         * @throw SaslException if an error occurs initializing the keys and
         * IVs for the chosen cipher.
         */
        private void generatePrivacyKeyPair(boolean clientMode)
            throws IOException, UnsupportedEncodingException,
            NoSuchAlgorithmException, SaslException {

            byte[] ccmagic = CLIENT_CONF_MAGIC.getBytes(encoding);
            byte[] scmagic = SVR_CONF_MAGIC.getBytes(encoding);

            /* Kcc = MD5{H(A1)[0..n], "Digest ... client-to-server"} */
            MessageDigest md5 = MessageDigest.getInstance("MD5");

            int n;
            if (negotiatedCipher.equals(CIPHER_TOKENS[RC4_40])) {
                n = 5;          /* H(A1)[0..5] */
            } else if (negotiatedCipher.equals(CIPHER_TOKENS[RC4_56])) {
                n = 7;          /* H(A1)[0..7] */
            } else { // des and 3des and rc4
                n = 16;         /* H(A1)[0..16] */
            }

            /* {H(A1)[0..n], "Digest ... client-to-server..."} */
            // Both client-magic-keys and server-magic-keys are the same length
            byte[] keyBuffer =  new byte[n + ccmagic.length];
            System.arraycopy(H_A1, 0, keyBuffer, 0, n);   // H(A1)[0..n]

            /* Kcc: Key for encrypting messages from client->server */
            System.arraycopy(ccmagic, 0, keyBuffer, n, ccmagic.length);
            md5.update(keyBuffer);
            byte[] Kcc = md5.digest();

            /* Kcs: Key for decrypting messages from server->client */
            // No need to copy H_A1 again since it hasn't changed
            System.arraycopy(scmagic, 0, keyBuffer, n, scmagic.length);
            md5.update(keyBuffer);
            byte[] Kcs = md5.digest();

            if (logger.isLoggable(Level.FINER)) {
                traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
                    "DIGEST24:Kcc: ", Kcc);
                traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
                    "DIGEST25:Kcs: ", Kcs);
            }

            byte[] myKc;
            byte[] peerKc;

            if (clientMode) {
                myKc = Kcc;
                peerKc = Kcs;
            } else {
                myKc = Kcs;
                peerKc = Kcc;
            }

            try {
                SecretKey encKey;
                SecretKey decKey;

                /* Initialize cipher objects */
                if (negotiatedCipher.indexOf(CIPHER_TOKENS[RC4]) > -1) {
                    encCipher = Cipher.getInstance("RC4");
                    decCipher = Cipher.getInstance("RC4");

                    encKey = new SecretKeySpec(myKc, "RC4");
                    decKey = new SecretKeySpec(peerKc, "RC4");

                    encCipher.init(Cipher.ENCRYPT_MODE, encKey);
                    decCipher.init(Cipher.DECRYPT_MODE, decKey);

                } else if ((negotiatedCipher.equals(CIPHER_TOKENS[DES])) ||
                    (negotiatedCipher.equals(CIPHER_TOKENS[DES3]))) {

                    // DES or 3DES
                    String cipherFullname, cipherShortname;

                        // Use "NoPadding" when specifying cipher names
                        // RFC 2831 already defines padding rules for producing
                        // 8-byte aligned blocks
                    if (negotiatedCipher.equals(CIPHER_TOKENS[DES])) {
                        cipherFullname = "DES/CBC/NoPadding";
                        cipherShortname = "des";
                    } else {
                        /* 3DES */
                        cipherFullname = "DESede/CBC/NoPadding";
                        cipherShortname = "desede";
                    }

                    encCipher = Cipher.getInstance(cipherFullname);
                    decCipher = Cipher.getInstance(cipherFullname);

                    encKey = makeDesKeys(myKc, cipherShortname);
                    decKey = makeDesKeys(peerKc, cipherShortname);

                    // Set up the DES IV, which is the last 8 bytes of Kcc/Kcs
                    IvParameterSpec encIv = new IvParameterSpec(myKc, 8, 8);
                    IvParameterSpec decIv = new IvParameterSpec(peerKc, 8, 8);

                    // Initialize cipher objects
                    encCipher.init(Cipher.ENCRYPT_MODE, encKey, encIv);
                    decCipher.init(Cipher.DECRYPT_MODE, decKey, decIv);

                    if (logger.isLoggable(Level.FINER)) {
                        traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
                            "DIGEST26:" + negotiatedCipher + " IVcc: ",
                            encIv.getIV());
                        traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
                            "DIGEST27:" + negotiatedCipher + " IVcs: ",
                            decIv.getIV());
                        traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
                            "DIGEST28:" + negotiatedCipher + " encryption key: ",
                            encKey.getEncoded());
                        traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
                            "DIGEST29:" + negotiatedCipher + " decryption key: ",
                            decKey.getEncoded());
                    }
                }
            } catch (InvalidKeySpecException e) {
                throw new SaslException("DIGEST-MD5: Unsupported key " +
                    "specification used.", e);
            } catch (InvalidAlgorithmParameterException e) {
                throw new SaslException("DIGEST-MD5: Invalid cipher " +
                    "algorithem parameter used to create cipher instance", e);
            } catch (NoSuchPaddingException e) {
                throw new SaslException("DIGEST-MD5: Unsupported " +
                    "padding used for chosen cipher", e);
            } catch (InvalidKeyException e) {
                throw new SaslException("DIGEST-MD5: Invalid data " +
                    "used to initialize keys", e);
            }
        }

        // -------------------------------------------------------------------

        /**
         * Encrypt out-going message.
         *
         * @param outgoing A non-null byte array containing the outgoing message.
         * @param start The offset from which to read the byte array.
         * @param len The non-zero number of bytes to be read from the offset.
         * @return The encrypted message.
         *
         * @throws SaslException if an error occurs when writing to or from the
         * byte array output buffers or if the MD5 message digest algorithm
         * cannot loaded or if an UTF-8 encoding is not supported on the
         * platform.
         */
        public byte[] wrap(byte[] outgoing, int start, int len)
            throws SaslException {

            if (len == 0) {
                return EMPTY_BYTE_ARRAY;
            }

            /* HMAC(Ki, {SeqNum, msg})[0..9] */
            incrementSeqNum();
            byte[] mac = getHMAC(myKi, sequenceNum, outgoing, start, len);

            if (logger.isLoggable(Level.FINEST)) {
                traceOutput(DP_CLASS_NAME, "wrap", "DIGEST30:Outgoing: ",
                    outgoing, start, len);
                traceOutput(DP_CLASS_NAME, "wrap", "seqNum: ",
                    sequenceNum);
                traceOutput(DP_CLASS_NAME, "wrap", "MAC: ", mac);
            }

            // Calculate padding
            int bs = encCipher.getBlockSize();
            byte[] padding;
            if (bs > 1 ) {
                int pad = bs - ((len + 10) % bs); // add 10 for HMAC[0..9]
                padding = new byte[pad];
                for (int i=0; i < pad; i++) {
                    padding[i] = (byte)pad;
                }
            } else {
                padding = EMPTY_BYTE_ARRAY;
            }

            byte[] toBeEncrypted = new byte[len+padding.length+10];

            /* {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])} */
            System.arraycopy(outgoing, start, toBeEncrypted, 0, len);
            System.arraycopy(padding, 0, toBeEncrypted, len, padding.length);
            System.arraycopy(mac, 0, toBeEncrypted, len+padding.length, 10);

            if (logger.isLoggable(Level.FINEST)) {
                traceOutput(DP_CLASS_NAME, "wrap",
                    "DIGEST31:{msg, pad, KicMAC}: ", toBeEncrypted);
            }

            /* CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])}) */
            byte[] cipherBlock;
            try {
                // Do CBC (chaining) across packets
                cipherBlock = encCipher.update(toBeEncrypted);

                if (cipherBlock == null) {
                    // update() can return null
                    throw new IllegalBlockSizeException(""+toBeEncrypted.length);
                }
            } catch (IllegalBlockSizeException e) {
                throw new SaslException(
                    "DIGEST-MD5: Invalid block size for cipher", e);
            }

            byte[] wrapped = new byte[cipherBlock.length+2+4];
            System.arraycopy(cipherBlock, 0, wrapped, 0, cipherBlock.length);
            System.arraycopy(messageType, 0, wrapped, cipherBlock.length, 2);
            System.arraycopy(sequenceNum, 0, wrapped, cipherBlock.length+2, 4);

            if (logger.isLoggable(Level.FINEST)) {
                traceOutput(DP_CLASS_NAME, "wrap", "DIGEST32:Wrapped: ", wrapped);
            }

            return wrapped;
        }

        /*
         * Decrypt incoming messages and verify their integrity.
         *
         * @param incoming A non-null byte array containing the incoming
         * encrypted message.
         * @param start The offset from which to read the byte array.
         * @param len The non-zero number of bytes to read from the offset
         * position.
         * @return The decrypted, verified message or null if integrity
         * checking
         * fails.
         * @throws SaslException if there are the SASL buffer is empty or if
         * if an error occurs reading the SASL buffer.
         */
        public byte[] unwrap(byte[] incoming, int start, int len)
            throws SaslException {

            if (len == 0) {
                return EMPTY_BYTE_ARRAY;
            }

            byte[] encryptedMsg = new byte[len - 6];
            byte[] msgType = new byte[2];
            byte[] seqNum = new byte[4];

            /* Get cipherMsg; msgType; sequenceNum */
            System.arraycopy(incoming, start,
                encryptedMsg, 0, encryptedMsg.length);
            System.arraycopy(incoming, start+encryptedMsg.length,
                msgType, 0, 2);
            System.arraycopy(incoming, start+encryptedMsg.length+2,
                seqNum, 0, 4);

            if (logger.isLoggable(Level.FINEST)) {
                logger.log(Level.FINEST,
                    "DIGEST33:Expecting sequence num: {0}",
                    new Integer(peerSeqNum));
                traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST34:incoming: ",
                    encryptedMsg);
            }

            // Decrypt message
            /* CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])}) */
            byte[] decryptedMsg;

            try {
                // Do CBC (chaining) across packets
                decryptedMsg = decCipher.update(encryptedMsg);

                if (decryptedMsg == null) {
                    // update() can return null
                    throw new IllegalBlockSizeException(""+encryptedMsg.length);
                }
            } catch (IllegalBlockSizeException e) {
                throw new SaslException("DIGEST-MD5: Illegal block " +
                    "sizes used with chosen cipher", e);
            }

            byte[] msgWithPadding = new byte[decryptedMsg.length - 10];
            byte[] mac = new byte[10];

            System.arraycopy(decryptedMsg, 0,
                msgWithPadding, 0, msgWithPadding.length);
            System.arraycopy(decryptedMsg, msgWithPadding.length,
                mac, 0, 10);

            if (logger.isLoggable(Level.FINEST)) {
                traceOutput(DP_CLASS_NAME, "unwrap",
                    "DIGEST35:Unwrapped (w/padding): ", msgWithPadding);
                traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST36:MAC: ", mac);
                traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST37:messageType: ",
                    msgType);
                traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST38:sequenceNum: ",
                    seqNum);
            }

            int msgLength = msgWithPadding.length;
            int blockSize = decCipher.getBlockSize();
            if (blockSize > 1) {
                // get value of last octet of the byte array
                msgLength -= (int)msgWithPadding[msgWithPadding.length - 1];
                if (msgLength < 0) {
                    //  Discard message and do not increment sequence number
                    if (logger.isLoggable(Level.INFO)) {
                        logger.log(Level.INFO,
                            "DIGEST39:Incorrect padding: {0}",
                            new Byte(msgWithPadding[msgWithPadding.length - 1]));
                    }
                    return EMPTY_BYTE_ARRAY;
                }
            }

            /* Re-calculate MAC to ensure integrity */
            byte[] expectedMac = getHMAC(peerKi, seqNum, msgWithPadding,
                0, msgLength);

            if (logger.isLoggable(Level.FINEST)) {
                traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST40:KisMAC: ",
                    expectedMac);
            }

            // First, compare MACs before updating state
            if (!Arrays.equals(mac, expectedMac)) {
                //  Discard message and do not increment sequence number
                logger.log(Level.INFO, "DIGEST41:Unmatched MACs");
                return EMPTY_BYTE_ARRAY;
            }

            /* Ensure sequence number is correct */
            if (peerSeqNum != networkByteOrderToInt(seqNum, 0, 4)) {
                throw new SaslException("DIGEST-MD5: Out of order " +
                    "sequencing of messages from server. Got: " +
                    networkByteOrderToInt(seqNum, 0, 4) + " Expected: " +
                    peerSeqNum);
            }

            /* Check message type */
            if (!Arrays.equals(messageType, msgType)) {
                throw new SaslException("DIGEST-MD5: invalid message type: " +
                    networkByteOrderToInt(msgType, 0, 2));
            }

            // Increment sequence number and return message
            peerSeqNum++;

            if (msgLength == msgWithPadding.length) {
                return msgWithPadding; // no padding
            } else {
                // Get a copy of the message without padding
                byte[] clearMsg = new byte[msgLength];
                System.arraycopy(msgWithPadding, 0, clearMsg, 0, msgLength);
                return clearMsg;
            }
        }
    }

    // ---------------- DES and 3 DES key manipulation routines

    private static final BigInteger MASK = new BigInteger("7f", 16);

    /**
     * Sets the parity bit (0th bit) in each byte so that each byte
     * contains an odd number of 1's.
     */
    private static void setParityBit(byte[] key) {
        for (int i = 0; i < key.length; i++) {
            int b = key[i] & 0xfe;
            b |= (Integer.bitCount(b) & 1) ^ 1;
            key[i] = (byte) b;
        }
    }

    /**
     * Expands a 7-byte array into an 8-byte array that contains parity bits
     * The binary format of a cryptographic key is:
     *     (B1,B2,...,B7,P1,B8,...B14,P2,B15,...,B49,P7,B50,...,B56,P8)
     * where (B1,B2,...,B56) are the independent bits of a DES key and
     * (PI,P2,...,P8) are reserved for parity bits computed on the preceding
     * seven independent bits and set so that the parity of the octet is odd,
     * i.e., there is an odd number of "1" bits in the octet.
     */
    private static byte[] addDesParity(byte[] input, int offset, int len) {
        if (len != 7)
            throw new IllegalArgumentException(
                "Invalid length of DES Key Value:" + len);

        byte[] raw = new byte[7];
        System.arraycopy(input, offset, raw, 0, len);

        byte[] result = new byte[8];
        BigInteger in = new BigInteger(raw);

        // Shift 7 bits each time into a byte
        for (int i=result.length-1; i>=0; i--) {
            result[i] = in.and(MASK).toByteArray()[0];
            result[i] <<= 1;         // make room for parity bit
            in = in.shiftRight(7);
        }
        setParityBit(result);
        return result;
    }

    /**
     * Create parity-adjusted keys suitable for DES / DESede encryption.
     *
     * @param input A non-null byte array containing key material for
     * DES / DESede.
     * @param desStrength A string specifying eithe a DES or a DESede key.
     * @return SecretKey An instance of either DESKeySpec or DESedeKeySpec.
     *
     * @throws NoSuchAlgorithmException if the either the DES or DESede
     * algorithms cannote be lodaed by JCE.
     * @throws InvalidKeyException if an invalid array of bytes is used
     * as a key for DES or DESede.
     * @throws InvalidKeySpecException in an invalid parameter is passed
     * to either te DESKeySpec of the DESedeKeySpec constructors.
     */
    private static SecretKey makeDesKeys(byte[] input, String desStrength)
        throws NoSuchAlgorithmException, InvalidKeyException,
            InvalidKeySpecException {

        // Generate first subkey using first 7 bytes
        byte[] subkey1 = addDesParity(input, 0, 7);

        KeySpec spec = null;
        SecretKeyFactory desFactory =
            SecretKeyFactory.getInstance(desStrength);
        switch (desStrength) {
            case "des":
                spec = new DESKeySpec(subkey1, 0);
                if (logger.isLoggable(Level.FINEST)) {
                    traceOutput(DP_CLASS_NAME, "makeDesKeys",
                        "DIGEST42:DES key input: ", input);
                    traceOutput(DP_CLASS_NAME, "makeDesKeys",
                        "DIGEST43:DES key parity-adjusted: ", subkey1);
                    traceOutput(DP_CLASS_NAME, "makeDesKeys",
                        "DIGEST44:DES key material: ", ((DESKeySpec)spec).getKey());
                    logger.log(Level.FINEST, "DIGEST45: is parity-adjusted? {0}",
                        Boolean.valueOf(DESKeySpec.isParityAdjusted(subkey1, 0)));
                }
                break;
            case "desede":
                // Generate second subkey using second 7 bytes
                byte[] subkey2 = addDesParity(input, 7, 7);
                // Construct 24-byte encryption-decryption-encryption sequence
                byte[] ede = new byte[subkey1.length*2+subkey2.length];
                System.arraycopy(subkey1, 0, ede, 0, subkey1.length);
                System.arraycopy(subkey2, 0, ede, subkey1.length, subkey2.length);
                System.arraycopy(subkey1, 0, ede, subkey1.length+subkey2.length,
                    subkey1.length);
                spec = new DESedeKeySpec(ede, 0);
                if (logger.isLoggable(Level.FINEST)) {
                    traceOutput(DP_CLASS_NAME, "makeDesKeys",
                        "DIGEST46:3DES key input: ", input);
                    traceOutput(DP_CLASS_NAME, "makeDesKeys",
                        "DIGEST47:3DES key ede: ", ede);
                    traceOutput(DP_CLASS_NAME, "makeDesKeys",
                        "DIGEST48:3DES key material: ",
                        ((DESedeKeySpec)spec).getKey());
                    logger.log(Level.FINEST, "DIGEST49: is parity-adjusted? ",
                        Boolean.valueOf(DESedeKeySpec.isParityAdjusted(ede, 0)));
                }
                break;
            default:
                throw new IllegalArgumentException("Invalid DES strength:" +
                    desStrength);
        }
        return desFactory.generateSecret(spec);
    }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java DigestMD5Base.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.