Неправильные начальные байты после расшифровки Java AES / CBC

116

Что не так в следующем примере?

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

Result: eB6OgeS��i are you? Have a nice day.
@Test
public void testEncrypt() {
  try {
    String s = "Hello there. How are you? Have a nice day.";

    // Generate key
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    kgen.init(128);
    SecretKey aesKey = kgen.generateKey();

    // Encrypt cipher
    Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

    // Encrypt
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
    cipherOutputStream.write(s.getBytes());
    cipherOutputStream.flush();
    cipherOutputStream.close();
    byte[] encryptedBytes = outputStream.toByteArray();

    // Decrypt cipher
    Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
    decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

    // Decrypt
    outputStream = new ByteArrayOutputStream();
    ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
    CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
    byte[] buf = new byte[1024];
    int bytesRead;
    while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
        outputStream.write(buf, 0, bytesRead);
    }

    System.out.println("Result: " + new String(outputStream.toByteArray()));

  } 
  catch (Exception ex) {
    ex.printStackTrace();
  }
}
TedTrippin
источник
48
НЕ ИСПОЛЬЗУЙТЕ ОТВЕТЫ НА ДАННЫЙ ВОПРОС В СЕРЬЕЗНОМ ПРОЕКТЕ! Все примеры, представленные в этом вопросе, уязвимы для оракула заполнения и в целом очень плохо используют криптографию. Вы внесете в свой проект серьезную криптографическую уязвимость, используя любой из приведенных ниже фрагментов.
HoLyVieR 09
16
@HoLyVieR, Относительно следующих цитат: «Вы не должны разрабатывать свою собственную криптографическую библиотеку» и «используйте высокоуровневый API, который предоставляет ваша структура». Здесь никто не разрабатывает собственную библиотеку криптографии. Мы просто используем уже существующий высокоуровневый API, предоставляемый java framework. Вы, сэр, очень неточны.
k170 02
10
@MaartenBodewes, то, что вы оба согласны, не означает, что вы оба правы. Хорошие разработчики знают разницу между оберткой API высокого уровня и переписыванием API низкого уровня. Хорошие читатели заметят, что OP запросил «простой пример шифрования / дешифрования java AES», и это именно то, что он получил . Я также не согласен с другими ответами, поэтому я опубликовал собственный ответ. Возможно, вам стоит попробовать то же самое и просветить всех нас своим опытом.
k170 05
6
@HoLyVieR Это действительно самая абсурдная вещь, которую я когда-либо читал на SO! Кто вы такие, чтобы говорить людям, что они могут и чего не могут развивать?
TedTrippin
14
Я до сих пор не вижу примеров @HoLyVieR. Посмотрим какие-нибудь, или указатели на библиотеки? Совсем не конструктивно.
danieljimenez

Ответы:

245

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

Надеюсь, это будет полезно для всех вас: для компиляции вам понадобится дополнительная банка кодеков Apache Commons, которая доступна здесь: http://commons.apache.org/proper/commons-codec/download_codec.cgi

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted string: "
                    + Base64.encodeBase64String(encrypted));

            return Base64.encodeBase64String(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(decrypt(key, initVector,
                encrypt(key, initVector, "Hello World")));
    }
}
Чанд Приянкара
источник
47
Если вы не хотите зависеть от сторонней библиотеки кодеков Apache Commons, вы можете использовать JDK javax.xml.bind.DatatypeConverter для кодирования / декодирования Base64: System.out.println("encrypted string:" + DatatypeConverter.printBase64Binary(encrypted)); byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted));
curd0
8
Вы используете постоянный IV ?!
vianna77
36
В Java 8 уже есть инструменты Base64: java.util.Base64.getDecoder () и java.util.Base64.getEncoder ()
Христо Стоянов
11
IV не обязательно должен быть секретным, но он должен быть непредсказуемым для режима CBC (и уникальным для CTR). Его можно отправить вместе с зашифрованным текстом. Обычный способ сделать это - добавить к зашифрованному тексту префикс IV и отрезать его перед расшифровкой. Он должен быть сгенерирован черезSecureRandom
Artjom B.
6
Пароль - это не ключ. IV должен быть случайным.
Maarten Bodewes
40

