001/**
002 *
003 * Copyright 2017 Paul Schaub, 2019-2023 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * 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.jivesoftware.smackx.omemo.internal;
018
019import java.nio.charset.StandardCharsets;
020import java.security.InvalidAlgorithmParameterException;
021import java.security.InvalidKeyException;
022import java.security.NoSuchAlgorithmException;
023
024import javax.crypto.BadPaddingException;
025import javax.crypto.Cipher;
026import javax.crypto.IllegalBlockSizeException;
027import javax.crypto.NoSuchPaddingException;
028import javax.crypto.SecretKey;
029import javax.crypto.spec.IvParameterSpec;
030import javax.crypto.spec.SecretKeySpec;
031
032import org.jivesoftware.smack.util.RandomUtil;
033
034import org.jivesoftware.smackx.omemo.util.OmemoConstants;
035import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder;
036
037public class OmemoAesCipher {
038
039    static {
040        byte[] iv = OmemoMessageBuilder.generateIv();
041        byte[] key = new byte[16];
042        RandomUtil.fillWithSecureRandom(key);
043
044        try {
045            encryptAesGcmNoPadding("This is just a test", key, iv);
046        } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
047                        | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
048            String message = "Unable to perform " + OmemoConstants.Crypto.CIPHERMODE
049                            + " operation requires by OMEMO. Ensure that a suitable crypto provider for is available."
050                            + " For example Bouncycastle on Android (BouncyCastleProvider)";
051            throw new AssertionError(message, e);
052        }
053    }
054
055    private enum CipherOpmode {
056        encrypt(Cipher.ENCRYPT_MODE),
057        decrypt(Cipher.DECRYPT_MODE),
058        ;
059
060        public final int opmodeInt;
061
062        CipherOpmode(int opmodeInt) {
063            this.opmodeInt = opmodeInt;
064        }
065    }
066
067    private static byte[] performCipherOperation(CipherOpmode opmode, byte[] input, byte[] key,
068                    byte[] initializationVector)
069                    throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
070                    NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
071        SecretKey secretKey = new SecretKeySpec(key, OmemoConstants.Crypto.KEYTYPE);
072        IvParameterSpec ivSpec = new IvParameterSpec(initializationVector);
073
074        Cipher cipher = Cipher.getInstance(OmemoConstants.Crypto.CIPHERMODE);
075        cipher.init(opmode.opmodeInt, secretKey, ivSpec);
076
077        byte[] ciphertext = cipher.doFinal(input);
078
079        return ciphertext;
080    }
081
082    public static byte[] decryptAesGcmNoPadding(byte[] ciphertext, byte[] key, byte[] initializationVector)
083                    throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
084                    NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException {
085        return performCipherOperation(CipherOpmode.decrypt, ciphertext, key, initializationVector);
086    }
087
088    public static byte[] encryptAesGcmNoPadding(byte[] plaintext, byte[] key, byte[] initializationVector)
089                    throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
090                    InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
091        return performCipherOperation(CipherOpmode.encrypt, plaintext, key, initializationVector);
092    }
093
094    public static byte[] encryptAesGcmNoPadding(String plaintext, byte[] key, byte[] initializationVector)
095                    throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
096                    InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
097        byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
098        return encryptAesGcmNoPadding(plaintextBytes, key, initializationVector);
099    }
100}