IGNITE-8485: TDE implementation. - Fixes #4167.

Signed-off-by: Nikolay Izhikov <[email protected]>


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/aabacfa0
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/aabacfa0
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/aabacfa0

Branch: refs/heads/master
Commit: aabacfa00f5fd7ef89c9a5bda7b236ff45ed2ac4
Parents: 754c733
Author: Nikolay Izhikov <[email protected]>
Authored: Fri Oct 5 12:55:06 2018 +0300
Committer: Nikolay Izhikov <[email protected]>
Committed: Fri Oct 5 12:55:06 2018 +0300

----------------------------------------------------------------------
 .../JmhKeystoreEncryptionSpiBenchmark.java      | 117 +++
 .../ignite/codegen/MessageCodeGenerator.java    |   2 +
 .../configuration/CacheConfiguration.java       |  33 +
 .../configuration/IgniteConfiguration.java      |  27 +
 .../apache/ignite/internal/GridComponent.java   |   9 +-
 .../ignite/internal/GridKernalContext.java      |   8 +
 .../ignite/internal/GridKernalContextImpl.java  |  12 +
 .../org/apache/ignite/internal/GridTopic.java   |   5 +-
 .../apache/ignite/internal/IgniteKernal.java    |   2 +
 .../ignite/internal/IgniteNodeAttributes.java   |   3 +
 .../org/apache/ignite/internal/IgnitionEx.java  |   4 +
 .../communication/GridIoMessageFactory.java     |  12 +
 .../discovery/GridDiscoveryManager.java         |   2 +
 .../GenerateEncryptionKeyRequest.java           | 142 +++
 .../GenerateEncryptionKeyResponse.java          | 148 ++++
 .../encryption/GridEncryptionManager.java       | 864 +++++++++++++++++++
 .../ignite/internal/pagemem/PageMemory.java     |   6 +
 .../pagemem/impl/PageMemoryNoStoreImpl.java     |   5 +
 .../internal/pagemem/store/PageStore.java       |  27 +
 .../pagemem/wal/record/EncryptedRecord.java     |  60 ++
 .../pagemem/wal/record/PageSnapshot.java        |  19 +-
 .../internal/pagemem/wal/record/WALRecord.java  |   8 +-
 .../delta/DataPageInsertFragmentRecord.java     |   2 +-
 .../wal/record/delta/DataPageInsertRecord.java  |   2 +-
 .../delta/DataPageMvccMarkUpdatedRecord.java    |   2 +-
 .../DataPageMvccUpdateNewTxStateHintRecord.java |   2 +-
 .../DataPageMvccUpdateTxStateHintRecord.java    |   2 +-
 .../wal/record/delta/DataPageRemoveRecord.java  |   2 +-
 .../wal/record/delta/DataPageUpdateRecord.java  |   2 +-
 .../wal/record/delta/InitNewPageRecord.java     |   2 +-
 .../wal/record/delta/MetaPageAddRootRecord.java |   2 +-
 .../wal/record/delta/MetaPageCutRootRecord.java |   2 +-
 .../wal/record/delta/MetaPageInitRecord.java    |   2 +-
 .../delta/MetaPageInitRootInlineRecord.java     |   2 +-
 .../record/delta/MetaPageInitRootRecord.java    |   2 +-
 .../wal/record/delta/NewRootInitRecord.java     |   3 +-
 .../record/delta/PagesListAddPageRecord.java    |   2 +-
 .../delta/PagesListInitNewPageRecord.java       |   4 +-
 .../record/delta/TrackingPageDeltaRecord.java   |   2 +-
 .../IgniteAuthenticationProcessor.java          |  16 +-
 .../processors/cache/ClusterCachesInfo.java     |  34 +-
 .../cache/DynamicCacheChangeRequest.java        |  17 +
 .../processors/cache/GridCacheAttributes.java   |   9 +-
 .../processors/cache/GridCacheProcessor.java    | 135 ++-
 .../processors/cache/GridCacheUtils.java        |  23 +
 .../cache/IgniteCacheOffheapManagerImpl.java    |  10 +-
 .../GridDhtPartitionsExchangeFuture.java        |   2 +
 .../processors/cache/mvcc/MvccUtils.java        |   3 +-
 .../cache/persistence/CacheDataRowAdapter.java  |   2 +-
 .../cache/persistence/DataStructure.java        |   6 +-
 .../GridCacheDatabaseSharedManager.java         |   3 +
 .../persistence/GridCacheOffheapManager.java    |  23 +-
 .../cache/persistence/file/EncryptedFileIO.java | 371 ++++++++
 .../file/EncryptedFileIOFactory.java            | 100 +++
 .../cache/persistence/file/FilePageStore.java   |  28 +-
 .../persistence/file/FilePageStoreFactory.java  |   3 +-
 .../persistence/file/FilePageStoreManager.java  |  76 +-
 .../file/FileVersionCheckingFactory.java        |  25 +-
 .../cache/persistence/freelist/PagesList.java   |   4 +-
 .../persistence/metastorage/MetaStorage.java    |   2 +
 .../persistence/pagemem/PageMemoryImpl.java     |  30 +-
 .../cache/persistence/tree/io/BPlusIO.java      |   4 +-
 .../cache/persistence/tree/io/PageIO.java       |   5 +-
 .../tree/io/PagePartitionCountersIO.java        |   2 +-
 .../persistence/tree/util/PageHandler.java      |  50 +-
 .../wal/reader/StandaloneGridKernalContext.java |   6 +
 .../reader/StandaloneWalRecordsIterator.java    |   3 +
 .../wal/serializer/RecordDataV1Serializer.java  | 355 +++++++-
 .../wal/serializer/RecordDataV2Serializer.java  |  60 +-
 .../serializer/RecordSerializerFactoryImpl.java |   5 +-
 .../wal/serializer/RecordV1Serializer.java      |   8 +-
 .../wal/serializer/RecordV2Serializer.java      |   2 +-
 .../cluster/ChangeGlobalStateMessage.java       |   6 +-
 .../cluster/GridClusterStateProcessor.java      |   2 +
 .../utils/PlatformConfigurationUtils.java       |  59 ++
 .../processors/query/GridQueryProcessor.java    |   6 +-
 .../ignite/internal/util/IgniteUtils.java       |  65 ++
 .../ignite/internal/util/lang/GridFunc.java     |   6 +-
 .../ignite/spi/encryption/EncryptionSpi.java    | 113 +++
 .../keystore/KeystoreEncryptionKey.java         |  84 ++
 .../keystore/KeystoreEncryptionSpi.java         | 501 +++++++++++
 .../spi/encryption/keystore/package-info.java   |  22 +
 .../spi/encryption/noop/NoopEncryptionSpi.java  | 101 +++
 .../ignite/spi/encryption/package-info.java     |  22 +
 .../encryption/AbstractEncryptionTest.java      | 245 ++++++
 .../encryption/EncryptedCacheBigEntryTest.java  | 114 +++
 .../encryption/EncryptedCacheCreateTest.java    | 164 ++++
 .../encryption/EncryptedCacheDestroyTest.java   | 127 +++
 .../EncryptedCacheGroupCreateTest.java          | 116 +++
 .../encryption/EncryptedCacheNodeJoinTest.java  | 237 +++++
 .../EncryptedCachePreconfiguredRestartTest.java |  87 ++
 .../encryption/EncryptedCacheRestartTest.java   |  64 ++
 .../pagemem/impl/PageMemoryNoLoadSelfTest.java  |  18 +-
 ...gnitePdsRecoveryAfterFileCorruptionTest.java |   8 +-
 ...ckpointSimulationWithRealCpDisabledTest.java |  21 +-
 .../db/file/IgnitePdsPageReplacementTest.java   |   2 +-
 .../persistence/db/wal/WalCompactionTest.java   |  12 +-
 .../pagemem/BPlusTreePageMemoryImplTest.java    |  22 +-
 .../BPlusTreeReuseListPageMemoryImplTest.java   |  18 +-
 ...gnitePageMemReplaceDelayedWriteUnitTest.java |   7 +-
 .../pagemem/IndexStoragePageMemoryImplTest.java |  22 +-
 .../pagemem/PageMemoryImplNoLoadTest.java       |  22 +-
 .../persistence/pagemem/PageMemoryImplTest.java |   7 +
 .../wal/memtracker/PageMemoryTracker.java       |  13 +
 .../KeystoreEncryptionSpiSelfTest.java          | 123 +++
 .../ignite/testframework/GridTestUtils.java     |  81 +-
 .../testframework/junits/GridAbstractTest.java  |   4 +-
 .../IgniteBasicWithPersistenceTestSuite.java    |  15 +
 .../testsuites/IgniteKernalSelfTestSuite.java   |   1 -
 .../ignite/testsuites/IgniteSpiTestSuite.java   |   3 +
 .../src/test/resources/other_tde_keystore.jks   | Bin 0 -> 347 bytes
 modules/core/src/test/resources/tde.jks         | Bin 0 -> 347 bytes
 .../query/h2/ddl/DdlStatementsProcessor.java    |   3 +-
 .../query/h2/sql/GridSqlCreateTable.java        |  17 +
 .../query/h2/sql/GridSqlQueryParser.java        |   8 +
 .../cache/encryption/EncryptedSqlTableTest.java |  69 ++
 .../cache/index/H2DynamicTableSelfTest.java     |   4 +-
 .../IgniteCacheQuerySelfTestSuite.java          |   2 +
 modules/indexing/src/test/resources/tde.jks     | Bin 0 -> 347 bytes
 .../Apache.Ignite.Core.Tests.DotNetCore/tde.jks | Bin 0 -> 347 bytes
 .../ApiParity/IgniteConfigurationParityTest.cs  |   5 +-
 .../IgniteConfigurationTest.cs                  |  17 +
 .../Apache.Ignite.Core.csproj                   |   6 +-
 .../Cache/Configuration/CacheConfiguration.cs   |  13 +
 .../Encryption/IEncryptionSpi.cs                |  34 +
 .../Keystore/KeystoreEncryptionSpi.cs           |  84 ++
 .../Encryption/Keystore/Package-Info.cs         |  26 +
 .../Encryption/Package-Info.cs                  |  26 +
 .../Apache.Ignite.Core/IgniteConfiguration.cs   |  31 +
 .../IgniteConfigurationSection.xsd              |  39 +-
 .../spring/src/test/config/enc/base-enc-cfg.xml |  70 ++
 .../src/test/config/enc/enc-cache-client.xml    |  35 +
 .../spring/src/test/config/enc/enc-cache.xml    |  35 +
 .../spring/src/test/config/enc/enc-group-2.xml  |  36 +
 .../spring/src/test/config/enc/enc-group.xml    |  37 +
 .../config/enc/not-encrypted-cache-in-group.xml |  36 +
 .../src/test/config/enc/not-encrypted-cache.xml |  35 +
 .../SpringEncryptedCacheRestartClientTest.java  |  60 ++
 .../SpringEncryptedCacheRestartTest.java        | 190 ++++
 .../testsuites/IgniteSpringTestSuite.java       |   6 +
 modules/spring/src/test/resources/tde.jks       | Bin 0 -> 347 bytes
 .../config/benchmark-multicast-tde.properties   | 128 +++
 modules/yardstick/config/ignite-base-config.xml |  18 +-
 modules/yardstick/config/ignite-tde-config.xml  |  55 ++
 modules/yardstick/src/main/resources/tde.jks    | Bin 0 -> 347 bytes
 145 files changed, 6319 insertions(+), 320 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/encryption/JmhKeystoreEncryptionSpiBenchmark.java