Здесь решение без Apache Commons Codec«S Base64:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AdvancedEncryptionStandard
{
    private byte[] key;

    private static final String ALGORITHM = "AES";

    public AdvancedEncryptionStandard(byte[] key)
    {
        this.key = key;
    }

    /**
     * Encrypts the given plain text
     *
     * @param plainText The plain text to encrypt
     */
    public byte[] encrypt(byte[] plainText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        return cipher.doFinal(plainText);
    }

    /**
     * Decrypts the given byte array
     *
     * @param cipherText The data to decrypt
     */
    public byte[] decrypt(byte[] cipherText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        return cipher.doFinal(cipherText);
    }
}

Пример использования:

byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
        encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);

System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));

Печать:

Hello world!
դ;��LA+�ߙb*
Hello world!
BullyWiiPlaza
источник
5
Это совершенно функциональный пример, как и у @ chandpriyankara. Но зачем определять подпись encrypt(String)а нет encrypt(byte[] )? Шифрование (в том числе и дешифрование) - это процесс, основанный на байтах (в любом случае AES). Шифрование принимает байты на входе и выводит байты, так же как и дешифрование (пример: Cipherобъект делает). Теперь одним из конкретных вариантов использования может быть зашифрованные байты, поступающие из String или отправляемые как String (вложение MIME base64 для Mail ...), но это проблема кодирования байтов, для которых существуют сотни решения, совершенно не связанные с AES / шифрованием.
GPI
3
@GPI: Да, но я нахожу это более полезным, Stringsпоскольку в основном я работаю с ним 95% времени, и вы все равно конвертируете.
BullyWiiPlaza
9
Нет, это не эквивалент кода чандприянкары! В вашем коде используется ECB, который обычно небезопасен и нежелателен. Следует явно указать CBC. Когда указан CBC, ваш код сломается.
Дэн
Совершенно функциональный, совершенно небезопасный и использующий очень плохие методы программирования. класс назван плохо. Размер ключа заранее не проверяется. Но самое главное, код использует небезопасный режим ECB, скрывая проблему в исходном вопросе . Наконец, в нем не указана кодировка символов, что означает, что декодирование текста может завершиться ошибкой на других платформах.
Maarten Bodewes
24

Мне кажется, что вы неправильно работаете со своим вектором инициализации (IV). Прошло много времени с тех пор, как я в последний раз читал об AES, IV и цепочке блоков, но ваша строка

IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());

вроде не в порядке. В случае AES вы можете думать о векторе инициализации как о «начальном состоянии» экземпляра шифра, и это состояние представляет собой часть информации, которую вы не можете получить от своего ключа, а от фактического вычисления шифра шифрования. (Можно было бы возразить, что, если бы IV можно было извлечь из ключа, это было бы бесполезно, поскольку ключ уже передан экземпляру шифра во время его фазы инициализации).

Следовательно, вы должны получить IV в виде байта [] из экземпляра шифра в конце вашего шифрования.

  cipherOutputStream.close();
  byte[] iv = encryptCipher.getIV();

и вы должны инициализировать свой Cipherin DECRYPT_MODEэтим байтом []:

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Тогда ваша расшифровка должна быть в порядке. Надеюсь это поможет.

