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