001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.pageStore.crypt;
018
019import java.security.AlgorithmParameters;
020import java.security.GeneralSecurityException;
021import java.security.SecureRandom;
022import java.util.Arrays;
023
024import javax.crypto.Cipher;
025import javax.crypto.KeyGenerator;
026import javax.crypto.SecretKey;
027
028import org.apache.wicket.WicketRuntimeException;
029import org.bouncycastle.jcajce.spec.AEADParameterSpec;
030
031/**
032 * Encryption and decryption implementation using AES-256-GCM-SIV authenticated encryption.
033 * 
034 * This implementation requires Bouncy Castle. It is more secure than the {@link DefaultCrypter},
035 * but also more expensive. Simple measurements have shown {@link DefaultCrypter} to be about 10 to
036 * 15 times faster than this implementation. This is likely caused by not-so-optimal implementation
037 * of the algorithm in Java by BC. When the JDK gets support for GCM-SIV
038 * (https://bugs.openjdk.org/browse/JDK-8256530), this implementation will likely be faster than or
039 * about as fast as CBC.
040 */
041public class GCMSIVCrypter implements ICrypter
042{
043        protected Cipher getCipher() throws GeneralSecurityException
044        {
045                return Cipher.getInstance("AES/GCM-SIV/NoPadding");
046        }
047
048        @Override
049        public SecretKey generateKey(SecureRandom random)
050        {
051                try
052                {
053                        KeyGenerator generator = KeyGenerator.getInstance("AES");
054                        generator.init(256, random);
055                        return generator.generateKey();
056                }
057                catch (GeneralSecurityException ex)
058                {
059                        throw new WicketRuntimeException(ex);
060                }
061        }
062
063        @Override
064        public byte[] encrypt(byte[] decrypted, SecretKey key, SecureRandom random)
065        {
066                try
067                {
068                        Cipher cipher = getCipher();
069                        cipher.init(Cipher.ENCRYPT_MODE, key, random);
070
071                        AlgorithmParameters params = cipher.getParameters();
072                        byte[] nonce = params.getParameterSpec(AEADParameterSpec.class).getNonce();
073                        byte[] ciphertext = cipher.doFinal(decrypted);
074
075                        byte[] encrypted = Arrays.copyOf(nonce, nonce.length + ciphertext.length);
076                        System.arraycopy(ciphertext, 0, encrypted, nonce.length, ciphertext.length);
077
078                        return encrypted;
079                }
080                catch (GeneralSecurityException ex)
081                {
082                        throw new WicketRuntimeException(ex);
083                }
084        }
085
086        @Override
087        public byte[] decrypt(byte[] encrypted, SecretKey key)
088        {
089                try
090                {
091                        byte[] nonce = new byte[12];
092                        byte[] ciphertext = new byte[encrypted.length - nonce.length];
093                        System.arraycopy(encrypted, 0, nonce, 0, nonce.length);
094                        System.arraycopy(encrypted, nonce.length, ciphertext, 0, ciphertext.length);
095
096                        Cipher cipher = getCipher();
097                        cipher.init(Cipher.DECRYPT_MODE, key, new AEADParameterSpec(nonce, 128));
098                        byte[] decrypted = cipher.doFinal(ciphertext);
099
100                        return decrypted;
101                }
102                catch (GeneralSecurityException ex)
103                {
104                        throw new WicketRuntimeException(ex);
105                }
106        }
107}