GPI
источник
Спасибо за помощь новичку. Я взял этот пример из других сообщений. Я не думаю, что вы знаете, как избежать необходимости в капельнице? Я видел, но не пробовал, другие примеры AES, которые его не используют.
TedTrippin
Не обращайте на это внимания, я нашел ответ! Мне нужно использовать AES / ECB / PKCS5Padding.
TedTrippin
20
В большинстве случаев вы не хотите использовать ECB. Просто погуглите почему.
Жуан Фернандес,
2
@Mushy: согласился с тем, что выбор и явная установка IV из надежного случайного источника лучше, чем просто позволить экземпляру Cihper забрать его. С другой стороны, этот ответ касается исходной проблемы путаницы вектора инициализации для ключа. Вот почему сначала за него проголосовали. Теперь этот пост стал скорее примером кода, и люди здесь сделали отличный пример - как раз помимо того, о чем был исходный вопрос.
GPI
3
@GPI проголосовало за. Другие «замечательные примеры» не так уж хороши и вообще не решают этот вопрос. Вместо этого, похоже, это было местом для новичков, которые слепо копировали образцы криптографии, не понимая, что могут быть возможные проблемы с безопасностью - и, как всегда, они есть.
Maarten Bodewes
17

IV, который вы используете для дешифрования, неверен. Замените этот код

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

С этим кодом

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

И это должно решить вашу проблему.


Ниже приведен пример простого класса AES в Java. Я не рекомендую использовать этот класс в производственной среде, поскольку он может не учитывать все специфические потребности вашего приложения.

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AES 
{
    public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        byte[] transformedBytes = null;

        try
        {
            final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

            cipher.init(mode, keySpec, ivSpec);

            transformedBytes = cipher.doFinal(messageBytes);
        }        
        catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) 
        {
            e.printStackTrace();
        }
        return transformedBytes;
    }

    public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        //Retrieved from a protected local file.
        //Do not hard-code and do not version control.
        final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";

        //Retrieved from a protected database.
        //Do not hard-code and do not version control.
        final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";

        //Extract the iv and the ciphertext from the shadow entry.
        final String[] shadowData = shadowEntry.split(":");        
        final String base64Iv = shadowData[0];
        final String base64Ciphertext = shadowData[1];

        //Convert to raw bytes.
        final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
        final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
        final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);

        //Decrypt data and do something with it.
        final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);

        //Use non-blocking SecureRandom implementation for the new IV.
        final SecureRandom secureRandom = new SecureRandom();

        //Generate a new IV.
        secureRandom.nextBytes(ivBytes);

        //At this point instead of printing to the screen, 
        //one should replace the old shadow entry with the new one.
        System.out.println("Old Shadow Entry      = " + shadowEntry);
        System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
        System.out.println("New Shadow Entry      = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
    }
}

Обратите внимание, что AES не имеет ничего общего с кодированием, поэтому я решил обрабатывать его отдельно и без каких-либо сторонних библиотек.

K170
источник
Во-первых, вы не ответили на исходный вопрос. Во-вторых, почему вы отвечаете на уже отвеченный, хорошо принятый вопрос? Я думал, что защита должна остановить этот спам.
TedTrippin
14
Как и в принятом ответе, я решил ответить на ваш вопрос с помощью примера. Я предоставил полностью функциональный фрагмент кода, который, помимо прочего, показывает вам, как правильно работать с вектором инициализации. Что касается вашего второго вопроса, я почувствовал, что нужен обновленный ответ, поскольку кодек Apache больше не нужен. Так что нет, это не спам. Прекратите трогать.
k170
7
IV имеет определенную цель , которая должна рандомизации шифротекста и обеспечения семантической безопасности. Если вы используете ту же пару ключ + IV, злоумышленники могут определить, отправили ли вы сообщение с тем же префиксом, что и раньше. IV не обязательно должен быть секретным, но он должен быть непредсказуемым. Обычный способ - просто добавить к зашифрованному тексту префикс IV и отрезать его перед расшифровкой.
Artjom B.
4
голос против: жестко запрограммированный IV, см. комментарий Artjom B. выше, почему это плохо
Мурмель
1
Режим CTR должен быть в паре с NoPadding. Режим CTR , конечно , не требуется вместо CBC (если не применяется обивка оракулы), но если CTR будет использоваться, а затем использовать "/NoPadding". CTR - это режим, который превращает AES в потоковый шифр, а потоковый шифр работает с байтами, а не с блоками.
Maarten Bodewes
16

