Початкові байти невірні після розшифрування 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();
  }
}

48
НЕ ВИКОРИСТОВУЙТЕ БІЛЬКИЙ ВІДПОВІДЬ ЦЬОГО ЗАПИТАННЯ В СЕРІЙНИЙ ПРОЕКТ! Всі приклади, наведені в цьому запитанні, вразливі до оракула, що вкладається, і загалом дуже погано використовуються криптографії. Ви введете серйозну вразливість криптографії у своєму проекті, використовуючи будь-який з фрагментів нижче.
HoLyVieR

16
@HoLyVieR, Що стосується наступних цитат: "Ви не повинні розробляти власну бібліотеку криптовалют" та "використовувати API високого рівня, який надає ваша рамка". Тут ніхто не розробляє власну бібліотеку криптовалют. Ми просто використовуємо вже існуючий API високого рівня, який надає java Framework. Ви, пан, дико неточні.
k170

10
@MaartenBodewes, Тільки тому, що ви обоє згодні, це не означає, що ви обоє правильні. Хороші розробники знають різницю між загортанням API високого рівня та перезаписом API низького рівня. Хороші читачі помітять, що ОП попросили "простий приклад шифрування / розшифрування java AES", і саме це він отримав . Я також не згоден з іншими відповідями, саме тому я опублікував свою власну відповідь. Можливо, ви, хлопці, повинні спробувати те саме і просвітите всіх нас своїм досвідом.
k170

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 Codec, ви можете використовувати javax.xml.bind.DatatypeConverter JDK для виконання кодування / декодування 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!

5
Це ідеально функціональний приклад, як і @ chandpriyankara. Але навіщо визначати підпис encrypt(String)і ні encrypt(byte[] )? Шифрування (теж дешифрування) - процес, що базується на байтах (AES все одно). Шифрування приймає байти як вхід, а виводить байти, так само розшифровує (випадок у точці: Cipherоб’єкт робить). Тепер одним конкретним випадком використання може бути зашифрований байт, що надходить із String, або надісланий як String (базовий вкладення MIME для пошти ...), але це проблема кодування байтів, для яких існує сотні рішення, абсолютно не пов'язані з AES / шифруванням.
GPI

3
@GPI: Так, але я вважаю, що це корисніше, Stringsоскільки це в основному те, з чим я працюю в 95% часу, і ви все одно перетворюєте.
BullyWiiPlaza

9
Ні, це не рівнозначно коду chandpriyankara! У вашому коді використовується ЕЦБ, який, як правило, не є безпечним і не потрібен. Слід чітко вказати CBC. Коли вказано CBC, ваш код порушиться.
Dan

Ідеально функціональний, абсолютно незахищений та використовуючи дуже погані практики програмування. клас названо погано. Розмір ключа не перевіряється заздалегідь. Але найголовніше, що код використовує незахищений режим ЄЦБ, приховуючи проблему в оригінальному питанні . Нарешті, він не вказує кодування символів, а це означає, що розшифровка тексту в тексті може не працювати на інших платформах.
Maarten Bodewes

24

Мені здається, ви не маєте належного поводження з вашим вектором ініціалізації (IV). Минуло давно, коли я востаннє читав про AES, IV та блок-ланцюги, але ваша лінія

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

не здається нормальним. У випадку AES можна вважати вектор ініціалізації "початковим станом" шифрного екземпляра, і цей стан є трохи інформацією, яку ви можете отримати не від вашого ключа, а від фактичного обчислення шифрувального шифру. (Можна стверджувати, що якщо IV можна витягти з ключа, то він не принесе користі, оскільки ключ вже надається екземпляру шифру під час його init фази).

Тому вам слід отримати IV у вигляді байту [] від шифрного екземпляра наприкінці вашого шифрування

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

і ви повинні ініціалізувати Cipherін DECRYPT_MODEз цим байт []:

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Тоді ваше дешифрування має бути нормальним. Сподіваюся, це допомагає.


Дякуємо за допомогу новачку. Я привів цей приклад з інших постів. Я не думаю, що ви знаєте, як уникнути необхідності IV? Я бачив, але не пробував, інші приклади AES, які не використовують його.
TedTrippin

Ігноруйте це, я знайшов відповідь! Мені потрібно використовувати AES / ECB / PKCS5Padding.
TedTrippin

20
У більшості випадків ви не хочете використовувати ECB. Просто google чому.
Жоао Фернандес

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 не має нічого спільного з кодуванням, саме тому я вирішив обробляти його окремо і без необхідності будь-яких сторонніх бібліотек.


По-перше, ви не відповіли на початкове запитання. По-друге, чому ви відповідаєте на вже відповів, добре прийняте запитання? Я думав, що захист повинен був зупинити цей спам.
TedTrippin

