org.apache.hadoop.hbase.io.crypto.aes.CryptoAES.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hbase.io.crypto.aes.CryptoAES.java

Source

/**
 * 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.hadoop.hbase.io.crypto.aes;

import org.apache.commons.crypto.cipher.CryptoCipher;
import org.apache.commons.crypto.utils.Utils;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.classification.InterfaceStability;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.security.sasl.SaslException;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Properties;

/**
 * AES encryption and decryption.
 */
@InterfaceAudience.Private
@InterfaceStability.Evolving
public class CryptoAES {

    private final CryptoCipher encryptor;
    private final CryptoCipher decryptor;

    private final Integrity integrity;

    public CryptoAES(String transformation, Properties properties, byte[] inKey, byte[] outKey, byte[] inIv,
            byte[] outIv) throws IOException {
        checkTransformation(transformation);
        // encryptor
        encryptor = Utils.getCipherInstance(transformation, properties);
        try {
            SecretKeySpec outKEYSpec = new SecretKeySpec(outKey, "AES");
            IvParameterSpec outIVSpec = new IvParameterSpec(outIv);
            encryptor.init(Cipher.ENCRYPT_MODE, outKEYSpec, outIVSpec);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IOException("Failed to initialize encryptor", e);
        }

        // decryptor
        decryptor = Utils.getCipherInstance(transformation, properties);
        try {
            SecretKeySpec inKEYSpec = new SecretKeySpec(inKey, "AES");
            IvParameterSpec inIVSpec = new IvParameterSpec(inIv);
            decryptor.init(Cipher.DECRYPT_MODE, inKEYSpec, inIVSpec);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IOException("Failed to initialize decryptor", e);
        }

        integrity = new Integrity(outKey, inKey);
    }

    /**
     * Encrypts input data. The result composes of (msg, padding if needed, mac) and sequence num.
     * @param data the input byte array
     * @param offset the offset in input where the input starts
     * @param len the input length
     * @return the new encrypted byte array.
     * @throws SaslException if error happens
     */
    public byte[] wrap(byte[] data, int offset, int len) throws SaslException {
        // mac
        byte[] mac = integrity.getHMAC(data, offset, len);
        integrity.incMySeqNum();

        // encrypt
        byte[] encrypted = new byte[len + 10];
        try {
            int n = encryptor.update(data, offset, len, encrypted, 0);
            encryptor.update(mac, 0, 10, encrypted, n);
        } catch (ShortBufferException sbe) {
            // this should not happen
            throw new SaslException("Error happens during encrypt data", sbe);
        }

        // append seqNum used for mac
        byte[] wrapped = new byte[encrypted.length + 4];
        System.arraycopy(encrypted, 0, wrapped, 0, encrypted.length);
        System.arraycopy(integrity.getSeqNum(), 0, wrapped, encrypted.length, 4);

        return wrapped;
    }

    /**
     * Decrypts input data. The input composes of (msg, padding if needed, mac) and sequence num.
     * The result is msg.
     * @param data the input byte array
     * @param offset the offset in input where the input starts
     * @param len the input length
     * @return the new decrypted byte array.
     * @throws SaslException if error happens
     */
    public byte[] unwrap(byte[] data, int offset, int len) throws SaslException {
        // get plaintext and seqNum
        byte[] decrypted = new byte[len - 4];
        byte[] peerSeqNum = new byte[4];
        try {
            decryptor.update(data, offset, len - 4, decrypted, 0);
        } catch (ShortBufferException sbe) {
            // this should not happen
            throw new SaslException("Error happens during decrypt data", sbe);
        }
        System.arraycopy(data, offset + decrypted.length, peerSeqNum, 0, 4);

        // get msg and mac
        byte[] msg = new byte[decrypted.length - 10];
        byte[] mac = new byte[10];
        System.arraycopy(decrypted, 0, msg, 0, msg.length);
        System.arraycopy(decrypted, msg.length, mac, 0, 10);

        // check mac integrity and msg sequence
        if (!integrity.compareHMAC(mac, peerSeqNum, msg, 0, msg.length)) {
            throw new SaslException("Unmatched MAC");
        }
        if (!integrity.comparePeerSeqNum(peerSeqNum)) {
            throw new SaslException("Out of order sequencing of messages. Got: " + integrity.byteToInt(peerSeqNum)
                    + " Expected: " + integrity.peerSeqNum);
        }
        integrity.incPeerSeqNum();

        return msg;
    }

    private void checkTransformation(String transformation) throws IOException {
        if ("AES/CTR/NoPadding".equalsIgnoreCase(transformation)) {
            return;
        }
        throw new IOException("AES cipher transformation is not supported: " + transformation);
    }

    /**
     * Helper class for providing integrity protection.
     */
    private static class Integrity {

        private int mySeqNum = 0;
        private int peerSeqNum = 0;
        private byte[] seqNum = new byte[4];

        private byte[] myKey;
        private byte[] peerKey;

        Integrity(byte[] outKey, byte[] inKey) throws IOException {
            myKey = outKey;
            peerKey = inKey;
        }

        byte[] getHMAC(byte[] msg, int start, int len) throws SaslException {
            intToByte(mySeqNum);
            return calculateHMAC(myKey, seqNum, msg, start, len);
        }

        boolean compareHMAC(byte[] expectedHMAC, byte[] peerSeqNum, byte[] msg, int start, int len)
                throws SaslException {
            byte[] mac = calculateHMAC(peerKey, peerSeqNum, msg, start, len);
            return Arrays.equals(mac, expectedHMAC);
        }

        boolean comparePeerSeqNum(byte[] peerSeqNum) {
            return this.peerSeqNum == byteToInt(peerSeqNum);
        }

        byte[] getSeqNum() {
            return seqNum;
        }

        void incMySeqNum() {
            mySeqNum++;
        }

        void incPeerSeqNum() {
            peerSeqNum++;
        }

        private byte[] calculateHMAC(byte[] key, 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(key, "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("Invalid bytes used for key of HMAC-MD5 hash.", e);
            } catch (NoSuchAlgorithmException e) {
                throw new SaslException("Error creating instance of MD5 MAC algorithm", e);
            }
        }

        private void intToByte(int num) {
            for (int i = 3; i >= 0; i--) {
                seqNum[i] = (byte) (num & 0xff);
                num >>>= 8;
            }
        }

        private int byteToInt(byte[] seqNum) {
            int answer = 0;
            for (int i = 0; i < 4; i++) {
                answer <<= 8;
                answer |= ((int) seqNum[i] & 0xff);
            }
            return answer;
        }
    }
}