В этом ответе я предпочитаю подходить к основной теме «Простой пример шифрования / дешифрования Java AES», а не к конкретному вопросу отладки, потому что я думаю, что это принесет пользу большинству читателей.

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

В этом примере я выберу использовать аутентификацию шифрование с Галуа Mode / счетчика или GCM режимом. Причина в том, что в большинстве случаев вам нужна целостность и аутентичность в сочетании с конфиденциальностью (подробнее читайте в блоге ).

Руководство по шифрованию / дешифрованию AES-GCM

Вот шаги, необходимые для шифрования / дешифрования с помощью AES-GCM с архитектурой криптографии Java (JCA) . Не смешивайте с другими примерами , так как тонкие различия могут сделать ваш код совершенно небезопасным.

1. Создать ключ

Поскольку это зависит от вашего варианта использования, я предполагаю самый простой случай: случайный секретный ключ.

SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");

Важный:

2. Создайте вектор инициализации.

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

byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);

Важный:

3. Зашифровать с помощью IV и ключа

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);

Важный:

  • использовать тег аутентификации 16 байт / 128 бит (используется для проверки целостности / аутентичности)
  • тег аутентификации будет автоматически добавлен к зашифрованному тексту (в реализации JCA)
  • Поскольку GCM ведет себя как потоковый шифр, заполнение не требуется
  • использовать CipherInputStreamпри шифровании больших блоков данных
  • хотите проверить дополнительные (несекретные) данные, если они были изменены? Вы можете использовать соответствующие данные с cipher.updateAAD(associatedData); Подробнее здесь.

3. Сериализовать в одно сообщение

Просто добавьте IV и зашифрованный текст. Как было сказано выше, IV не обязательно должен быть секретным.

ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();

При необходимости закодируйте с помощью Base64, если вам нужно строковое представление. Используйте встроенную реализацию Android или Java 8 (не используйте кодек Apache Commons - это ужасная реализация). Кодирование используется для «преобразования» байтовых массивов в строковое представление, чтобы сделать его безопасным ASCII, например:

String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);

4. Подготовка к расшифровке: десериализация

Если вы закодировали сообщение, сначала декодируйте его в массив байтов:

byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)

Важный:

5. Расшифровать

Инициализируйте шифр и установите те же параметры, что и при шифровании:

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);

Важный:

В этом суть можно найти рабочий фрагмент кода.


Обратите внимание, что самые последние реализации Android (SDK 21+) и Java (7+) должны иметь AES-GCM. В более старых версиях он может отсутствовать. Я по-прежнему выбираю этот режим, поскольку его проще реализовать, и он более эффективен по сравнению с аналогичным режимом Encrypt-then-Mac (например, с AES-CBC + HMAC ). См. Эту статью о том, как реализовать AES-CBC с HMAC .