14
Як і прийнята відповідь, я вирішив відповісти на ваше запитання на прикладі. Я надав повністю функціональний фрагмент коду, який показує, як правильно поводитися з вектором ініціалізації, серед іншого. Щодо вашого другого питання, я вважав, що потрібна оновлена ​​відповідь, оскільки кодек Apache вже не потрібен. Так ні це не спам. Зупиніть трипсин.
k170

7
IV має певну мету , яка повинна рандомізації шифротекста і забезпечення семантичної безпеки. Якщо ви використовуєте той самий ключ + пара IV, то зловмисники можуть визначити, чи ви надіслали повідомлення з тим же префіксом, що і раніше. IV не повинен бути секретним, але він повинен бути непередбачуваним. Поширений спосіб - просто приєднати IV до шифротексту та вирізати його перед розшифровкою.
Artjom B.

4
downvote: hardcoded IV, див. коментар Artjom B. вище, чому це погано
Murmel

1
Режим CTR слід з'єднати з NoPadding. Режим CTR , звичайно , не потрібно замість CBC (якщо не застосовується оббивка оракули), але якщо CTR буде використовуватися, а потім використовувати "/NoPadding". CTR - це режим, який перетворює AES у шифр потоку, а шифр потоку працює на байтах, а не на блоках.
Maarten Bodewes

16

У цій відповіді я вирішу підходити до основної теми "Простий приклад шифрування / розшифрування Java AES", а не до конкретного питання налагодження, тому що я думаю, що це принесе користь більшості читачів.

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

У цьому прикладі я виберу використовувати автентифіковане шифрування в режимі Galois / Counter або 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 та Key

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 Codec - це жахлива реалізація). Кодування використовується для "перетворення" байтових масивів у представлення рядків, щоб зробити його 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);

Важливо:

  • не забудьте додати відповідні дані з , cipher.updateAAD(associatedData);якщо ви додали його під час шифрування.

Фрагмент робочого коду можна знайти в цій суті.


Зауважте, що в останніх реалізаціях Android (SDK 21+) та Java (7+) має бути AES-GCM. Старіші версії можуть бракувати його. Я все одно вибираю цей режим, оскільки його легше реалізувати, крім того, що він є більш ефективним порівняно з аналогічним режимом Encrypt-then-Mac (наприклад, AES-CBC + HMAC ). Дивіться цю статтю про те, як реалізувати AES-CBC з HMAC .


Проблема полягає в тому, що просити приклади явно поза темою на SO. І більша проблема полягає в тому, що це непереглянуті фрагменти коду, які важко перевірити. Я ціную зусилля, але не думаю, що ТАК повинен бути місцем для цього.
Maarten Bodewes

1
Я захоплююся зусиллями, тому я просто зазначу одну помилку: "iv повинен бути непередбачуваним у поєднанні з унікальністю (тобто використовувати випадковий iv)" - це стосується режиму CBC, але не для GCM.
Maarten Bodewes

this is true for CBC mode but not for GCMти маєш на увазі цілу частину, або лише вона насправді не потребує непередбачуваності?
Патрік Фавр

1
"Якщо ви не знайдете тему, ви, мабуть, не повинні в першу чергу використовувати низькорівневі примітиви", але це повинно бути так, що багато розробників все ще роблять це. Я не впевнений, що утримуватися від розміщення високоякісного контенту щодо безпеки / криптографії в місцях, де часто не так багато, є правильним рішенням для цього. - thx за вказівку на мою помилку btw
Патрік Фавр

1
Гаразд, тільки тому, що мені подобається вміст wrt відповіді (а не мета): обробка 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 Pant

Пароль не є ключем, IV не повинен бути статичним. Ще строго набраний код, що унеможливлює руйнування ключа. Ні вказівки, що робити з 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 Cryptography та https://stackoverflow.com/a/20591539/146745 .


5
Ніколи не використовуйте режим ECB. Період.
Костянтино Спаракіс

1
ECB не слід використовувати, якщо шифрувати більше одного блоку даних одним і тим же ключем, тому для "Тексту для кодування" це досить добре. stackoverflow.com/a/1220869/146745
andrej

Ключ @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);
}

Хеш досі не є функцією генерації ключів на основі пароля / 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);
    }
}

застосування.властивості

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

Приклад

http: // localhost: 8082 / cipher / encrypt / jmendoza

2h41HH8Shzc4BRU3hVDOXA ==

http: // localhost: 8082 / cipher / decrypt / 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

Функція виведення пароля небезпечна. Я б не сподівався e.printStackTrace()на так званий оптимізований код.
Maarten Bodewes
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.