Шифрование данных с помощью алгоритма AES-256 на Java

В данной статье говорится о том, как осуществить шифрование с помощью алгоритма AES-256 на Java.

Как уже говорилось ранее, AES-256 представляет собой алгоритм шифрования с симметричным ключом. Это означает, что для шифрования и дешифрования используется один и тот же криптографический ключ.

Алгоритм используется в военных целях, используется для шифрования передачи сообщений по открытым каналам, для шифрования файлов. Так же с помощью AES-256 злоумышленники шифруют файлы на компьютерах «жертв», используя его в целях вымогательства денег (программы-вымогатели). Расшифровать данные возможно только лишь зная ключ.

Как зашифровать и дешифровать с помощью AES-256 средствами Java

Класс для шифрования и дешифрования потока байт на Java может выглядеть следующим образом:

package Crypto;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.NoSuchAlgorithmException;

/**
 * Created by administrator on 16.03.2018.
 */
public class Aes256Class {

    private SecretKey secretKey;

    public Aes256Class() {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(256);

            this.secretKey = keyGenerator.generateKey();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    public byte[] makeAes(byte[] rawMessage, int cipherMode){
        try {
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(cipherMode, this.secretKey);
            byte [] output = cipher.doFinal(rawMessage);
            return output;

        } catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
}

В конструкторе Aes256Class() инициализируется объект keyGenerator, которому задается размер ключа 256 бит. Далее с помощью функции generateKey() генерируется случайный ключ с заданным размером (256 бит).

В функцию makeAes передается исходный массив байт и режим работы объекта класса Cipher:

Cipher.ENCRYPT_MODE либо Cipher.DECRYPT_MODE.

В классе с функцией main можем вызвать описанные выше методы следующим образом:

package Main;

import Crypto.Aes256Class;

import javax.crypto.Cipher;

/**
 * Created by administrator on 16.03.2018.
 */
public class Main {
    public static void main(String[] args) {
        Aes256Class aes256 = new Aes256Class();

        String mes = "Hello";

        for (int i = 0; i < 3; i++) {
            byte[] shifr = aes256.makeAes(mes.getBytes(), Cipher.ENCRYPT_MODE);
            System.out.println(new String(shifr));
            byte[] src = aes256.makeAes(shifr, Cipher.DECRYPT_MODE);
            System.out.println(new String(src));
        }
    }
}

Получаем следующий результат:

Java AES

Объясняю, зачем я зациклил выполнение функции makeAes.

Благодаря шифрованию и дешифрованию одного и того же текста несколько раз, можно увидеть, что шифр получается один и тот же, что не есть хорошо.

Это уже может дать злоумышленнику какую-то информацию. Представьте себе, что ваша система отправляет в канал сообщение, например, «Опасность». И отправляет его 10 раз подряд.

Если каким-то образом сеть будет прослушиваться, перехваченное сообщение будет иметь 10 раз подряд один и тот же шифр.

Для того, чтобы одно и то же сообщение имело разный шифр, можно использовать соль.

Не смотря на то, что AES-256 не поддерживает концепцию соли, ничего не мешает сгенерировать случайный массив байт из, например, 8 элементов (получим 8 байтовую соль) и добавить к сообщению.

При дешифрации сообщения можно просто выбросить эти самые последние 8 байт.

Функция приобретет следующий вид:

public byte[] makeAes(byte[] rawMessage, int cipherMode){
    try {
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(cipherMode, this.secretKey);
        byte [] output = cipher.doFinal(rawMessage);
        if(cipherMode == Cipher.DECRYPT_MODE){
            byte[]result = new byte[output.length-8];
            //Выбрасываем последние 8 байт
            System.arraycopy(output, 0, result, 0, output.length-8);
            return result;
        }
        return output;

    } catch (Exception e){
        e.printStackTrace();
        return null;
    }
}

Вызываем функцию makeAes в методе main:

package Main;

import Crypto.Aes256Class;

import javax.crypto.Cipher;
import java.security.SecureRandom;

/**
 * Created by administrator on 16.03.2018.
 */
public class Main {
    public static void main(String[] args)  {
        Aes256Class aes256 = new Aes256Class();

        //Массив для соли
        byte[] salt = new byte[8];
        //Исходное сообщение
        String mes = "Опасность";

        //Выполним операцию 10 раз
        for (int i = 0; i < 10; i++) {
            //Генерация соли
            SecureRandom random = new SecureRandom();
            random.nextBytes(salt);

            //Преобразуем исходный текст в поток байт и добавим полученную соль
            byte[]srcMessage = mes.getBytes();
            byte[]fullsrcMessage = new byte[srcMessage.length+8];
            System.arraycopy(srcMessage, 0, fullsrcMessage, 0, srcMessage.length);
            System.arraycopy(salt, 0, fullsrcMessage, srcMessage.length, salt.length);

            //Шифруем
            byte[] shifr = aes256.makeAes(fullsrcMessage, Cipher.ENCRYPT_MODE);
            System.out.println(new String(shifr));
            //Дешифруем
            byte[] src = aes256.makeAes(shifr, Cipher.DECRYPT_MODE);
            System.out.println(new String(src));
        }
    }
}

В этом примере шифруем и дешифруем 10 раз сообщение «Опасность». Получаем следующий результат:

Java AES with salt

Как видим, одно и то же сообщение, но шифр разный. Однако здесь есть нюанс, если обратите внимание, то увидите, что первая часть сообщения всегда одна и та же. Это и имелось ввиду, когда говорилось, что AES не поддерживает концепцию соли.

Можете попробовать прикреплять соль к началу сообщения, к середине, или использовать несколько солей. Однако Принцип Керкгоффса подразумевает, что злоумышленнику известен алгоритм шифрования и какую-то закономерность он все же найдет.

Нужна очень «хитрая» реализация алгоритма, задача которой максимально запутать злоумышленника.

Когда я писал статью о реализации данного алгоритма на C# я положился на реализацию AES от Microsoft.

Реализация AES-256 на Java с помощью фреймворка Spring Security

В данной статье приведу реализацию AES с помощью Java фреймворка Spring Security:

package Main;

import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.crypto.keygen.KeyGenerators;

/**
 * Created by administrator on 16.03.2018.
 */
public class Main {

    public static void main(String[] args)  {
        final String password = "Here is the password";
        String textToEncrypt = "Hello";

        for (int i = 0; i < 10; i++) {
            String salt = KeyGenerators.string().generateKey();
            TextEncryptor encryptor = Encryptors.text(password, salt);
            String cipherText = encryptor.encrypt(textToEncrypt);
            String decryptedText = encryptor.decrypt(cipherText);
            System.out.println("Src: " + textToEncrypt);
            System.out.println("Cipher: " + cipherText);
            System.out.println("Decrypted: " + decryptedText);
            System.out.println("__________________");
        }
    }
}

Результат работы кода:

AES Spring Security

Как видите, реализация AES-256 от Spring Security не подводит. Как знать, что это именно AES? В официальной документации есть такое упоминание:

The "standard" encryption method is 256-bit AES (Spring Crypto Module)

P.S. Расшифровать подобные сообщения относительно разумными средствами (взломать шифр), не имея ключа в настоящее время не представляется возможным.

Можете попробовать перебором паролей, если у вас в запасе есть сотни миллиардов лет.

P.P.S. Так же на сайте имеется запись о шифровании данных с помощью AES-256 на C#.

Шифрование данных с помощью алгоритма AES-256 (C#)

В текущей статье напишу два метода для шифрования потока байт с помощью алгоритма AES-256, и расшифровки обратно в массив байт.

Что такое AES-256

AES-256 представляет собой алгоритм шифрования с симметричным ключом. Это означает, что для шифрования и дешифрирования используется один и тот же криптографический ключ.

Symmetric key, симметричный ключ

Алгоритм может пригодиться где угодно — от шифрования файлов до защиты данных, передаваемых по открытым каналам.

Реализация алгоритма на C#

Для реализации алгоритма нужно подключить библиотеку System.Security.Cryptography и указать пространство имен:

using System.Security.Cryptography;

Далее напишем метод для шифрования данных:

/// <summary>
/// Шифрует исходное сообщение AES ключом (добавляет соль)
/// </summary>
/// <param name="src"></param>
/// <returns></returns>
public static byte[] ToAes256(string src)
{
  //Объявляем объект класса AES
  Aes aes = Aes.Create();
  //Генерируем соль
  aes.GenerateIV();
  //Присваиваем ключ. aeskey - переменная (массив байт), сгенерированная методом GenerateKey() класса AES
  aes.Key = aeskey;
  byte[] encrypted;
  ICryptoTransform crypt = aes.CreateEncryptor(aes.Key, aes.IV);
  using (MemoryStream ms = new MemoryStream())
  {
    using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Write))
    {
      using (StreamWriter sw = new StreamWriter(cs))
      {
        sw.Write(src);
      }
    }
    //Записываем в переменную encrypted зашиврованный поток байтов
    encrypted = ms.ToArray();
  }
  //Возвращаем поток байт + крепим соль
  return encrypted.Concat(aes.IV).ToArray();
}

Метод, для дешифрирования потока байтов:

/// <summary>
/// Расшифровывает криптованного сообщения
/// </summary>
/// <param name="shifr">Шифротекст в байтах</param>
/// <returns>Возвращает исходную строку</returns>
public static string FromAes256(byte[] shifr)
{
  byte[] bytesIv = new byte[16];
  byte[] mess = new byte[shifr.Length - 16];

  //Списываем соль
  for (int i = shifr.Length - 16, j = 0; i < shifr.Length; i++, j++)
    bytesIv[j] = shifr[i];

  //Списываем оставшуюся часть сообщения
  for (int i = 0; i < shifr.Length - 16; i++)
    mess[i] = shifr[i];

  //Объект класса Aes
  Aes aes = Aes.Create();
  //Задаем тот же ключ, что и для шифрования
  aes.Key = aeskey;
  //Задаем соль
  aes.IV = bytesIv;
  //Строковая переменная для результата
  string text = "";
  byte[] data = mess;
  ICryptoTransform crypt = aes.CreateDecryptor(aes.Key, aes.IV);

  using (MemoryStream ms = new MemoryStream(data))
  {
    using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Read))
    {
      using (StreamReader sr = new StreamReader(cs))
      {
        //Результат записываем в переменную text в вие исходной строки
        text = sr.ReadToEnd();
      }
    }
  }
  return text;
}

Зашифрованное сообщение можно передавать в открытый канал. При перехвате злоумышленники обнаружат лишь шифр, с которым мало что смогут сделать.

Стоит отметить, что сам ключ стоит передавать по закрытому каналу либо используя алгоритмы шифрования с ассиметричным ключом. Например RSA.

Ключ AES шифруется открытым ключом RSA, передается в открытый канал, далее вторая сторона расшифровывает ключ AES закрытым ключом RSA.

Во время передачи шифра в открытый канал может произойти MITM атака — попытка подмены сообщения и т.п. злоумышленником.

Для проверки подлинности сообщения используйте цифровую подпись для каждого сообщения (вычисляется хеш, проверяемый получателем для проверки подлинности сообщения).

Данным методом можно воспользоваться, чтобы зашифровать данные другими алгоритмами с симметричным ключом, например TripleDes.