Патрик Фавр
источник
Проблема в том, что запрос примеров явно не по теме SO. И более серьезная проблема заключается в том, что это непроверенные фрагменты кода, которые трудно проверить. Я ценю усилия, но не думаю, что SO должно быть подходящим местом для этого.
Maarten Bodewes
1
Я восхищаюсь этими усилиями, поэтому я просто укажу на одну ошибку: «iv должен быть непредсказуемым в сочетании с уникальностью (т.е. использовать случайный iv)» - это верно для режима CBC, но не для GCM.
Maarten Bodewes
this is true for CBC mode but not for GCMВы имеете в виду всю часть или только это не обязательно должно быть непредсказуемым?
Патрик Фавр
1
«Если вы не понимаете тему, то вам, вероятно, не следует использовать примитивы низкого уровня в первую очередь», конечно, это ДОЛЖНО быть так, многие разработчики все еще делают это. Я не уверен, что отказ от размещения высококачественного контента о безопасности / криптографии в местах, где часто не так много, является правильным решением для этого. - спасибо за указание на мою ошибку, кстати
Патрик Фавр
1
Хорошо, просто потому, что мне нравится ответ по содержанию (а не цели): обработка IV может быть упрощена, особенно во время дешифрования: в конце концов, Java упрощает создание IV непосредственно из существующего массива байтов. То же самое и с расшифровкой, которая не должна начинаться со смещения 0. Все это копирование просто не нужно. Кроме того, если вам нужно отправить длину для IV (не так ли?), То почему бы не использовать один (без знака) байт - вы не собираетесь проходить мимо 255 байтов для IV, верно?
Maarten Bodewes
2

Онлайн-редактор Рабочая версия: -

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));

            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());

            //System.out.println("encrypted string: "
              //      + Base64.encodeBase64String(encrypted));

            //return Base64.encodeBase64String(encrypted);
            String s = new String(Base64.getEncoder().encode(encrypted));
            return s;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(encrypt(key, initVector, "Hello World"));
        System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
    }
}
Брюки Bhupesh
источник
Здорово, что помогло!
Bhupesh Pant
Пароль - это не ключ, IV не должен быть статичным. Все еще строго набранный код, что делает невозможным уничтожение ключа. Нет указаний на то, что делать с внутривенным вливанием, или никаких предположений о том, что это должно быть непредсказуемо.
Maarten Bodewes
1

Часто рекомендуется полагаться на решение, предоставляемое стандартной библиотекой:

private static void stackOverflow15554296()
    throws
        NoSuchAlgorithmException, NoSuchPaddingException,        
        InvalidKeyException, IllegalBlockSizeException,
        BadPaddingException
{

    // prepare key
    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    SecretKey aesKey = keygen.generateKey();
    String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
            aesKey.getEncoded()
    );

    // cipher engine
    Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

    // cipher input
    aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
    byte[] clearTextBuff = "Text to encode".getBytes();
    byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);

    // recreate key
    byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
    SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");

    // decipher input
    aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
    byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
    System.out.println(new String(decipheredBuff));
}

Это напечатает «Текст для кодирования».

Решение основано на Справочном руководстве по архитектуре криптографии Java и ответе https://stackoverflow.com/a/20591539/146745 .

андрей
источник
5
Никогда не используйте режим ECB. Период.
Константино Спаракис
1
ECB не следует использовать при шифровании более одного блока данных одним и тем же ключом, поэтому для «Текст для кодирования» этого достаточно. stackoverflow.com/a/1220869/146745
андрей
Ключ @AndroidDev создается в разделе подготовки ключа: aesKey = keygen.generateKey ()
andrej
1

Это улучшение по сравнению с принятым ответом.

Изменения:

(1) Использование случайного IV и добавление его к зашифрованному тексту

(2) Использование SHA-256 для генерации ключа из парольной фразы

(3) Нет зависимости от Apache Commons

public static void main(String[] args) throws GeneralSecurityException {
    String plaintext = "Hello world";
    String passphrase = "My passphrase";
    String encrypted = encrypt(passphrase, plaintext);
    String decrypted = decrypt(passphrase, encrypted);
    System.out.println(encrypted);
    System.out.println(decrypted);
}

private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}

private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
    return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}

public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
    byte[] initVector = new byte[16];
    SecureRandom.getInstanceStrong().nextBytes(initVector);
    Cipher cipher = getCipher();
    cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] encrypted = cipher.doFinal(value.getBytes());
    return DatatypeConverter.printBase64Binary(initVector) +
            DatatypeConverter.printBase64Binary(encrypted);
}

