001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.io.crypto.aes;
019
020import java.io.IOException;
021import java.security.InvalidAlgorithmParameterException;
022import java.security.InvalidKeyException;
023import java.security.NoSuchAlgorithmException;
024import java.util.Arrays;
025import java.util.Properties;
026import javax.crypto.Cipher;
027import javax.crypto.Mac;
028import javax.crypto.SecretKey;
029import javax.crypto.ShortBufferException;
030import javax.crypto.spec.IvParameterSpec;
031import javax.crypto.spec.SecretKeySpec;
032import javax.security.sasl.SaslException;
033import org.apache.commons.crypto.cipher.CryptoCipher;
034import org.apache.commons.crypto.utils.Utils;
035import org.apache.yetus.audience.InterfaceAudience;
036import org.apache.yetus.audience.InterfaceStability;
037
038/**
039 * AES encryption and decryption.
040 */
041@InterfaceAudience.Private
042@InterfaceStability.Evolving
043public class CryptoAES {
044
045  private final CryptoCipher encryptor;
046  private final CryptoCipher decryptor;
047
048  private final Integrity integrity;
049
050  public CryptoAES(String transformation, Properties properties, byte[] inKey, byte[] outKey,
051    byte[] inIv, byte[] outIv) throws IOException {
052    checkTransformation(transformation);
053    // encryptor
054    encryptor = Utils.getCipherInstance(transformation, properties);
055    try {
056      SecretKeySpec outKEYSpec = new SecretKeySpec(outKey, "AES");
057      IvParameterSpec outIVSpec = new IvParameterSpec(outIv);
058      encryptor.init(Cipher.ENCRYPT_MODE, outKEYSpec, outIVSpec);
059    } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
060      throw new IOException("Failed to initialize encryptor", e);
061    }
062
063    // decryptor
064    decryptor = Utils.getCipherInstance(transformation, properties);
065    try {
066      SecretKeySpec inKEYSpec = new SecretKeySpec(inKey, "AES");
067      IvParameterSpec inIVSpec = new IvParameterSpec(inIv);
068      decryptor.init(Cipher.DECRYPT_MODE, inKEYSpec, inIVSpec);
069    } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
070      throw new IOException("Failed to initialize decryptor", e);
071    }
072
073    integrity = new Integrity(outKey, inKey);
074  }
075
076  /**
077   * Encrypts input data. The result composes of (msg, padding if needed, mac) and sequence num.
078   * @param data   the input byte array
079   * @param offset the offset in input where the input starts
080   * @param len    the input length
081   * @return the new encrypted byte array.
082   * @throws SaslException if error happens
083   */
084  public byte[] wrap(byte[] data, int offset, int len) throws SaslException {
085    // mac
086    byte[] mac = integrity.getHMAC(data, offset, len);
087    integrity.incMySeqNum();
088
089    // encrypt
090    byte[] encrypted = new byte[len + 10];
091    try {
092      int n = encryptor.update(data, offset, len, encrypted, 0);
093      encryptor.update(mac, 0, 10, encrypted, n);
094    } catch (ShortBufferException sbe) {
095      // this should not happen
096      throw new SaslException("Error happens during encrypt data", sbe);
097    }
098
099    // append seqNum used for mac
100    byte[] wrapped = new byte[encrypted.length + 4];
101    System.arraycopy(encrypted, 0, wrapped, 0, encrypted.length);
102    System.arraycopy(integrity.getSeqNum(), 0, wrapped, encrypted.length, 4);
103
104    return wrapped;
105  }
106
107  /**
108   * Decrypts input data. The input composes of (msg, padding if needed, mac) and sequence num. The
109   * result is msg.
110   * @param data   the input byte array
111   * @param offset the offset in input where the input starts
112   * @param len    the input length
113   * @return the new decrypted byte array.
114   * @throws SaslException if error happens
115   */
116  public byte[] unwrap(byte[] data, int offset, int len) throws SaslException {
117    // get plaintext and seqNum
118    byte[] decrypted = new byte[len - 4];
119    byte[] peerSeqNum = new byte[4];
120    try {
121      decryptor.update(data, offset, len - 4, decrypted, 0);
122    } catch (ShortBufferException sbe) {
123      // this should not happen
124      throw new SaslException("Error happens during decrypt data", sbe);
125    }
126    System.arraycopy(data, offset + decrypted.length, peerSeqNum, 0, 4);
127
128    // get msg and mac
129    byte[] msg = new byte[decrypted.length - 10];
130    byte[] mac = new byte[10];
131    System.arraycopy(decrypted, 0, msg, 0, msg.length);
132    System.arraycopy(decrypted, msg.length, mac, 0, 10);
133
134    // check mac integrity and msg sequence
135    if (!integrity.compareHMAC(mac, peerSeqNum, msg, 0, msg.length)) {
136      throw new SaslException("Unmatched MAC");
137    }
138    if (!integrity.comparePeerSeqNum(peerSeqNum)) {
139      throw new SaslException("Out of order sequencing of messages. Got: "
140        + integrity.byteToInt(peerSeqNum) + " Expected: " + integrity.peerSeqNum);
141    }
142    integrity.incPeerSeqNum();
143
144    return msg;
145  }
146
147  private void checkTransformation(String transformation) throws IOException {
148    if ("AES/CTR/NoPadding".equalsIgnoreCase(transformation)) {
149      return;
150    }
151    throw new IOException("AES cipher transformation is not supported: " + transformation);
152  }
153
154  /**
155   * Helper class for providing integrity protection.
156   */
157  private static class Integrity {
158
159    private int mySeqNum = 0;
160    private int peerSeqNum = 0;
161    private byte[] seqNum = new byte[4];
162
163    private byte[] myKey;
164    private byte[] peerKey;
165
166    Integrity(byte[] outKey, byte[] inKey) throws IOException {
167      myKey = outKey;
168      peerKey = inKey;
169    }
170
171    byte[] getHMAC(byte[] msg, int start, int len) throws SaslException {
172      intToByte(mySeqNum);
173      return calculateHMAC(myKey, seqNum, msg, start, len);
174    }
175
176    boolean compareHMAC(byte[] expectedHMAC, byte[] peerSeqNum, byte[] msg, int start, int len)
177      throws SaslException {
178      byte[] mac = calculateHMAC(peerKey, peerSeqNum, msg, start, len);
179      return Arrays.equals(mac, expectedHMAC);
180    }
181
182    boolean comparePeerSeqNum(byte[] peerSeqNum) {
183      return this.peerSeqNum == byteToInt(peerSeqNum);
184    }
185
186    byte[] getSeqNum() {
187      return seqNum;
188    }
189
190    void incMySeqNum() {
191      mySeqNum++;
192    }
193
194    void incPeerSeqNum() {
195      peerSeqNum++;
196    }
197
198    private byte[] calculateHMAC(byte[] key, byte[] seqNum, byte[] msg, int start, int len)
199      throws SaslException {
200      byte[] seqAndMsg = new byte[4 + len];
201      System.arraycopy(seqNum, 0, seqAndMsg, 0, 4);
202      System.arraycopy(msg, start, seqAndMsg, 4, len);
203
204      try {
205        SecretKey keyKi = new SecretKeySpec(key, "HmacMD5");
206        Mac m = Mac.getInstance("HmacMD5");
207        m.init(keyKi);
208        m.update(seqAndMsg);
209        byte[] hMAC_MD5 = m.doFinal();
210
211        /* First 10 bytes of HMAC_MD5 digest */
212        byte macBuffer[] = new byte[10];
213        System.arraycopy(hMAC_MD5, 0, macBuffer, 0, 10);
214
215        return macBuffer;
216      } catch (InvalidKeyException e) {
217        throw new SaslException("Invalid bytes used for key of HMAC-MD5 hash.", e);
218      } catch (NoSuchAlgorithmException e) {
219        throw new SaslException("Error creating instance of MD5 MAC algorithm", e);
220      }
221    }
222
223    private void intToByte(int num) {
224      for (int i = 3; i >= 0; i--) {
225        seqNum[i] = (byte) (num & 0xff);
226        num >>>= 8;
227      }
228    }
229
230    private int byteToInt(byte[] seqNum) {
231      int answer = 0;
232      for (int i = 0; i < 4; i++) {
233        answer <<= 8;
234        answer |= ((int) seqNum[i] & 0xff);
235      }
236      return answer;
237    }
238  }
239}