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}