1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.io.hfile;
19
20 import java.io.DataInputStream;
21 import java.io.DataOutputStream;
22 import java.io.IOException;
23 import java.security.SecureRandom;
24 import java.util.List;
25 import java.util.UUID;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.apache.hadoop.conf.Configuration;
30 import org.apache.hadoop.fs.FSDataInputStream;
31 import org.apache.hadoop.fs.FSDataOutputStream;
32 import org.apache.hadoop.fs.FileSystem;
33 import org.apache.hadoop.fs.Path;
34 import org.apache.hadoop.hbase.Cell;
35 import org.apache.hadoop.hbase.HBaseTestingUtility;
36 import org.apache.hadoop.hbase.HConstants;
37 import org.apache.hadoop.hbase.KeyValue;
38 import org.apache.hadoop.hbase.KeyValueUtil;
39 import org.apache.hadoop.hbase.testclassification.SmallTests;
40 import org.apache.hadoop.hbase.io.compress.Compression;
41 import org.apache.hadoop.hbase.io.crypto.Cipher;
42 import org.apache.hadoop.hbase.io.crypto.Encryption;
43 import org.apache.hadoop.hbase.io.crypto.KeyProviderForTesting;
44 import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
45 import org.apache.hadoop.hbase.util.Bytes;
46 import org.apache.hadoop.hbase.util.test.RedundantKVGenerator;
47 import org.junit.BeforeClass;
48 import org.junit.Test;
49 import org.junit.experimental.categories.Category;
50
51 import static org.junit.Assert.*;
52
53 @Category(SmallTests.class)
54 public class TestHFileEncryption {
55 private static final Log LOG = LogFactory.getLog(TestHFileEncryption.class);
56 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
57 private static final SecureRandom RNG = new SecureRandom();
58
59 private static FileSystem fs;
60 private static Encryption.Context cryptoContext;
61
62 @BeforeClass
63 public static void setUp() throws Exception {
64 Configuration conf = TEST_UTIL.getConfiguration();
65
66 conf.setFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, 0.0f);
67 conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, KeyProviderForTesting.class.getName());
68 conf.set(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, "hbase");
69 conf.setInt("hfile.format.version", 3);
70
71 fs = FileSystem.get(conf);
72
73 cryptoContext = Encryption.newContext(conf);
74 String algorithm =
75 conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES);
76 Cipher aes = Encryption.getCipher(conf, algorithm);
77 assertNotNull(aes);
78 cryptoContext.setCipher(aes);
79 byte[] key = new byte[aes.getKeyLength()];
80 RNG.nextBytes(key);
81 cryptoContext.setKey(key);
82 }
83
84 private int writeBlock(FSDataOutputStream os, HFileContext fileContext, int size)
85 throws IOException {
86 HFileBlock.Writer hbw = new HFileBlock.Writer(null, fileContext);
87 DataOutputStream dos = hbw.startWriting(BlockType.DATA);
88 for (int j = 0; j < size; j++) {
89 dos.writeInt(j);
90 }
91 hbw.writeHeaderAndData(os);
92 LOG.info("Wrote a block at " + os.getPos() + " with" +
93 " onDiskSizeWithHeader=" + hbw.getOnDiskSizeWithHeader() +
94 " uncompressedSizeWithoutHeader=" + hbw.getOnDiskSizeWithoutHeader() +
95 " uncompressedSizeWithoutHeader=" + hbw.getUncompressedSizeWithoutHeader());
96 return hbw.getOnDiskSizeWithHeader();
97 }
98
99 private long readAndVerifyBlock(long pos, HFileContext ctx, HFileBlock.FSReaderImpl hbr, int size)
100 throws IOException {
101 HFileBlock b = hbr.readBlockData(pos, -1, -1, false);
102 assertEquals(0, HFile.getChecksumFailuresCount());
103 b.sanityCheck();
104 assertFalse(b.isUnpacked());
105 b = b.unpack(ctx, hbr);
106 LOG.info("Read a block at " + pos + " with" +
107 " onDiskSizeWithHeader=" + b.getOnDiskSizeWithHeader() +
108 " uncompressedSizeWithoutHeader=" + b.getOnDiskSizeWithoutHeader() +
109 " uncompressedSizeWithoutHeader=" + b.getUncompressedSizeWithoutHeader());
110 DataInputStream dis = b.getByteStream();
111 for (int i = 0; i < size; i++) {
112 int read = dis.readInt();
113 if (read != i) {
114 fail("Block data corrupt at element " + i);
115 }
116 }
117 return b.getOnDiskSizeWithHeader();
118 }
119
120 @Test(timeout=20000)
121 public void testDataBlockEncryption() throws IOException {
122 final int blocks = 10;
123 final int[] blockSizes = new int[blocks];
124 for (int i = 0; i < blocks; i++) {
125 blockSizes[i] = (1024 + RNG.nextInt(1024 * 63)) / Bytes.SIZEOF_INT;
126 }
127 for (Compression.Algorithm compression : TestHFileBlock.COMPRESSION_ALGORITHMS) {
128 Path path = new Path(TEST_UTIL.getDataTestDir(), "block_v3_" + compression + "_AES");
129 LOG.info("testDataBlockEncryption: encryption=AES compression=" + compression);
130 long totalSize = 0;
131 HFileContext fileContext = new HFileContextBuilder()
132 .withCompression(compression)
133 .withEncryptionContext(cryptoContext)
134 .build();
135 FSDataOutputStream os = fs.create(path);
136 try {
137 for (int i = 0; i < blocks; i++) {
138 totalSize += writeBlock(os, fileContext, blockSizes[i]);
139 }
140 } finally {
141 os.close();
142 }
143 FSDataInputStream is = fs.open(path);
144 try {
145 HFileBlock.FSReaderImpl hbr = new HFileBlock.FSReaderImpl(is, totalSize, fileContext);
146 long pos = 0;
147 for (int i = 0; i < blocks; i++) {
148 pos += readAndVerifyBlock(pos, fileContext, hbr, blockSizes[i]);
149 }
150 } finally {
151 is.close();
152 }
153 }
154 }
155
156 @Test(timeout=20000)
157 public void testHFileEncryptionMetadata() throws Exception {
158 Configuration conf = TEST_UTIL.getConfiguration();
159 CacheConfig cacheConf = new CacheConfig(conf);
160 HFileContext fileContext = new HFileContextBuilder()
161 .withEncryptionContext(cryptoContext)
162 .build();
163
164
165 Path path = new Path(TEST_UTIL.getDataTestDir(), "cryptometa.hfile");
166 FSDataOutputStream out = fs.create(path);
167 HFile.Writer writer = HFile.getWriterFactory(conf, cacheConf)
168 .withOutputStream(out)
169 .withFileContext(fileContext)
170 .create();
171 try {
172 KeyValue kv = new KeyValue("foo".getBytes(), "f1".getBytes(), null, "value".getBytes());
173 writer.append(kv);
174 } finally {
175 writer.close();
176 out.close();
177 }
178
179
180 HFile.Reader reader = HFile.createReader(fs, path, cacheConf, conf);
181 try {
182 reader.loadFileInfo();
183 FixedFileTrailer trailer = reader.getTrailer();
184 assertNotNull(trailer.getEncryptionKey());
185 Encryption.Context readerContext = reader.getFileContext().getEncryptionContext();
186 assertEquals(readerContext.getCipher().getName(), cryptoContext.getCipher().getName());
187 assertTrue(Bytes.equals(readerContext.getKeyBytes(),
188 cryptoContext.getKeyBytes()));
189 } finally {
190 reader.close();
191 }
192 }
193
194 @Test(timeout=6000000)
195 public void testHFileEncryption() throws Exception {
196
197 RedundantKVGenerator generator = new RedundantKVGenerator();
198 List<KeyValue> testKvs = generator.generateTestKeyValues(1000);
199
200
201 Configuration conf = TEST_UTIL.getConfiguration();
202 CacheConfig cacheConf = new CacheConfig(conf);
203 for (DataBlockEncoding encoding: DataBlockEncoding.values()) {
204 for (Compression.Algorithm compression: TestHFileBlock.COMPRESSION_ALGORITHMS) {
205 HFileContext fileContext = new HFileContextBuilder()
206 .withBlockSize(4096)
207 .withEncryptionContext(cryptoContext)
208 .withCompression(compression)
209 .withDataBlockEncoding(encoding)
210 .build();
211
212 LOG.info("Writing with " + fileContext);
213 Path path = new Path(TEST_UTIL.getDataTestDir(), UUID.randomUUID().toString() + ".hfile");
214 FSDataOutputStream out = fs.create(path);
215 HFile.Writer writer = HFile.getWriterFactory(conf, cacheConf)
216 .withOutputStream(out)
217 .withFileContext(fileContext)
218 .create();
219 for (KeyValue kv: testKvs) {
220 writer.append(kv);
221 }
222 writer.close();
223 out.close();
224
225
226 LOG.info("Reading with " + fileContext);
227 HFile.Reader reader = HFile.createReader(fs, path, cacheConf, conf);
228 reader.loadFileInfo();
229 FixedFileTrailer trailer = reader.getTrailer();
230 assertNotNull(trailer.getEncryptionKey());
231 HFileScanner scanner = reader.getScanner(false, false);
232 assertTrue("Initial seekTo failed", scanner.seekTo());
233 int i = 0;
234 do {
235 Cell kv = scanner.getKeyValue();
236 assertTrue("Read back an unexpected or invalid KV",
237 testKvs.contains(KeyValueUtil.ensureKeyValue(kv)));
238 i++;
239 } while (scanner.next());
240 reader.close();
241
242 assertEquals("Did not read back as many KVs as written", i, testKvs.size());
243
244
245 LOG.info("Random seeking with " + fileContext);
246 reader = HFile.createReader(fs, path, cacheConf, conf);
247 scanner = reader.getScanner(false, true);
248 assertTrue("Initial seekTo failed", scanner.seekTo());
249 for (i = 0; i < 100; i++) {
250 KeyValue kv = testKvs.get(RNG.nextInt(testKvs.size()));
251 assertEquals("Unable to find KV as expected: " + kv, scanner.seekTo(kv), 0);
252 }
253 reader.close();
254 }
255 }
256 }
257
258 }