Package ch.ethz.inf.vs.scandium.dtls

Source Code of ch.ethz.inf.vs.scandium.dtls.CCMBlockCipher

/*******************************************************************************
* Copyright (c) 2014, Institute for Pervasive Computing, ETH Zurich.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of the Scandium (Sc) Security for Californium.
******************************************************************************/
package ch.ethz.inf.vs.scandium.dtls;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import ch.ethz.inf.vs.scandium.dtls.AlertMessage.AlertDescription;
import ch.ethz.inf.vs.scandium.dtls.AlertMessage.AlertLevel;
import ch.ethz.inf.vs.scandium.util.DatagramWriter;

/**
* A generic authenticated encryption block cipher mode which uses the 128-bit
* block cipher AES. See <a href="http://tools.ietf.org/html/rfc3610">RFC
* 3610</a> for details.
*
* @author Stefan Jucker
*
*/
public final class CCMBlockCipher {

  // Logging ////////////////////////////////////////////////////////

  private static final Logger LOGGER = Logger.getLogger(CCMBlockCipher.class.getCanonicalName());

  // Members ////////////////////////////////////////////////////////

  /**
   * CCM is only defined for use with 128-bit block ciphers, such as AES
   * (http://tools.ietf.org/html/rfc3610).
   */
  private static final int BLOCK_SIZE = 16;

  /**
   * The underlying block cipher.
   */
  private static final String BLOCK_CIPHER = "AES";

  // Static methods /////////////////////////////////////////////////

  /**
   * See <a href="http://tools.ietf.org/html/rfc3610#section-2.5">RFC 3610</a>
   * for details.
   *
   * @param key
   *            the encryption key K.
   * @param nonce
   *            the nonce N.
   * @param a
   *            the additional authenticated data a.
   * @param c
   *            the encrypted and authenticated message c.
   * @param numAuthenticationBytes
   *            Number of octets in authentication field.
   * @return the decrypted message
   *
   * @throws HandshakeException
   *             if the message could not be authenticated.
   */
  public static byte[] decrypt(byte[] key, byte[] nonce, byte[] a, byte[] c, int numAuthenticationBytes) throws HandshakeException {
    byte[] T;
    byte[] m;
    byte[] mac;
    try {
      /*
       * http://tools.ietf.org/html/draft-mcgrew-tls-aes-ccm-04#section-6.1:
       * "AEAD_AES_128_CCM_8 ciphertext is exactly 8 octets longer than
       * its corresponding plaintext"
       */
      long lengthM = c.length - numAuthenticationBytes;

      // instantiate the underlying block cipher
      Cipher cipher = Cipher.getInstance(BLOCK_CIPHER);
      cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, BLOCK_CIPHER));

      /*
       * Decryption starts by recomputing the key stream to recover the
       * message m and the MAC value T.
       */
      List<byte[]> S_i = generateKeyStreamBlocks(lengthM, nonce, cipher);
      byte[] S_0 = S_i.get(0);
      byte[] concatenatedS_i = generateConcatenatedKeyStream(S_i, lengthM);

      // extract the encrypted message (cut of authentication value)
      byte[] encryptedM = ByteArrayUtils.truncate(c, (int) lengthM);

      /*
       * The message is decrypted by XORing the octets of message m with
       * the first l(m) octets of the concatenation of S_1, S_2, S_3
       */
      m = ByteArrayUtils.xorArrays(encryptedM, concatenatedS_i);

      // extract the authentication value from the cipher text
      byte[] encryptedT = new byte[numAuthenticationBytes];
      System.arraycopy(c, (int) lengthM, encryptedT, 0, numAuthenticationBytes);

      // T := U XOR first-M-bytes( S_0 )
      T = ByteArrayUtils.xorArrays(encryptedT, ByteArrayUtils.truncate(S_0, numAuthenticationBytes));