public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
    byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
    Cipher cipher = getCipher();
    cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
    return new String(original);
}
wvdz
источник
Хэш по-прежнему не является функцией генерации ключей на основе пароля / PBKDF. Либо вы используете случайный ключ, либо используете PBKDF, например PBKDF2 / шифрование на основе пароля.
Maarten Bodewes
@MaartenBodewes Вы можете предложить улучшение?
wvdz
PBKDF2 присутствует в Java, поэтому, думаю, я только что предложил его. Хорошо, я не кодировал один, но, на мой взгляд, это требует слишком многого. Существует множество примеров шифрования на основе пароля.
Maarten Bodewes
@MaartenBodewes Я подумал, что это простое решение. Из любопытства, каковы будут конкретные уязвимости при использовании этого кода как есть?
wvdz
0

Другое решение с использованием java.util.Base64 с Spring Boot

Класс шифратора

package com.jmendoza.springboot.crypto.cipher;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

@Component
public class Encryptor {

    @Value("${security.encryptor.key}")
    private byte[] key;
    @Value("${security.encryptor.algorithm}")
    private String algorithm;

    public String encrypt(String plainText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
    }

    public String decrypt(String cipherText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
    }
}

Класс EncryptorController

package com.jmendoza.springboot.crypto.controller;

import com.jmendoza.springboot.crypto.cipher.Encryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cipher")
public class EncryptorController {

    @Autowired
    Encryptor encryptor;

    @GetMapping(value = "encrypt/{value}")
    public String encrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.encrypt(value);
    }

    @GetMapping(value = "decrypt/{value}")
    public String decrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.decrypt(value);
    }
}

application.properties

server.port=8082
security.encryptor.algorithm=AES
security.encryptor.key=M8jFt46dfJMaiJA0

пример

HTTP: // локальный: 8082 / шифр / шифровать / jmendoza

2h41HH8Shzc4BRU3hVDOXA ==

HTTP: // локальный: 8082 / шифр / расшифровывать / 2h41HH8Shzc4BRU3hVDOXA ==

jmendoza

Джонатан Мендоса
источник
-1

Оптимизированная версия принятого ответа.

  • нет сторонних библиотек

  • включает IV в зашифрованное сообщение (может быть публичным)

  • пароль может быть любой длины

Код:

import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Encryptor {
    public static byte[] getRandomInitialVector() {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
            byte[] initVector = new byte[cipher.getBlockSize()];
            randomSecureRandom.nextBytes(initVector);
            return initVector;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static byte[] passwordTo16BitKey(String password) {
        try {
            byte[] srcBytes = password.getBytes("UTF-8");
            byte[] dstBytes = new byte[16];

            if (srcBytes.length == 16) {
                return srcBytes;
            }

            if (srcBytes.length < 16) {
                for (int i = 0; i < dstBytes.length; i++) {
                    dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
                }
            } else if (srcBytes.length > 16) {
                for (int i = 0; i < srcBytes.length; i++) {
                    dstBytes[i % dstBytes.length] += srcBytes[i];
                }
            }

            return dstBytes;
        } catch (UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String encrypt(String key, String value) {
        return encrypt(passwordTo16BitKey(key), value);
    }

    public static String encrypt(byte[] key, String value) {
        try {
            byte[] initVector = Encryptor.getRandomInitialVector();
            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String encrypted) {
        return decrypt(passwordTo16BitKey(key), encrypted);
    }

    public static String decrypt(byte[] key, String encrypted) {
        try {
            String[] encryptedParts = encrypted.split(" ");
            byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
            if (initVector.length != 16) {
                return null;
            }

            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedParts[0]));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }
}

Использование:

String key = "Password of any length.";
String encrypted = Encryptor.encrypt(key, "Hello World");
String decrypted = Encryptor.decrypt(key, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);

Пример вывода:

QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World
ue7m
источник
Ваша функция получения пароля небезопасна. Я бы не ожидал от e.printStackTrace()так называемого оптимизированного кода.
Маартен Бодевес,