----------------------------------------------------------------------
diff --git 
a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/encryption/JmhKeystoreEncryptionSpiBenchmark.java
 
b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/encryption/JmhKeystoreEncryptionSpiBenchmark.java
new file mode 100644
index 0000000..932d57e
--- /dev/null
+++ 
b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/encryption/JmhKeystoreEncryptionSpiBenchmark.java
@@ -0,0 +1,117 @@
+/*
+ * 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.ignite.internal.benchmarks.jmh.encryption;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.ThreadLocalRandom;
+import org.apache.ignite.internal.benchmarks.jmh.JmhAbstractBenchmark;
+import org.apache.ignite.spi.encryption.keystore.KeystoreEncryptionKey;
+import org.apache.ignite.spi.encryption.keystore.KeystoreEncryptionSpi;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.infra.Blackhole;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import static org.apache.ignite.internal.util.IgniteUtils.resolveIgnitePath;
+
+/**
+ */
+public class JmhKeystoreEncryptionSpiBenchmark extends JmhAbstractBenchmark {
+    /** Data amount. */
+    private static final int DATA_AMOUNT = 100;
+
+    public static final int PAGE_SIZE = 1024 * 4;
+
+    /** */
+    @Benchmark
+    public void encryptBenchmark(EncryptionData d, Blackhole receiver) {
+        for (int i = 0; i < DATA_AMOUNT; i++) {
+            ByteBuffer[] dt = d.randomData[i];
+
+            KeystoreEncryptionKey key = 
d.keys[ThreadLocalRandom.current().nextInt(4)];
+
+            d.encSpi.encryptNoPadding(dt[0], key, dt[1]);
+
+            receiver.consume(d.res);
+
+            dt[0].rewind();
+            dt[1].rewind();
+
+            d.encSpi.decryptNoPadding(dt[1], key, dt[0]);
+        }
+    }
+
+    @State(Scope.Thread)
+    public static class EncryptionData {
+        KeystoreEncryptionSpi encSpi;
+
+        KeystoreEncryptionKey[] keys = new KeystoreEncryptionKey[4];
+
+        ByteBuffer[][] randomData = new ByteBuffer[DATA_AMOUNT][2];
+
+        ByteBuffer res = ByteBuffer.allocate(PAGE_SIZE);
+
+        public EncryptionData() {
+            encSpi = new KeystoreEncryptionSpi();
+
+            
encSpi.setKeyStorePath(resolveIgnitePath("modules/core/src/test/resources/tde.jks").getAbsolutePath());
+            encSpi.setKeyStorePassword("love_sex_god".toCharArray());
+
+            encSpi.onBeforeStart();
+            encSpi.spiStart("test-instance");
+        }
+
+        @Setup(Level.Invocation)
+        public void prepareCollection() {
+            for (int i = 0; i < keys.length; i++)
+                keys[i] = encSpi.create();
+
+            for (int i = 0; i < DATA_AMOUNT; i++) {
+                byte[] dt = new byte[PAGE_SIZE - 16];
+
+                ThreadLocalRandom.current().nextBytes(dt);
+
+                randomData[i][0] = ByteBuffer.wrap(dt);
+                randomData[i][1] = ByteBuffer.allocate(PAGE_SIZE);
+            }
+        }
+
+        @TearDown(Level.Iteration)
+        public void tearDown() {
+            //No - op
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        Options opt = new OptionsBuilder()
+            .include(JmhKeystoreEncryptionSpiBenchmark.class.getSimpleName())
+            .threads(1)
+            .forks(1)
+            .warmupIterations(10)
+            .measurementIterations(20)
+            .build();
+
+        new Runner(opt).run();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/codegen/src/main/java/org/apache/ignite/codegen/MessageCodeGenerator.java
----------------------------------------------------------------------
diff --git 
a/modules/codegen/src/main/java/org/apache/ignite/codegen/MessageCodeGenerator.java
 
b/modules/codegen/src/main/java/org/apache/ignite/codegen/MessageCodeGenerator.java
index 2f7e6c0..2599d7a 100644
--- 
a/modules/codegen/src/main/java/org/apache/ignite/codegen/MessageCodeGenerator.java
+++ 
b/modules/codegen/src/main/java/org/apache/ignite/codegen/MessageCodeGenerator.java
@@ -240,6 +240,8 @@ public class MessageCodeGenerator {
 //        gen.generateAndWrite(GridH2DmlResponse.class);
 //        gen.generateAndWrite(GridNearTxEnlistRequest.class);
 //        gen.generateAndWrite(GridNearTxEnlistResponse.class);
+//        gen.generateAndWrite(GenerateEncryptionKeyRequest.class);
+//        gen.generateAndWrite(GenerateEncryptionKeyResponse.class);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
 
b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
index fb3789d..795fcfd 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
@@ -50,7 +50,9 @@ import 
org.apache.ignite.cache.query.annotations.QuerySqlFunction;
 import org.apache.ignite.cache.store.CacheStore;
 import org.apache.ignite.cache.store.CacheStoreSessionListener;
 import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.spi.encryption.EncryptionSpi;
 import org.apache.ignite.internal.binary.BinaryContext;
+import org.apache.ignite.spi.encryption.keystore.KeystoreEncryptionSpi;
 import org.apache.ignite.internal.processors.query.QueryUtils;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.A;
@@ -373,6 +375,15 @@ public class CacheConfiguration<K, V> extends 
MutableConfiguration<K, V> {
     /** Events disabled. */
     private boolean evtsDisabled = DFLT_EVENTS_DISABLED;
 
+    /**
+     * Flag indicating whether data must be encrypted.
+     * If {@code true} data on the disk will be encrypted.
+     *
+     * @see EncryptionSpi
+     * @see KeystoreEncryptionSpi
+     */
+    private boolean encryptionEnabled;
+
     /** Empty constructor (all values are initialized to their defaults). */
     public CacheConfiguration() {
         /* No-op. */
@@ -412,6 +423,7 @@ public class CacheConfiguration<K, V> extends 
MutableConfiguration<K, V> {
         cpOnRead = cc.isCopyOnRead();
         dfltLockTimeout = cc.getDefaultLockTimeout();
         eagerTtl = cc.isEagerTtl();
+        encryptionEnabled = cc.isEncryptionEnabled();
         evictFilter = cc.getEvictionFilter();
         evictPlc = cc.getEvictionPolicy();
         evictPlcFactory = cc.getEvictionPolicyFactory();
@@ -2268,6 +2280,27 @@ public class CacheConfiguration<K, V> extends 
MutableConfiguration<K, V> {
         return this;
     }
 
+    /**
+     * Gets flag indicating whether data must be encrypted.
+     *
+     * @return {@code True} if this cache persistent data is encrypted.
+     */
+    public boolean isEncryptionEnabled() {
+        return encryptionEnabled;
+    }
+
+    /**
+     * Sets encrypted flag.
+     *
+     * @param encryptionEnabled {@code True} if this cache persistent data 
should be encrypted.
+     * @return {@code this} for chaining.
+     */
+    public CacheConfiguration<K, V> setEncryptionEnabled(boolean 
encryptionEnabled) {
+        this.encryptionEnabled = encryptionEnabled;
+        
+        return this;
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         return S.toString(CacheConfiguration.class, this);

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/configuration/IgniteConfiguration.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/configuration/IgniteConfiguration.java
 
b/modules/core/src/main/java/org/apache/ignite/configuration/IgniteConfiguration.java
index 964c73b..1dbec7d 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/configuration/IgniteConfiguration.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/configuration/IgniteConfiguration.java
@@ -40,6 +40,7 @@ import org.apache.ignite.cluster.ClusterGroup;
 import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.compute.ComputeJob;
 import org.apache.ignite.compute.ComputeTask;
+import org.apache.ignite.spi.encryption.EncryptionSpi;
 import org.apache.ignite.events.Event;
 import org.apache.ignite.events.EventType;
 import org.apache.ignite.failure.FailureHandler;
@@ -367,6 +368,9 @@ public class IgniteConfiguration {
     /** Address resolver. */
     private AddressResolver addrRslvr;
 
+    /** Encryption SPI. */
+    private EncryptionSpi encryptionSpi;
+
     /** Cache configurations. */
     private CacheConfiguration[] cacheCfg;
 
@@ -537,6 +541,7 @@ public class IgniteConfiguration {
         failSpi = cfg.getFailoverSpi();
         loadBalancingSpi = cfg.getLoadBalancingSpi();
         indexingSpi = cfg.getIndexingSpi();
+        encryptionSpi = cfg.getEncryptionSpi();
 
         commFailureRslvr = cfg.getCommunicationFailureResolver();
 
@@ -2062,6 +2067,28 @@ public class IgniteConfiguration {
     }
 
     /**
+     * Sets fully configured instances of {@link EncryptionSpi}.
+     *
+     * @param encryptionSpi Fully configured instance of {@link EncryptionSpi}.
+     * @see IgniteConfiguration#getEncryptionSpi()
+     * @return {@code this} for chaining.
+     */
+    public IgniteConfiguration setEncryptionSpi(EncryptionSpi encryptionSpi) {
+        this.encryptionSpi = encryptionSpi;
+
+        return this;
+    }
+
+    /**
+     * Gets fully configured encryption SPI implementations.
+     *
+     * @return Encryption SPI implementation.
+     */
+    public EncryptionSpi getEncryptionSpi() {
+        return encryptionSpi;
+    }
+
+    /**
      * Gets address resolver for addresses mapping determination.
      *
      * @return Address resolver.

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/GridComponent.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/GridComponent.java 
b/modules/core/src/main/java/org/apache/ignite/internal/GridComponent.java
index 0cf3a6e..607217e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/GridComponent.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/GridComponent.java
@@ -67,7 +67,10 @@ public interface GridComponent {
         AUTH_PROC,
 
         /** */
-        CACHE_CRD_PROC
+        CACHE_CRD_PROC,
+
+        /** Encryption manager. */
+        ENCRYPTION_MGR
     }
 
     /**
@@ -153,7 +156,7 @@ public interface GridComponent {
     @Nullable public IgniteNodeValidationResult validateNode(ClusterNode node);
 
     /** */
-    @Nullable public IgniteNodeValidationResult validateNode(ClusterNode node, 
DiscoveryDataBag.JoiningNodeDiscoveryData discoData);
+    @Nullable public IgniteNodeValidationResult validateNode(ClusterNode node, 
JoiningNodeDiscoveryData discoData);
 
     /**
      * Gets unique component type to distinguish components providing 
discovery data. Must return non-null value
@@ -180,4 +183,4 @@ public interface GridComponent {
      * @return Future to wait before completing reconnect future.
      */
     @Nullable public IgniteInternalFuture<?> onReconnected(boolean 
clusterRestarted) throws IgniteCheckedException;
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java 
b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
index 4cb68da..970b8e7 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
@@ -28,6 +28,7 @@ import 
org.apache.ignite.internal.managers.collision.GridCollisionManager;
 import org.apache.ignite.internal.managers.communication.GridIoManager;
 import org.apache.ignite.internal.managers.deployment.GridDeploymentManager;
 import org.apache.ignite.internal.managers.discovery.GridDiscoveryManager;
+import org.apache.ignite.internal.managers.encryption.GridEncryptionManager;
 import 
org.apache.ignite.internal.managers.eventstorage.GridEventStorageManager;
 import org.apache.ignite.internal.managers.failover.GridFailoverManager;
 import org.apache.ignite.internal.managers.indexing.GridIndexingManager;
@@ -425,6 +426,13 @@ public interface GridKernalContext extends 
Iterable<GridComponent> {
     public GridIndexingManager indexing();
 
     /**
+     * Gets encryption manager.
+     *
+     * @return Encryption manager.
+     */
+    public GridEncryptionManager encryption();
+
+    /**
      * Gets workers registry.
      *
      * @return Workers registry.

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
index a0e3f93..f23e650 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
@@ -38,6 +38,7 @@ import org.apache.ignite.IgniteSystemProperties;
 import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.failure.FailureType;
+import org.apache.ignite.internal.managers.encryption.GridEncryptionManager;
 import org.apache.ignite.internal.managers.checkpoint.GridCheckpointManager;
 import org.apache.ignite.internal.managers.collision.GridCollisionManager;
 import org.apache.ignite.internal.managers.communication.GridIoManager;
@@ -162,6 +163,10 @@ public class GridKernalContextImpl implements 
GridKernalContext, Externalizable
     @GridToStringExclude
     private GridIndexingManager indexingMgr;
 
+    /** */
+    @GridToStringExclude
+    private GridEncryptionManager encryptionMgr;
+
     /*
      * Processors.
      * ==========
@@ -557,6 +562,8 @@ public class GridKernalContextImpl implements 
GridKernalContext, Externalizable
             loadMgr = (GridLoadBalancerManager)comp;
         else if (comp instanceof GridIndexingManager)
             indexingMgr = (GridIndexingManager)comp;
+        else if (comp instanceof GridEncryptionManager)
+            encryptionMgr = (GridEncryptionManager)comp;
 
         /*
          * Processors.
@@ -802,6 +809,11 @@ public class GridKernalContextImpl implements 
GridKernalContext, Externalizable
     }
 
     /** {@inheritDoc} */
+    @Override public GridEncryptionManager encryption() {
+        return encryptionMgr;
+    }
+
+    /** {@inheritDoc} */
     @Override public WorkersRegistry workersRegistry() {
         return workersRegistry;
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/GridTopic.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/GridTopic.java 
b/modules/core/src/main/java/org/apache/ignite/internal/GridTopic.java
index 98a4d8d..95d7717 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/GridTopic.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/GridTopic.java
@@ -133,7 +133,10 @@ public enum GridTopic {
     TOPIC_EXCHANGE,
 
     /** */
-    TOPIC_CACHE_COORDINATOR;
+    TOPIC_CACHE_COORDINATOR,
+
+    /** */
+    TOPIC_GEN_ENC_KEY;
 
     /** Enum values. */
     private static final GridTopic[] VALS = values();

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java 
b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
index 32e5dd8..cfde78f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
@@ -101,6 +101,7 @@ import org.apache.ignite.internal.binary.BinaryMarshaller;
 import org.apache.ignite.internal.binary.BinaryUtils;
 import org.apache.ignite.internal.cluster.ClusterGroupAdapter;
 import org.apache.ignite.internal.cluster.IgniteClusterEx;
+import org.apache.ignite.internal.managers.encryption.GridEncryptionManager;
 import org.apache.ignite.internal.managers.GridManager;
 import org.apache.ignite.internal.managers.checkpoint.GridCheckpointManager;
 import org.apache.ignite.internal.managers.collision.GridCollisionManager;
@@ -987,6 +988,7 @@ public class IgniteKernal implements IgniteEx, 
IgniteMXBean, Externalizable {
             startManager(new GridFailoverManager(ctx));
             startManager(new GridCollisionManager(ctx));
             startManager(new GridIndexingManager(ctx));
+            startManager(new GridEncryptionManager(ctx));
 
             ackSecurity();
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/IgniteNodeAttributes.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/IgniteNodeAttributes.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/IgniteNodeAttributes.java
index 5b764e4..4ca4f1b 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/IgniteNodeAttributes.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/IgniteNodeAttributes.java
@@ -196,6 +196,9 @@ public final class IgniteNodeAttributes {
     /** User authentication enabled flag. */
     public static final String ATTR_AUTHENTICATION_ENABLED = ATTR_PREFIX + 
".authentication.enabled";
 
+    /** Encryption master key digest. */
+    public static final String ATTR_ENCRYPTION_MASTER_KEY_DIGEST = ATTR_PREFIX 
+ ".master.key.digest";
+
     /** Rebalance thread pool size. */
     public static final String ATTR_REBALANCE_POOL_SIZE = ATTR_PREFIX + 
".rebalance.pool.size";
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/IgnitionEx.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/IgnitionEx.java 
b/modules/core/src/main/java/org/apache/ignite/internal/IgnitionEx.java
index ed0fbe9..95001de 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/IgnitionEx.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/IgnitionEx.java
@@ -115,6 +115,7 @@ import 
org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi;
 import org.apache.ignite.spi.deployment.local.LocalDeploymentSpi;
 import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
 import 
org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder;
+import org.apache.ignite.spi.encryption.noop.NoopEncryptionSpi;
 import org.apache.ignite.spi.eventstorage.NoopEventStorageSpi;
 import org.apache.ignite.spi.failover.always.AlwaysFailoverSpi;
 import org.apache.ignite.spi.indexing.noop.NoopIndexingSpi;
@@ -2444,6 +2445,9 @@ public class IgnitionEx {
 
             if (cfg.getIndexingSpi() == null)
                 cfg.setIndexingSpi(new NoopIndexingSpi());
+
+            if (cfg.getEncryptionSpi() == null)
+                cfg.setEncryptionSpi(new NoopEncryptionSpi());
         }
 
         /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoMessageFactory.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoMessageFactory.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoMessageFactory.java
index 54efb47..e405d7d 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoMessageFactory.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoMessageFactory.java
@@ -46,6 +46,8 @@ import 
org.apache.ignite.internal.processors.cache.CacheEvictionEntry;
 import org.apache.ignite.internal.processors.cache.CacheInvokeDirectResult;
 import org.apache.ignite.internal.processors.cache.CacheObjectByteArrayImpl;
 import org.apache.ignite.internal.processors.cache.CacheObjectImpl;
+import 
org.apache.ignite.internal.managers.encryption.GenerateEncryptionKeyRequest;
+import 
org.apache.ignite.internal.managers.encryption.GenerateEncryptionKeyResponse;
 import org.apache.ignite.internal.processors.cache.GridCacheEntryInfo;
 import org.apache.ignite.internal.processors.cache.GridCacheMvccEntryInfo;
 import org.apache.ignite.internal.processors.cache.GridCacheReturn;
@@ -1084,6 +1086,16 @@ public class GridIoMessageFactory implements 
MessageFactory {
 
                 break;
 
+            case 162:
+                msg = new GenerateEncryptionKeyRequest();
+
+                break;
+
+            case 163:
+                msg = new GenerateEncryptionKeyResponse();
+
+                break;
+
                 // [-3..119] [124..129] [-23..-27] [-36..-55]- this
             // [120..123] - DR
             // [-4..-22, -30..-35] - SQL

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java
index 19c11ac..d7514a0 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java
@@ -795,6 +795,8 @@ public class GridDiscoveryManager extends 
GridManagerAdapter<DiscoverySpi> {
                         ctx.cache().context().exchange().onLocalJoin(discoEvt, 
discoCache);
 
                         ctx.authentication().onLocalJoin();
+
+                        ctx.encryption().onLocalJoin();
                     }
 
                     IgniteInternalFuture<Boolean> transitionWaitFut = 
ctx.state().onLocalJoin(discoCache);

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GenerateEncryptionKeyRequest.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GenerateEncryptionKeyRequest.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GenerateEncryptionKeyRequest.java
new file mode 100644
index 0000000..3d48014
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GenerateEncryptionKeyRequest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.ignite.internal.managers.encryption;
+
+import java.nio.ByteBuffer;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.plugin.extensions.communication.Message;
+import org.apache.ignite.plugin.extensions.communication.MessageReader;
+import org.apache.ignite.plugin.extensions.communication.MessageWriter;
+
+/**
+ * Generate encryption key request.
+ */
+public class GenerateEncryptionKeyRequest implements Message {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Request ID. */
+    private IgniteUuid id = IgniteUuid.randomUuid();
+
+    /** */
+    private int keyCnt;
+
+    /** */
+    public GenerateEncryptionKeyRequest() {
+    }
+
+    /**
+     * @param keyCnt Count of encryption key to generate.
+     */
+    public GenerateEncryptionKeyRequest(int keyCnt) {
+        this.keyCnt = keyCnt;
+    }
+
+    /**
+     * @return Request id.
+     */
+    public IgniteUuid id() {
+        return id;
+    }
+
+    /**
+     * @return Count of encryption key to generate.
+     */
+    public int keyCount() {
+        return keyCnt;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean writeTo(ByteBuffer buf, MessageWriter writer) {
+        writer.setBuffer(buf);
+
+        if (!writer.isHeaderWritten()) {
+            if (!writer.writeHeader(directType(), fieldsCount()))
+                return false;
+
+            writer.onHeaderWritten();
+        }
+
+        switch (writer.state()) {
+            case 0:
+                if (!writer.writeIgniteUuid("id", id))
+                    return false;
+
+                writer.incrementState();
+
+            case 1:
+                if (!writer.writeInt("keyCnt", keyCnt))
+                    return false;
+
+                writer.incrementState();
+
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean readFrom(ByteBuffer buf, MessageReader reader) {
+        reader.setBuffer(buf);
+
+        if (!reader.beforeMessageRead())
+            return false;
+
+        switch (reader.state()) {
+            case 0:
+                id = reader.readIgniteUuid("id");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 1:
+                keyCnt = reader.readInt("keyCnt");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+        }
+
+        return reader.afterMessageRead(GenerateEncryptionKeyRequest.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public short directType() {
+        return 162;
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte fieldsCount() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onAckReceived() {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(GenerateEncryptionKeyRequest.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GenerateEncryptionKeyResponse.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GenerateEncryptionKeyResponse.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GenerateEncryptionKeyResponse.java
new file mode 100644
index 0000000..8971248
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GenerateEncryptionKeyResponse.java
@@ -0,0 +1,148 @@
+/*
+ * 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.ignite.internal.managers.encryption;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import org.apache.ignite.internal.GridDirectCollection;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.plugin.extensions.communication.Message;
+import 
org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+import org.apache.ignite.plugin.extensions.communication.MessageReader;
+import org.apache.ignite.plugin.extensions.communication.MessageWriter;
+
+/**
+ * Generate encryption key response.
+ */
+public class GenerateEncryptionKeyResponse implements Message {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Request message ID. */
+    private IgniteUuid id;
+
+    /** */
+    @GridDirectCollection(byte[].class)
+    private Collection<byte[]> encKeys;
+
+    /** */
+    public GenerateEncryptionKeyResponse() {
+    }
+
+    /**
+     * @param id Request id.
+     * @param encKeys Encryption keys.
+     */
+    public GenerateEncryptionKeyResponse(IgniteUuid id, Collection<byte[]> 
encKeys) {
+        this.id = id;
+        this.encKeys = encKeys;
+    }
+
+    /**
+     * @return Request id.
+     */
+    public IgniteUuid requestId() {
+        return id;
+    }
+
+    /**
+     * @return Encryption keys.
+     */
+    public Collection<byte[]> encryptionKeys() {
+        return encKeys;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean writeTo(ByteBuffer buf, MessageWriter writer) {
+        writer.setBuffer(buf);
+
+        if (!writer.isHeaderWritten()) {
+            if (!writer.writeHeader(directType(), fieldsCount()))
+                return false;
+
+            writer.onHeaderWritten();
+        }
+
+        switch (writer.state()) {
+            case 0:
+                if (!writer.writeCollection("encKeys", encKeys, 
MessageCollectionItemType.BYTE_ARR))
+                    return false;
+
+                writer.incrementState();
+
+            case 1:
+                if (!writer.writeIgniteUuid("id", id))
+                    return false;
+
+                writer.incrementState();
+
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean readFrom(ByteBuffer buf, MessageReader reader) {
+        reader.setBuffer(buf);
+
+        if (!reader.beforeMessageRead())
+            return false;
+
+        switch (reader.state()) {
+            case 0:
+                encKeys = reader.readCollection("encKeys", 
MessageCollectionItemType.BYTE_ARR);
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 1:
+                id = reader.readIgniteUuid("id");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+        }
+
+        return reader.afterMessageRead(GenerateEncryptionKeyResponse.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public short directType() {
+        return 163;
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte fieldsCount() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onAckReceived() {
+        //No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(GenerateEncryptionKeyResponse.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GridEncryptionManager.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GridEncryptionManager.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GridEncryptionManager.java
new file mode 100644
index 0000000..a1c0fdc
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GridEncryptionManager.java
@@ -0,0 +1,864 @@
+/*
+ * 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.ignite.internal.managers.encryption;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.spi.encryption.EncryptionSpi;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.managers.GridManagerAdapter;
+import org.apache.ignite.internal.managers.communication.GridMessageListener;
+import org.apache.ignite.internal.managers.eventstorage.DiscoveryEventListener;
+import org.apache.ignite.internal.processors.cache.CacheGroupDescriptor;
+import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
+import 
org.apache.ignite.internal.processors.cache.persistence.metastorage.MetastorageLifecycleListener;
+import 
org.apache.ignite.internal.processors.cache.persistence.metastorage.ReadOnlyMetastorage;
+import 
org.apache.ignite.internal.processors.cache.persistence.metastorage.ReadWriteMetastorage;
+import 
org.apache.ignite.internal.processors.cluster.IgniteChangeGlobalStateSupport;
+import org.apache.ignite.internal.util.future.GridFinishedFuture;
+import org.apache.ignite.internal.util.future.GridFutureAdapter;
+import org.apache.ignite.internal.util.lang.GridPlainClosure;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteFuture;
+import org.apache.ignite.lang.IgniteFutureCancelledException;
+import org.apache.ignite.lang.IgnitePredicate;
+import org.apache.ignite.lang.IgniteProductVersion;
+import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.spi.IgniteNodeValidationResult;
+import org.apache.ignite.spi.discovery.DiscoveryDataBag;
+import org.apache.ignite.spi.discovery.DiscoveryDataBag.GridDiscoveryData;
+import 
org.apache.ignite.spi.discovery.DiscoveryDataBag.JoiningNodeDiscoveryData;
+import org.apache.ignite.spi.discovery.DiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.events.EventType.EVT_NODE_FAILED;
+import static org.apache.ignite.events.EventType.EVT_NODE_LEFT;
+import static 
org.apache.ignite.internal.GridComponent.DiscoveryDataExchangeType.ENCRYPTION_MGR;
+import static org.apache.ignite.internal.GridTopic.TOPIC_GEN_ENC_KEY;
+import static 
org.apache.ignite.internal.IgniteNodeAttributes.ATTR_ENCRYPTION_MASTER_KEY_DIGEST;
+import static 
org.apache.ignite.internal.managers.communication.GridIoPolicy.SYSTEM_POOL;
+
+/**
+ * Manages cache keys and {@code EncryptionSpi} instances.
+ *
+ * NOTE: Following protocol applied to statically configured caches.
+ * For dynamically created caches key generated in request creation.
+ *
+ * Group keys generation protocol:
+ *
+ * <ul>
+ *     <li>Joining node:
+ *     <ul>
+ *         <li>1. Collects and send all stored group keys to coordinator.</li>
+ *         <li>2. Generate(but doesn't store locally!) and send keys for all 
statically configured groups in case the not presented in metastore.</li>
+ *         <li>3. Store all keys received from coordinator to local store.</li>
+ *     </ul>
+ *     </li>
+ *     <li>Coordinator:
+ *     <ul>
+ *         <li>1. Checks master key digest are equal to local. If not join is 
rejected.</li>
+ *         <li>2. Checks all stored keys from joining node are equal to stored 
keys. If not join is rejected.</li>
+ *         <li>3. Collects all stored keys and sends it to joining node.</li>
+ *     </ul>
+ *     </li>
+ *     <li>All nodes:
+ *     <ul>
+ *         <li>1. If new key for group doesn't exists locally it added to 
local store.</li>
+ *         <li>2. If new key for group exists locally, then received key 
skipped.</li>
+ *     </ul>
+ *     </li>
+ * </ul>
+ *
+ * @see GridCacheProcessor#generateEncryptionKeysAndStartCacheAfter(int, 
GridPlainClosure)
+ */
+public class GridEncryptionManager extends GridManagerAdapter<EncryptionSpi> 
implements MetastorageLifecycleListener,
+    IgniteChangeGlobalStateSupport {
+    /**
+     * Cache encryption introduced in this Ignite version.
+     */
+    private static final IgniteProductVersion CACHE_ENCRYPTION_SINCE = 
IgniteProductVersion.fromString("2.7.0");
+
+    /** Synchronization mutex. */
+    private final Object metaStorageMux = new Object();
+
+    /** Synchronization mutex for an generate encryption keys operations. */
+    private final Object genEcnKeyMux = new Object();
+
+    /** Disconnected flag. */
+    private volatile boolean disconnected;
+
+    /** Stopped flag. */
+    private volatile boolean stopped;
+
+    /** Flag to enable/disable write to metastore on cluster state change. */
+    private volatile boolean writeToMetaStoreEnabled;
+
+    /** Prefix for a encryption group key in meta store. */
+    public static final String ENCRYPTION_KEY_PREFIX = "grp-encryption-key-";
+
+    /** Encryption key predicate for meta store. */
+    private static final IgnitePredicate<String> ENCRYPTION_KEY_PREFIX_PRED =
+        (IgnitePredicate<String>)key -> key.startsWith(ENCRYPTION_KEY_PREFIX);
+
+    /** Group encryption keys. */
+    private Map<Integer, Serializable> grpEncKeys = new HashMap<>();
+
+    /** Pending generate encryption key futures. */
+    private ConcurrentMap<IgniteUuid, GenerateEncryptionKeyFuture> 
genEncKeyFuts = new ConcurrentHashMap<>();
+
+    /** Metastorage. */
+    private volatile ReadWriteMetastorage metaStorage;
+
+    /** I/O message listener. */
+    private GridMessageListener ioLsnr;
+
+    /** System discovery message listener. */
+    private DiscoveryEventListener discoLsnr;
+
+    /**
+     * @param ctx Kernel context.
+     */
+    public GridEncryptionManager(GridKernalContext ctx) {
+        super(ctx, ctx.config().getEncryptionSpi());
+
+        ctx.internalSubscriptionProcessor().registerMetastorageListener(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void start() throws IgniteCheckedException {
+        startSpi();
+
+        if (getSpi().masterKeyDigest() != null)
+            ctx.addNodeAttribute(ATTR_ENCRYPTION_MASTER_KEY_DIGEST, 
getSpi().masterKeyDigest());
+
+        ctx.event().addDiscoveryEventListener(discoLsnr = (evt, discoCache) -> 
{
+            UUID leftNodeId = evt.eventNode().id();
+
+            synchronized (genEcnKeyMux) {
+                Iterator<Map.Entry<IgniteUuid, GenerateEncryptionKeyFuture>> 
futsIter =
+                    genEncKeyFuts.entrySet().iterator();
+
+                while (futsIter.hasNext()) {
+                    GenerateEncryptionKeyFuture fut = 
futsIter.next().getValue();
+
+                    if (!F.eq(leftNodeId, fut.nodeId()))
+                        return;
+
+                    try {
+                        futsIter.remove();
+
+                        sendGenerateEncryptionKeyRequest(fut);
+
+                        genEncKeyFuts.put(fut.id(), fut);
+                    }
+                    catch (IgniteCheckedException e) {
+                        fut.onDone(null, e);
+                    }
+                }
+            }
+        }, EVT_NODE_LEFT, EVT_NODE_FAILED);
+
+        ctx.io().addMessageListener(TOPIC_GEN_ENC_KEY, ioLsnr = (nodeId, msg, 
plc) -> {
+            synchronized (genEcnKeyMux) {
+                if (msg instanceof GenerateEncryptionKeyRequest) {
+                    GenerateEncryptionKeyRequest req = 
(GenerateEncryptionKeyRequest)msg;
+
+                    assert req.keyCount() != 0;
+
+                    List<byte[]> encKeys = new ArrayList<>(req.keyCount());
+
+                    for (int i = 0; i < req.keyCount(); i++)
+                        encKeys.add(getSpi().encryptKey(getSpi().create()));
+
+                    try {
+                        ctx.io().sendToGridTopic(nodeId, TOPIC_GEN_ENC_KEY,
+                            new GenerateEncryptionKeyResponse(req.id(), 
encKeys), SYSTEM_POOL);
+                    }
+                    catch (IgniteCheckedException e) {
+                        U.error(log, "Unable to send generate key 
response[nodeId=" + nodeId + "]");
+                    }
+                }
+                else {
+                    GenerateEncryptionKeyResponse resp = 
(GenerateEncryptionKeyResponse)msg;
+
+                    GenerateEncryptionKeyFuture fut = 
genEncKeyFuts.get(resp.requestId());
+
+                    if (fut != null)
+                        fut.onDone(resp.encryptionKeys(), null);
+                    else
+                        U.warn(log, "Response received for a unknown 
request.[reqId=" + resp.requestId() + "]");
+                }
+            }
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public void stop(boolean cancel) throws IgniteCheckedException {
+        stopSpi();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void onKernalStart0() throws IgniteCheckedException {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void onKernalStop0(boolean cancel) {
+        synchronized (genEcnKeyMux) {
+            stopped = true;
+
+            if (ioLsnr != null)
+                ctx.io().removeMessageListener(TOPIC_GEN_ENC_KEY, ioLsnr);
+
+            if (discoLsnr != null)
+                ctx.event().removeDiscoveryEventListener(discoLsnr, 
EVT_NODE_LEFT, EVT_NODE_FAILED);
+
+            cancelFutures("Kernal stopped.");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onDisconnected(IgniteFuture<?> reconnectFut) {
+        synchronized (genEcnKeyMux) {
+            assert !disconnected;
+
+            disconnected = true;
+
+            cancelFutures("Client node was disconnected from topology 
(operation result is unknown).");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgniteInternalFuture<?> onReconnected(boolean 
clusterRestarted) {
+        synchronized (genEcnKeyMux) {
+            assert disconnected;
+
+            disconnected = false;
+
+            return null;
+        }
+    }
+
+    /**
+     * Callback for local join.
+     */
+    public void onLocalJoin() {
+        if (notCoordinator())
+            return;
+
+        //We can't store keys before node join to cluster(on statically 
configured cache registration).
+        //Because, keys should be received from cluster.
+        //Otherwise, we would generate different keys on each started node.
+        //So, after starting, coordinator saves locally newly generated 
encryption keys.
+        //And sends that keys to every joining node.
+        synchronized (metaStorageMux) {
+            //Keys read from meta storage.
+            HashMap<Integer, byte[]> knownEncKeys = knownEncryptionKeys();
+
+            //Generated(not saved!) keys for a new caches.
+            //Configured statically in config, but doesn't stored on the disk.
+            HashMap<Integer, byte[]> newEncKeys =
+                newEncryptionKeys(knownEncKeys == null ? Collections.EMPTY_SET 
: knownEncKeys.keySet());
+
+            if (newEncKeys == null)
+                return;
+
+            //We can store keys to the disk, because we are on a coordinator.
+            for (Map.Entry<Integer, byte[]> entry : newEncKeys.entrySet()) {
+                groupKey(entry.getKey(), entry.getValue());
+
+                U.quietAndInfo(log, "Added encryption key on local join 
[grpId=" + entry.getKey() + "]");
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Nullable @Override public IgniteNodeValidationResult 
validateNode(ClusterNode node,
+        JoiningNodeDiscoveryData discoData) {
+        IgniteNodeValidationResult res = super.validateNode(node, discoData);
+
+        if (res != null)
+            return res;
+
+        if (node.isClient())
+            return null;
+
+        res = validateNode(node);
+
+        if (res != null)
+            return res;
+
+        if (!discoData.hasJoiningNodeData()) {
+            U.quietAndInfo(log, "Joining node doesn't have encryption data 
[node=" + node.id() + "]");
+
+            return null;
+        }
+
+        NodeEncryptionKeys nodeEncKeys = 
(NodeEncryptionKeys)discoData.joiningNodeData();
+
+        if (nodeEncKeys == null || F.isEmpty(nodeEncKeys.knownKeys)) {
+            U.quietAndInfo(log, "Joining node doesn't have stored group keys 
[node=" + node.id() + "]");
+
+            return null;
+        }
+
+        for (Map.Entry<Integer, byte[]> entry : 
nodeEncKeys.knownKeys.entrySet()) {
+            Serializable locEncKey = grpEncKeys.get(entry.getKey());
+
+            if (locEncKey == null)
+                continue;
+
+            Serializable rmtKey = getSpi().decryptKey(entry.getValue());
+
+            if (F.eq(locEncKey, rmtKey))
+                continue;
+
+            return new IgniteNodeValidationResult(ctx.localNodeId(),
+                "Cache key differs! Node join is rejected. [node=" + node.id() 
+ ", grp=" + entry.getKey() + "]",
+                "Cache key differs! Node join is rejected.");
+        }
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Nullable @Override public IgniteNodeValidationResult 
validateNode(ClusterNode node) {
+        IgniteNodeValidationResult res = super.validateNode(node);
+
+        if (res != null)
+            return res;
+
+        if (node.isClient())
+            return null;
+
+        byte[] lclMkDig = getSpi().masterKeyDigest();
+
+        byte[] rmtMkDig = node.attribute(ATTR_ENCRYPTION_MASTER_KEY_DIGEST);
+
+        if (Arrays.equals(lclMkDig, rmtMkDig))
+            return null;
+
+        return new IgniteNodeValidationResult(ctx.localNodeId(),
+            "Master key digest differs! Node join is rejected. [node=" + 
node.id() + "]",
+            "Master key digest differs! Node join is rejected.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void collectJoiningNodeData(DiscoveryDataBag dataBag) {
+        HashMap<Integer, byte[]> knownEncKeys = knownEncryptionKeys();
+
+        HashMap<Integer, byte[]> newKeys =
+            newEncryptionKeys(knownEncKeys == null ? Collections.EMPTY_SET : 
knownEncKeys.keySet());
+
+        if ((knownEncKeys == null && newKeys == null) || 
dataBag.isJoiningNodeClient())
+            return;
+
+        if (log.isInfoEnabled()) {
+            String knownGrps = F.isEmpty(knownEncKeys) ? null : 
F.concat(knownEncKeys.keySet(), ",");
+
+            if (knownGrps != null)
+                U.quietAndInfo(log, "Sending stored group keys to coordinator 
[grps=" + knownGrps + "]");
+
+            String newGrps = F.isEmpty(newKeys) ? null : 
F.concat(newKeys.keySet(), ",");
+
+            if (newGrps != null)
+                U.quietAndInfo(log, "Sending new group keys to coordinator 
[grps=" + newGrps + "]");
+        }
+
+        dataBag.addJoiningNodeData(ENCRYPTION_MGR.ordinal(), new 
NodeEncryptionKeys(knownEncKeys, newKeys));
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onJoiningNodeDataReceived(JoiningNodeDiscoveryData 
data) {
+        NodeEncryptionKeys nodeEncryptionKeys = 
(NodeEncryptionKeys)data.joiningNodeData();
+
+        if (nodeEncryptionKeys == null || nodeEncryptionKeys.newKeys == null 
|| ctx.clientNode())
+            return;
+
+        for (Map.Entry<Integer, byte[]> entry : 
nodeEncryptionKeys.newKeys.entrySet()) {
+            if (groupKey(entry.getKey()) == null) {
+                U.quietAndInfo(log, "Store group key received from joining 
node [node=" +
+                        data.joiningNodeId() + ", grp=" + entry.getKey() + 
"]");
+
+                groupKey(entry.getKey(), entry.getValue());
+            }
+            else {
+                U.quietAndInfo(log, "Skip group key received from joining 
node. Already exists. [node=" +
+                    data.joiningNodeId() + ", grp=" + entry.getKey() + "]");
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void collectGridNodeData(DiscoveryDataBag dataBag) {
+        if (dataBag.isJoiningNodeClient() || 
dataBag.commonDataCollectedFor(ENCRYPTION_MGR.ordinal()))
+            return;
+
+        HashMap<Integer, byte[]> knownEncKeys = knownEncryptionKeys();
+
+        HashMap<Integer, byte[]> newKeys =
+            newEncryptionKeys(knownEncKeys == null ? Collections.EMPTY_SET : 
knownEncKeys.keySet());
+
+        if (knownEncKeys == null)
+            knownEncKeys = newKeys;
+        else if (newKeys != null) {
+            for (Map.Entry<Integer, byte[]> entry : newKeys.entrySet()) {
+                byte[] old = knownEncKeys.putIfAbsent(entry.getKey(), 
entry.getValue());
+
+                assert old == null;
+            }
+        }
+
+        dataBag.addGridCommonData(ENCRYPTION_MGR.ordinal(), knownEncKeys);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onGridDataReceived(GridDiscoveryData data) {
+        Map<Integer, byte[]> encKeysFromCluster = (Map<Integer, 
byte[]>)data.commonData();
+
+        if (F.isEmpty(encKeysFromCluster))
+            return;
+
+        for (Map.Entry<Integer, byte[]> entry : encKeysFromCluster.entrySet()) 
{
+            if (groupKey(entry.getKey()) == null) {
+                U.quietAndInfo(log, "Store group key received from coordinator 
[grp=" + entry.getKey() + "]");
+
+                groupKey(entry.getKey(), entry.getValue());
+            }
+            else {
+                U.quietAndInfo(log, "Skip group key received from coordinator. 
Already exists. [grp=" +
+                    entry.getKey() + "]");
+            }
+        }
+    }
+
+    /**
+     * Returns group encryption key.
+     *
+     * @param grpId Group id.
+     * @return Group encryption key.
+     */
+    @Nullable public Serializable groupKey(int grpId) {
+        return grpEncKeys.get(grpId);
+    }
+
+    /**
+     * Store group encryption key.
+     *
+     * @param grpId Group id.
+     * @param encGrpKey Encrypted group key.
+     */
+    public void groupKey(int grpId, byte[] encGrpKey) {
+        assert !grpEncKeys.containsKey(grpId);
+
+        Serializable encKey = getSpi().decryptKey(encGrpKey);
+
+        synchronized (metaStorageMux) {
+            if (log.isDebugEnabled())
+                log.debug("Key added. [grp=" + grpId + "]");
+
+            grpEncKeys.put(grpId, encKey);
+
+            writeToMetaStore(grpId, encGrpKey);
+        }
+    }
+
+    /**
+     * Removes encryption key.
+     *
+     * @param grpId Group id.
+     */
+    private void removeGroupKey(int grpId) {
+        synchronized (metaStorageMux) {
+            ctx.cache().context().database().checkpointReadLock();
+
+            try {
+                grpEncKeys.remove(grpId);
+
+                metaStorage.remove(ENCRYPTION_KEY_PREFIX + grpId);
+
+                if (log.isDebugEnabled())
+                    log.debug("Key removed. [grp=" + grpId + "]");
+            }
+            catch (IgniteCheckedException e) {
+                U.error(log, "Failed to clear meta storage", e);
+            }
+            finally {
+                ctx.cache().context().database().checkpointReadUnlock();
+            }
+        }
+    }
+
+    /**
+     * Callback for cache group start event.
+     * @param grpId Group id.
+     * @param encKey Encryption key
+     */
+    public void beforeCacheGroupStart(int grpId, @Nullable byte[] encKey) {
+        if (encKey == null || ctx.clientNode())
+            return;
+
+        groupKey(grpId, encKey);
+    }
+
+    /**
+     * Callback for cache group destroy event.
+     * @param grpId Group id.
+     */
+    public void onCacheGroupDestroyed(int grpId) {
+        if (groupKey(grpId) == null)
+            return;
+
+        removeGroupKey(grpId);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadyForRead(ReadOnlyMetastorage metastorage) {
+        try {
+            Map<String, ? extends Serializable> encKeys = 
metastorage.readForPredicate(ENCRYPTION_KEY_PREFIX_PRED);
+
+            if (encKeys.isEmpty())
+                return;
+
+            for (String key : encKeys.keySet()) {
+                Integer grpId = 
Integer.valueOf(key.replace(ENCRYPTION_KEY_PREFIX, ""));
+
+                byte[] encGrpKey = (byte[])encKeys.get(key);
+
+                grpEncKeys.putIfAbsent(grpId, getSpi().decryptKey(encGrpKey));
+            }
+
+            if (!grpEncKeys.isEmpty()) {
+                U.quietAndInfo(log, "Encryption keys loaded from metastore. 
[grps=" +
+                    F.concat(grpEncKeys.keySet(), ",") + "]");
+            }
+        }
+        catch (IgniteCheckedException e) {
+            throw new IgniteException("Failed to read encryption keys state.", 
e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadyForReadWrite(ReadWriteMetastorage 
metaStorage) throws IgniteCheckedException {
+        synchronized (metaStorageMux) {
+            this.metaStorage = metaStorage;
+
+            writeToMetaStoreEnabled = true;
+
+            writeAllToMetaStore();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onActivate(GridKernalContext kctx) throws 
IgniteCheckedException {
+        synchronized (metaStorageMux) {
+            writeToMetaStoreEnabled = metaStorage != null;
+
+            if (writeToMetaStoreEnabled)
+                writeAllToMetaStore();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onDeActivate(GridKernalContext kctx) {
+        synchronized (metaStorageMux) {
+            writeToMetaStoreEnabled = false;
+        }
+    }
+
+    /**
+     * @param keyCnt Count of keys to generate.
+     * @return Future that will contain results of generation.
+     */
+    public IgniteInternalFuture<Collection<byte[]>> generateKeys(int keyCnt) {
+        if (keyCnt == 0 || !ctx.clientNode())
+            return new GridFinishedFuture<>(createKeys(keyCnt));
+
+        synchronized (genEcnKeyMux) {
+            if (disconnected || stopped) {
+                return new GridFinishedFuture<>(
+                    new IgniteFutureCancelledException("Node " + (stopped ? 
"stopped" : "disconnected")));
+            }
+
+            try {
+                GenerateEncryptionKeyFuture genEncKeyFut = new 
GenerateEncryptionKeyFuture(keyCnt);
+
+                sendGenerateEncryptionKeyRequest(genEncKeyFut);
+
+                genEncKeyFuts.put(genEncKeyFut.id(), genEncKeyFut);
+
+                return genEncKeyFut;
+            }
+            catch (IgniteCheckedException e) {
+                return new GridFinishedFuture<>(e);
+            }
+        }
+    }
+
+    /** */
+    private void sendGenerateEncryptionKeyRequest(GenerateEncryptionKeyFuture 
fut) throws IgniteCheckedException {
+        ClusterNode rndNode = U.randomServerNode(ctx);
+
+        if (rndNode == null)
+            throw new IgniteCheckedException("There is no node to send 
GenerateEncryptionKeyRequest to");
+
+        GenerateEncryptionKeyRequest req = new 
GenerateEncryptionKeyRequest(fut.keyCount());
+
+        fut.id(req.id());
+        fut.nodeId(rndNode.id());
+
+        ctx.io().sendToGridTopic(rndNode.id(), TOPIC_GEN_ENC_KEY, req, 
SYSTEM_POOL);
+    }
+
+    /**
+     * Writes all unsaved grpEncKeys to metaStorage.
+     * @throws IgniteCheckedException If failed.
+     */
+    private void writeAllToMetaStore() throws IgniteCheckedException {
+        for (Map.Entry<Integer, Serializable> entry : grpEncKeys.entrySet()) {
+            if (metaStorage.read(ENCRYPTION_KEY_PREFIX + entry.getKey()) != 
null)
+                continue;
+
+            writeToMetaStore(entry.getKey(), 
getSpi().encryptKey(entry.getValue()));
+        }
+    }
+
+    /**
+     * Checks cache encryption supported by all nodes in cluster.
+     *
+     * @throws IgniteCheckedException If check fails.
+     */
+    public void checkEncryptedCacheSupported() throws IgniteCheckedException {
+        Collection<ClusterNode> nodes = ctx.grid().cluster().nodes();
+
+        for (ClusterNode node : nodes) {
+            if (CACHE_ENCRYPTION_SINCE.compareTo(node.version()) > 0) {
+                throw new IgniteCheckedException("All nodes in cluster should 
be 2.7.0 or greater " +
+                    "to create encrypted cache! [nodeId=" + node.id() + "]");
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public DiscoveryDataExchangeType discoveryDataType() {
+        return ENCRYPTION_MGR;
+    }
+
+    /**
+     * Writes encryption key to metastore.
+     *
+     * @param grpId Group id.
+     * @param encGrpKey Group encryption key.
+     */
+    private void writeToMetaStore(int grpId, byte[] encGrpKey) {
+        if (metaStorage == null || !writeToMetaStoreEnabled)
+            return;
+
+        ctx.cache().context().database().checkpointReadLock();
+
+        try {
+            metaStorage.write(ENCRYPTION_KEY_PREFIX + grpId, encGrpKey);
+        }
+        catch (IgniteCheckedException e) {
+            throw new IgniteException("Failed to write cache group encryption 
key [grpId=" + grpId + ']', e);
+        }
+        finally {
+            ctx.cache().context().database().checkpointReadUnlock();
+        }
+    }
+
+    /**
+     * @param knownKeys Saved keys set.
+     * @return New keys for local cache groups.
+     */
+    @Nullable private HashMap<Integer, byte[]> newEncryptionKeys(Set<Integer> 
knownKeys) {
+        Map<Integer, CacheGroupDescriptor> grpDescs = 
ctx.cache().cacheGroupDescriptors();
+
+        HashMap<Integer, byte[]> newKeys = null;
+
+        for (CacheGroupDescriptor grpDesc : grpDescs.values()) {
+            if (knownKeys.contains(grpDesc.groupId()) || 
!grpDesc.config().isEncryptionEnabled())
+                continue;
+
+            if (newKeys == null)
+                newKeys = new HashMap<>();
+
+            newKeys.put(grpDesc.groupId(), 
getSpi().encryptKey(getSpi().create()));
+        }
+
+        return newKeys;
+    }
+
+    /**
+     * @return Local encryption keys.
+     */
+    @Nullable private HashMap<Integer, byte[]> knownEncryptionKeys() {
+        if (F.isEmpty(grpEncKeys))
+            return null;
+
+        HashMap<Integer, byte[]> knownKeys = new HashMap<>();
+
+        for (Map.Entry<Integer, Serializable> entry : grpEncKeys.entrySet())
+            knownKeys.put(entry.getKey(), 
getSpi().encryptKey(entry.getValue()));
+
+        return knownKeys;
+    }
+
+    /**
+     * Generates required count of encryption keys.
+     *
+     * @param keyCnt Keys count.
+     * @return Collection with newly generated encryption keys.
+     */
+    private Collection<byte[]> createKeys(int keyCnt) {
+        if (keyCnt == 0)
+            return Collections.emptyList();
+
+        List<byte[]> encKeys = new ArrayList<>(keyCnt);
+
+        for(int i=0; i<keyCnt; i++)
+            encKeys.add(getSpi().encryptKey(getSpi().create()));
+
+        return encKeys;
+    }
+
+    /**
+     * @param msg Error message.
+     */
+    private void cancelFutures(String msg) {
+        for (GenerateEncryptionKeyFuture fut : genEncKeyFuts.values())
+            fut.onDone(new IgniteFutureCancelledException(msg));
+    }
+
+    /**
+     * Checks whether local node is coordinator. Nodes that are leaving or 
failed
+     * (but are still in topology) are removed from search.
+     *
+     * @return {@code true} if local node is coordinator.
+     */
+    private boolean notCoordinator() {
+        DiscoverySpi spi = ctx.discovery().getInjectedDiscoverySpi();
+
+        if (spi instanceof TcpDiscoverySpi)
+            return !((TcpDiscoverySpi)spi).isLocalNodeCoordinator();
+        else {
+            ClusterNode crd = null;
+
+            for (ClusterNode node : ctx.discovery().aliveServerNodes()) {
+                if (crd == null || crd.order() > node.order())
+                    crd = node;
+            }
+
+            return crd == null || !F.eq(ctx.localNodeId(), crd.id());
+        }
+    }
+
+    /** */
+    public static class NodeEncryptionKeys implements Serializable {
+        /** */
+        private static final long serialVersionUID = 0L;
+
+        /** */
+        NodeEncryptionKeys(Map<Integer, byte[]> knownKeys, Map<Integer, 
byte[]> newKeys) {
+            this.knownKeys = knownKeys;
+            this.newKeys = newKeys;
+        }
+
+        /** Known i.e. stored in {@code ReadWriteMetastorage} keys from node. 
*/
+        Map<Integer, byte[]> knownKeys;
+
+        /**  New keys i.e. keys for a local statically configured caches. */
+        Map<Integer, byte[]> newKeys;
+    }
+
+    /** */
+    @SuppressWarnings("ExternalizableWithoutPublicNoArgConstructor")
+    private class GenerateEncryptionKeyFuture extends 
GridFutureAdapter<Collection<byte[]>> {
+        /** */
+        private IgniteUuid id;
+
+        /** */
+        private int keyCnt;
+
+        /** */
+        private UUID nodeId;
+
+        /**
+         * @param keyCnt Count of keys to generate.
+         */
+        private GenerateEncryptionKeyFuture(int keyCnt) {
+            this.keyCnt = keyCnt;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean onDone(@Nullable Collection<byte[]> res, 
@Nullable Throwable err) {
+            // Make sure to remove future before completion.
+            genEncKeyFuts.remove(id, this);
+
+            return super.onDone(res, err);
+        }
+
+        /** */
+        public IgniteUuid id() {
+            return id;
+        }
+
+        /** */
+        public void id(IgniteUuid id) {
+            this.id = id;
+        }
+
+        /** */
+        public UUID nodeId() {
+            return nodeId;
+        }
+
+        /** */
+        public void nodeId(UUID nodeId) {
+            this.nodeId = nodeId;
+        }
+
+        /** */
+        public int keyCount() {
+            return keyCnt;
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(GenerateEncryptionKeyFuture.class, this);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageMemory.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageMemory.java 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageMemory.java
index f7391d2..3ef0ec7 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageMemory.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageMemory.java
@@ -43,6 +43,12 @@ public interface PageMemory extends PageIdAllocator, 
PageSupport {
     public int pageSize();
 
     /**
+     * @param grpId Group id.
+     * @return Page size without encryption overhead.
+     */
+    public int realPageSize(int grpId);
+
+    /**
      * @return Page size with system overhead, in bytes.
      */
     public int systemPageSize();

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
index 02afac8..66d713c 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
@@ -341,6 +341,11 @@ public class PageMemoryNoStoreImpl implements PageMemory {
         return sysPageSize;
     }
 
+    /** {@inheritDoc} */
+    @Override public int realPageSize(int grpId) {
+        return pageSize();
+    }
+
     /**
      * @return Next index.
      */

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/pagemem/store/PageStore.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/store/PageStore.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/store/PageStore.java
index 42d584d..7a7f964 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/store/PageStore.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/store/PageStore.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.pagemem.store;
 import org.apache.ignite.IgniteCheckedException;
 
 import java.nio.ByteBuffer;
+import 
org.apache.ignite.internal.processors.cache.persistence.StorageException;
 
 /**
  * Persistent store of pages.
@@ -101,4 +102,30 @@ public interface PageStore {
      * @return Page store version.
      */
     public int version();
+
+    /**
+     * @param cleanFile {@code True} to delete file.
+     * @throws StorageException If failed.
+     */
+    public void stop(boolean cleanFile) throws StorageException;
+
+    /**
+     * Starts recover process.
+     */
+    public void beginRecover();
+
+    /**
+     * Ends recover process.
+     *
+     * @throws StorageException If failed.
+     */
+    public void finishRecover() throws StorageException;
+
+    /**
+     * Truncates and deletes partition file.
+     *
+     * @param tag New partition tag.
+     * @throws StorageException If failed.
+     */
+    public void truncate(int tag) throws StorageException;
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/EncryptedRecord.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/EncryptedRecord.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/EncryptedRecord.java
new file mode 100644
index 0000000..234292b
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/EncryptedRecord.java
@@ -0,0 +1,60 @@
+/*
+ * 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.ignite.internal.pagemem.wal.record;
+
+/**
+ * Encrypted record from WAL.
+ * That types of record returned from a {@code RecordDataSerializer} on 
offline WAL iteration.
+ */
+public class EncryptedRecord extends WALRecord implements 
WalRecordCacheGroupAware {
+    /**
+     * Group id.
+     */
+    private int grpId;
+
+    /**
+     * Type of plain record.
+     */
+    private RecordType plainRecType;
+
+    /**
+     * @param grpId Group id
+     * @param plainRecType Plain record type.
+     */
+    public EncryptedRecord(int grpId, RecordType plainRecType) {
+        this.grpId = grpId;
+        this.plainRecType = plainRecType;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RecordType type() {
+        return RecordType.ENCRYPTED_RECORD;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int groupId() {
+        return grpId;
+    }
+
+    /**
+     * @return Type of plain record.
+     */
+    public RecordType plainRecordType() {
+        return plainRecType;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/PageSnapshot.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/PageSnapshot.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/PageSnapshot.java
index 1aa065e..d3a465d 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/PageSnapshot.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/PageSnapshot.java
@@ -38,21 +38,30 @@ public class PageSnapshot extends WALRecord implements 
WalRecordCacheGroupAware{
     private FullPageId fullPageId;
 
     /**
+     * PageSIze without encryption overhead.
+     */
+    private int realPageSize;
+
+    /**
      * @param fullId Full page ID.
      * @param arr Read array.
+     * @param realPageSize Page size without encryption overhead.
      */
-    public PageSnapshot(FullPageId fullId, byte[] arr) {
-        fullPageId = fullId;
-        pageData = arr;
+    public PageSnapshot(FullPageId fullId, byte[] arr, int realPageSize) {
+        this.fullPageId = fullId;
+        this.pageData = arr;
+        this.realPageSize = realPageSize;
     }
 
     /**
      * @param fullPageId Full page ID.
      * @param ptr Pointer to copy from.
      * @param pageSize Page size.
+     * @param realPageSize Page size without encryption overhead.
      */
-    public PageSnapshot(FullPageId fullPageId, long ptr, int pageSize) {
+    public PageSnapshot(FullPageId fullPageId, long ptr, int pageSize, int 
realPageSize) {
         this.fullPageId = fullPageId;
+        this.realPageSize = realPageSize;
 
         pageData = new byte[pageSize];
 
@@ -88,7 +97,7 @@ public class PageSnapshot extends WALRecord implements 
WalRecordCacheGroupAware{
 
         try {
             return "PageSnapshot [fullPageId = " + fullPageId() + ", page = 
[\n"
-                + PageIO.printPage(addr, pageData.length)
+                + PageIO.printPage(addr, realPageSize)
                 + "],\nsuper = ["
                 + super.toString() + "]]";
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java
index a555aae..667f8d9 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java
@@ -193,7 +193,13 @@ public abstract class WALRecord {
         MVCC_DATA_PAGE_TX_STATE_HINT_UPDATED_RECORD,
 
         /** */
-        MVCC_DATA_PAGE_NEW_TX_STATE_HINT_UPDATED_RECORD;
+        MVCC_DATA_PAGE_NEW_TX_STATE_HINT_UPDATED_RECORD,
+
+        /** Encrypted WAL-record. */
+        ENCRYPTED_RECORD,
+
+        /** Ecnrypted data record */
+        ENCRYPTED_DATA_RECORD;
 
         /** */
         private static final RecordType[] VALS = RecordType.values();

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageInsertFragmentRecord.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageInsertFragmentRecord.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageInsertFragmentRecord.java
index 2b02bb57..650ae1e 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageInsertFragmentRecord.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageInsertFragmentRecord.java
@@ -57,7 +57,7 @@ public class DataPageInsertFragmentRecord extends 
PageDeltaRecord {
     @Override public void applyDelta(PageMemory pageMem, long pageAddr) throws 
IgniteCheckedException {
         AbstractDataPageIO io = PageIO.getPageIO(pageAddr);
 
-        io.addRowFragment(PageIO.getPageId(pageAddr), pageAddr, payload, 
lastLink, pageMem.pageSize());
+        io.addRowFragment(PageIO.getPageId(pageAddr), pageAddr, payload, 
lastLink, pageMem.realPageSize(groupId()));
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageInsertRecord.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageInsertRecord.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageInsertRecord.java
index 2c9a8e7..9b0637d 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageInsertRecord.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageInsertRecord.java
@@ -58,7 +58,7 @@ public class DataPageInsertRecord extends PageDeltaRecord {
 
         AbstractDataPageIO io = PageIO.getPageIO(pageAddr);
 
-        io.addRow(pageAddr, payload, pageMem.pageSize());
+        io.addRow(pageAddr, payload, pageMem.realPageSize(groupId()));
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccMarkUpdatedRecord.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccMarkUpdatedRecord.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccMarkUpdatedRecord.java
index 5e89f8e..907f4c0 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccMarkUpdatedRecord.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccMarkUpdatedRecord.java
@@ -60,7 +60,7 @@ public class DataPageMvccMarkUpdatedRecord extends 
PageDeltaRecord {
     @Override public void applyDelta(PageMemory pageMem, long pageAddr) throws 
IgniteCheckedException {
         DataPageIO io = PageIO.getPageIO(pageAddr);
 
-        io.updateNewVersion(pageAddr, itemId, pageMem.pageSize(), newMvccCrd, 
newMvccCntr, newMvccOpCntr);
+        io.updateNewVersion(pageAddr, itemId, pageMem.realPageSize(groupId()), 
newMvccCrd, newMvccCntr, newMvccOpCntr);
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/aabacfa0/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccUpdateNewTxStateHintRecord.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccUpdateNewTxStateHintRecord.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccUpdateNewTxStateHintRecord.java
index 4a244a1..f3d235d 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccUpdateNewTxStateHintRecord.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccUpdateNewTxStateHintRecord.java
@@ -50,7 +50,7 @@ public class DataPageMvccUpdateNewTxStateHintRecord extends 
PageDeltaRecord {
     @Override public void applyDelta(PageMemory pageMem, long pageAddr) throws 
IgniteCheckedException {
         DataPageIO io = PageIO.getPageIO(pageAddr);
 
-        io.updateNewTxState(pageAddr, itemId, pageMem.pageSize(), txState);
+        io.updateNewTxState(pageAddr, itemId, pageMem.realPageSize(groupId()), 
txState);
     }
 
     /** {@inheritDoc} */

Reply via email to