      /*
       * The message and additional authentication data is then used to
       * recompute the CBC-MAC value and check T.
       */
      mac = computeCbcMac(nonce, m, a, cipher, numAuthenticationBytes);
    } catch (Exception e) {
      LOGGER.severe("Could not decrypt the message.");
      e.printStackTrace();
      return new byte[] {};
    }

    /*
     * If the T value is not correct, the receiver MUST NOT reveal any
     * information except for the fact that T is incorrect. The receiver
     * MUST NOT reveal the decrypted message, the value T, or any other
     * information.
     */
    if (Arrays.equals(T, mac)) {
      return m;
    } else {
      String message = "The encrypted message could not be authenticated:\nExpected: " + ByteArrayUtils.toHexString(T) + "\nActual:   " + ByteArrayUtils.toHexString(mac);
      AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_RECORD_MAC);
      throw new HandshakeException(message, alert);
    }

  }

  /**
   * See <a href="http://tools.ietf.org/html/rfc3610#section-2.2">RFC 3610</a>
   * for details.
   *
   * @param key
   *            the encryption key K.
   * @param nonce
   *            the nonce N.
   * @param a
   *            the additional authenticated data a.
   * @param m
   *            the message to authenticate and encrypt.
   * @param numAuthenticationBytes
   *            Number of octets in authentication field.
   * @return the encrypted and authenticated message.
   */
  public static byte[] encrypt(byte[] key, byte[] nonce, byte[] a, byte[] m, int numAuthenticationBytes) {
    try {
      long lengthM = m.length;

      // instantiate the cipher
      Cipher cipher = Cipher.getInstance(BLOCK_CIPHER);
      cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, BLOCK_CIPHER));

      /*
       * First, authentication:
       * http://tools.ietf.org/html/rfc3610#section-2.2
       */

      // compute the authentication field T
      byte[] T = computeCbcMac(nonce, m, a, cipher, numAuthenticationBytes);

      /*
       * Second, encryption http://tools.ietf.org/html/rfc3610#section-2.3
       */

      List<byte[]> S_i = generateKeyStreamBlocks(lengthM, nonce, cipher);
      byte[] S_0 = S_i.get(0);
      byte[] concatenatedS_i = generateConcatenatedKeyStream(S_i, lengthM);

      /*
       * The message is encrypted by XORing the octets of message m with
       * the first l(m) octets of the concatenation of S_1, S_2, S_3, ...
       * . Note that S_0 is not used to encrypt the message.
       */
      byte[] encryptedMessage = ByteArrayUtils.xorArrays(m, concatenatedS_i);

      // U := T XOR first-M-bytes( S_0 )
      byte[] U = ByteArrayUtils.xorArrays(T, ByteArrayUtils.truncate(S_0, numAuthenticationBytes));

      /*
       * The final result c consists of the encrypted message followed by
       * the encrypted authentication value U.
       */
      byte[] c = ByteArrayUtils.concatenate(encryptedMessage, U);

      return c;
    } catch (Exception e) {
      LOGGER.severe("Could not encrypt the message.");
      e.printStackTrace();
      return new byte[] {};
    }
  }

  // Helper methods /////////////////////////////////////////////////

  /**
   * Computes CBC-MAC. See <a
   * href="http://tools.ietf.org/html/rfc3610#section-2.2">RFC 3610 -
   * Authentication</a> for details.
   *
   * @param nonce
   *            the nonce.
   * @param m
   *            the message to authenticate and encrypt.
   * @param a
   *            the additional authenticated data.
   * @param cipher
   *            the cipher.
   * @param authenticationBytes
   *            Number of octets in authentication field.
   * @return the CBC-MAC
   * @throws Exception
   *             if cipher can not be realized.
   */
  private static byte[] computeCbcMac(byte[] nonce, byte[] m, byte[] a, Cipher cipher, int authenticationBytes) throws Exception {
    long lengthM = m.length;
    long lengthA = a.length;
    int L = 15 - nonce.length;

    // build first block B_0

    /*
     * Octet Number  Contents
     * ------------  ---------
     * 0       Flags
     * 1 ... 15-L   Nonce N
     * 16-L ... 15   l(m)
     */
    byte[] b0 = new byte[BLOCK_SIZE];

    int adata = 0;
    // The Adata bit is set to zero if l(a)=0, and set to one if l(a)>0
    if (lengthA > 0) {
      adata = 1;
    }
    // M' field is set to (M-2)/2
    int mPrime = (authenticationBytes - 2) / 2;
    // L' = L-1 (the zero value is reserved)
    int lPrime = L - 1;

    /*
     * Bit Number  Contents
     * ----------  ----------------------
     * 7       Reserved (always zero)
     * 6       Adata
     * 5 ... 3     M'
     * 2 ... 0     L'
     */

    // Flags = 64*Adata + 8*M' + L'
    b0[0] = (byte) (64 * adata + 8 * mPrime + lPrime);

    // 1 ... 15-L Nonce N
    System.arraycopy(nonce, 0, b0, 1, nonce.length);

    b0[14] = (byte) (lengthM >> 8);
    b0[15] = (byte) (lengthM);

    List<byte[]> blocks = new ArrayList<byte[]>();

    // If l(a)>0 (as indicated by the Adata field), then one or more blocks
    // of authentication data are added.
    if (lengthA > 0) {

      /*
       * First two octets    Followed by      Comment
       * -----------------  ----------------  -------------------------------
       * 0x0000        Nothing        Reserved
       * 0x0001 ... 0xFEFF  Nothing        For 0 < l(a) < (2^16 - 2^8)
       * 0xFF00 ... 0xFFFD  Nothing        Reserved
       * 0xFFFE        4 octets of l(a)  For (2^16 - 2^8) <= l(a) < 2^32
       * 0xFFFF        8 octets of l(a)  For 2^32 <= l(a) < 2^64
       */

      // 2^16 - 2^8
      final int first = 65280;
      // 2^32
      final long second = 4294967296L;

      /*
       * The blocks encoding a are formed by concatenating this string
       * that encodes l(a) with a itself, and splitting the result into
       * 16-octet blocks, and then padding the last block with zeroes if
       * necessary.
       */

      DatagramWriter writer = new DatagramWriter();
      if (lengthA > 0 && lengthA < first) {
        // 2 bytes (0x0001 ... 0xFEFF)
        writer.writeLong(lengthA, 16);

      } else if (lengthA >= first && lengthA < second) {
        // 2 bytes (0xFFFE) + 4 octets of l(a)
        int field = 0xFFFE;
        writer.write(field, 16);
        writer.writeLong(lengthA, 32);

      } else {
        // 2 bytes (0xFFFF) + 8 octets of l(a)
        int field = 0xFFFF;
        writer.write(field, 16);
        writer.writeLong(lengthA, 64);
      }
      writer.writeBytes(a);

      byte[] aEncoded = writer.toByteArray();
      blocks.addAll(ByteArrayUtils.splitAndPad(aEncoded, BLOCK_SIZE));
    }
    /*
     * After the (optional) additional authentication blocks have been
     * added, we add the message blocks. The message blocks are formed by
     * splitting the message m into 16-octet blocks, and then padding the
     * last block with zeroes if necessary. If the message m consists of the
     * empty string, then no blocks are added in this step.
     */
    blocks.addAll(ByteArrayUtils.splitAndPad(m, BLOCK_SIZE));

    byte[] X_i;
    // X_1 := E( K, B_0 )
    X_i = ByteArrayUtils.truncate(cipher.doFinal(b0), BLOCK_SIZE);

    // X_i+1 := E( K, X_i XOR B_i ) for i=1, ..., n
    for (byte[] block : blocks) {
      byte[] xor = ByteArrayUtils.xorArrays(block, X_i);
      X_i = ByteArrayUtils.truncate(cipher.doFinal(xor), BLOCK_SIZE);
    }

    // T := first-M-bytes( X_n+1 )
    byte[] T = ByteArrayUtils.truncate(X_i, authenticationBytes);

    return T;
  }

  /**
   * See <a href="http://tools.ietf.org/html/rfc3610#section-2.3">RFC 3610 -
   * Key Stream Blocks</a> for details.
   *
   * @param lengthM
   *            the length of the message.
   * @param nonce
   *            the nonce.
   * @param cipher
   *            the cipher.
   * @return the key stream blocks.
   * @throws Exception
   *             if the cipher can not be realized.
   */
  private static List<byte[]> generateKeyStreamBlocks(long lengthM, byte[] nonce, Cipher cipher) throws Exception {
    int L = 15 - nonce.length;

    List<byte[]> S_i = new ArrayList<byte[]>();

    /*
     * Compute the material needed according to the message length and add
     * one more round to compute S_0 which is needed elsewhere.
     */
    int numRounds = (int) (Math.ceil(lengthM / (double) BLOCK_SIZE) + 1);

    // S_i := E( K, A_i ) for i=0, 1, 2, ...
    for (int i = 0; i < numRounds; i++) {
      DatagramWriter writer = new DatagramWriter();

      /*
       * Octet Number  Contents
       * ------------  ---------
       * 0      Flags
       * 1 ... 15-L  Nonce N
       * 16-L ... 15  Counter i
       */

      // Octet Number Contents
      // ------------ ---------
      // 0       Flags
      // 1 ... 15-L   Nonce N
      // 16-L ... 15   Counter i

      int flag = L - 1;

      // write the first byte: Flags
      writer.write(flag, 8);

      // the Nonce N
      writer.writeBytes(nonce);

      // writer the Counter i (L bytes)
      writer.writeLong(i, L * 8);
      byte[] S = writer.toByteArray();

      // S_i := E( K, A_i )
      S_i.add(ByteArrayUtils.truncate(cipher.doFinal(S), BLOCK_SIZE));
    }

    return S_i;
  }

  /**
   * Generates the concatenated key stream which is used to encrypt / decrypt
   * the message by XORing it with this key stream. Therefore, the message
   * length needs to be known.
   *
   * @param S_i
   *            the list of key stream blocks.
   * @param lengthM
   *            the length of the message m.
   * @return the concatenated key stream which is long enough to cover the
   *         message.
   */
  private static byte[] generateConcatenatedKeyStream(List<byte[]> S_i, long lengthM) {
    byte[] concatenatedS_i = new byte[0];

    // determine, how much "material" needed, to cover whole message
    int numRounds = (int) (Math.ceil(lengthM / (double) BLOCK_SIZE));

    // S_0 is not used to encrypt the message, therefore start with i = 1
    for (int i = 1; i <= numRounds; i++) {
      concatenatedS_i = ByteArrayUtils.concatenate(concatenatedS_i, S_i.get(i));
    }

    return concatenatedS_i;
  }
}
TOP

Related Classes of ch.ethz.inf.vs.scandium.dtls.CCMBlockCipher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.