Як зашифрувати String на Java


150

Що мені потрібно - це зашифрувати рядок, який відображатиметься у двовимірному штрих-коді (PDF-417), тому коли хтось отримає ідею просканувати його, він нічого не читає.

Інші вимоги:

  • не повинно бути складним
  • він не повинен складатися з RSA, інфраструктури PKI, пар ключів тощо.

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

Можливо, ці компанії могли б використовувати різні технології, тому було б добре дотримуватися певного стандарту, який не прив'язаний до якоїсь спеціальної платформи чи технології.

Що ти пропонуєш? Чи є якийсь клас Java, який робить encrypt()& decrypt()без особливих ускладнень у досягненні високих стандартів безпеки?



Попередження . Дуже багато відповідей нижче показують той чи інший метод для виконання будь-якого виду криптографії на Java. Відповіді можуть не відображати належну криптографічну практику і не можуть бути переглянуті належним чином; не існує такого поняття, як захист від копіювання / вставки . Відповіді повинні принаймні враховувати перетворення рядків. Справжнє запитання із включеним 2D-штрих-кодом занадто широке, і воно потребує конкретного рішення для клієнта.
Maarten Bodewes

Відповіді:


156

Це перша сторінка, яка відображається через Google, і вразливості безпеки у всіх реалізаціях змушують мене переслідувати, тому я публікую цю інформацію, щоб додати інформацію щодо шифрування для інших, оскільки минуло 7 років від початкової публікації. Я маю ступінь магістра комп’ютерної інженерії і витратив багато часу на вивчення та вивчення криптографії, тому я кидаю два центи, щоб зробити Інтернет безпечнішим місцем.

Також зауважте, що багато можливостей може бути безпечним для даної ситуації, але навіщо їх використовувати та, можливо, випадково помилитися? Використовуйте найсильніші інструменти, які у вас є, якщо у вас немає конкретних причин цього не робити. Загалом, я дуже раджу користуватися бібліотекою та триматися подалі від тонких зернистих деталей, якщо можете.

ОНОВЛЕННЯ 4/5/18: Я переписав деякі частини, щоб зробити їх простішими для розуміння, і змінив рекомендовану бібліотеку з Jasypt на нову бібліотеку Google Tink , я б рекомендував повністю видалити Jasypt з існуючих налаштувань.

Передмова

Я викладу основи безпечної симетричної криптографії нижче та зазначу поширені помилки, які я бачу в Інтернеті, коли люди самостійно реалізовують криптовалюту зі стандартною бібліотекою Java. Якщо ви хочете просто пропустити всі деталі, перейдіть до нової бібліотеки Google, імпортуйте їх у свій проект, і використовуйте режим AES-GCM для всіх своїх шифрувань, і ви будете захищені.

Тепер, якщо ви хочете дізнатися деталі з грізними зернами, як шифрувати в Java, читайте далі :)

Блокувати шифри

Перш за все, потрібно спочатку вибрати симетричний ключ Блок-шифр. Блок-шифр - це функція / програма комп’ютера, що використовується для створення псевдовипадковості. Псевдовипадковість - це підроблена випадковість, що жоден комп'ютер, окрім квантового комп'ютера, не зміг би визначити різницю між ним та реальною випадковістю. Блок-шифр схожий на будівельний блок криптографії, і при використанні в різних режимах чи схемах ми можемо створювати шифрування.

Тепер щодо доступних сьогодні алгоритмів блокових шифрів. Переконайтесь, що НІКОЛИ , повторююсь НІКОЛИ не використовувати DES , я б навіть сказав, що НІКОЛИ не використовуйте 3DES . Єдиний блок-шифр, який навіть випуску NSA Snowden зміг переконатися, чи справді він максимально наближений до псевдо-випадкових - AES 256 . Є також AES 128; різниця полягає в тому, що AES 256 працює в 256-бітних блоках, тоді як AES 128 працює в 128 блоках. Загалом AES 128 вважається безпечним, хоча деякі слабкі місця були виявлені, але 256 є настільки ж надійними.

Веселий факт DES був зламаний АНБ ще тоді, коли він був заснований і фактично зберігав таємницю протягом декількох років. Хоча деякі люди все ще стверджують, що 3DES є безпечним, існує досить багато наукових робіт, які знайшли та проаналізували слабкі місця в 3DES .

Режими шифрування

Шифрування створюється, коли ви берете блок-шифр і використовуєте певну схему, так що випадковість поєднується з ключем для створення чогось оборотного, доки ви знаєте ключ. Це називається режимом шифрування.

Ось приклад режиму шифрування та найпростішого режиму, відомого як ECB, щоб ви могли наочно зрозуміти, що відбувається:

Режим ЄЦБ

Режими шифрування, які ви побачите найчастіше в Інтернеті, є такими:

CTR ЄС, CBC, GCM

Існують інші режими поза переліченими, і дослідники завжди працюють над новими режимами, щоб поліпшити існуючі проблеми.

Тепер перейдемо до реалізацій і того, що захищено. НІКОЛИ не використовуйте ECB, це погано приховувати повторювані дані, як показав відомий пінгвін Linux .Приклад Linux Penguin

Під час реалізації в Java зауважте, що якщо ви використовуєте наступний код, режим ECB встановлений за замовчуванням:

Cipher cipher = Cipher.getInstance("AES");

... НЕБЕЗПЕЧНО ЦЕ ВІННОСТІ! і, на жаль, це спостерігається в усьому StackOverflow та в Інтернеті у навчальних посібниках та прикладах.

Нонце і IV

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

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

Генерація випадкових IV

SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);

Примітка: SHA1 зламаний, але я не міг знайти, як правильно реалізувати SHA256 в цьому випадку використання, тому якщо хтось захоче зламати тріщину при цьому та оновити, це було б приголомшливо! Також напади SHA1 як і раніше є нетрадиційними, оскільки на величезному скупченні може пройти кілька років. Перегляньте деталі тут.

Впровадження CTR

Для режиму CTR не потрібні прокладки.

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

Впровадження ЦБ

Якщо ви вирішите реалізувати режим CBC, зробіть це з PKCS7Padding наступним чином:

 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");

Уразливість CBC та CTR та чому слід використовувати GCM

Хоча деякі інші режими, такі як CBC та CTR захищені, вони стикаються з проблемою, коли зловмисник може перевернути зашифровані дані, змінюючи його значення при розшифровці. Так, скажімо, ви шифруєте уявне банківське повідомлення "Продай 100", ваше зашифроване повідомлення виглядає таким чином "eu23ng", зловмисник змінює один біт на "eu53ng", і раптом, коли розшифровує ваше повідомлення, воно звучить як "Продай 900".

Щоб уникнути цього більшість Інтернету використовує GCM, і кожного разу, коли ви бачите HTTPS, вони, ймовірно, використовують GCM. GCM підписує зашифроване повідомлення хешем і перевіряє, чи повідомлення не було змінено за допомогою цього підпису.

Я б уникав впровадження GCM через його складність. Вам краще скористатися новою бібліотекою Googles Tink, оскільки тут знову, якщо ви випадково повторите IV, ви компрометуєте ключ у випадку з GCM, що є остаточним недоліком безпеки. Нові дослідники працюють над режимами IV повторного стійкого шифрування, коли навіть якщо ви повторите IV, ключ не загрожує, але це ще має стати основним.

Тепер, якщо ви хочете реалізувати GCM, ось посилання на приємну реалізацію GCM . Однак я не можу забезпечити безпеку або якщо вона належним чином реалізована, але вона знижує основу. Також зауважте, що в GCM немає прокладки.

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

Ключі проти паролів

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

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

Іншим менш безпечним варіантом є використання, введення користувача, наприклад пароль. Проблему, яку ми обговорювали, полягає в тому, що в паролі недостатньо ентропії, тому нам доведеться використовувати PBKDF2 , алгоритм, який приймає пароль і посилює його. Ось реалізація StackOverflow, яка мені сподобалась . Однак у бібліотеці Google Tink все це вбудовано, і ви повинні цим скористатися.

Android Developers

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

Цікаве прочитання з 2013 року : вказує на те, що 88% впроваджень Crypto в Android були виконані неналежно.

Фінальні думки

Ще раз рекомендую уникати прямої використання бібліотеки Java для криптовалюти та використовувати Google Tink , це позбавить вас від головного болю, оскільки вони справді добре виконали всі алгоритми. І навіть тоді переконайтеся, що ви перевіряєте проблеми, що виникають на Github Tink, вразливі місця тут і там.

Якщо у вас є запитання чи відгуки, не соромтесь коментувати! Безпека завжди змінюється, і вам потрібно зробити все можливе, щоб не відставати від неї :)


15
Це найчистіша річ, яку я коли-небудь бачив.
Сераф

1
@SabirKhan Це може викликати занепокоєння, але основні алгоритми все ще не були порушені, тому я б не переживав з цього приводу. У випадку, коли ви не довіряєте цьому, також ознайомтеся з github.com/google/keyczar , його розробила команда служби безпеки Google.
Костянтино Спаракіс

1
@KonstantinoSparakis: Якщо я неправильно інтерпретував документацію для Jasypt's BasicTextEncryptor та StrongTextEncryptor, ці класи використовують DES та 3DES для шифрування, саме це ви скажете читачам не використовувати. ІМО, ви повинні замінити наведені приклади коду на той, який використовує Jasypt's StandardPBEStringEncryptor і вручну визначає алгоритм AES для використання.
xpages-noob

1
@ xpages-noob Я оновив публікацію. Я фактично знайшов Google Tink, яка є новітньою підтримуваною бібліотекою для криптовалют, тому ви повинні перевірити це!
Костянтино Спаракіс

2
Розмір блоку AES - 128 біт. У AES 256 розмір ключа - 256 біт. Так само AES 192 та AES 128. Крім того, оскільки Java 8, getInstanceStrong()метод Cipherкращого порівняно з SHA1PRNG
Saptarshi Basu

110

Я б рекомендував використовувати деякі стандартні симетричні комоди, які широко доступні, як DES , 3DES або AES . Хоча це не найбезпечніший алгоритм, є безліч реалізацій, і вам просто потрібно буде дати ключ тому, хто повинен розшифрувати інформацію в штрих-коді. javax.crypto.Cipher - це те, з чим ви хочете працювати тут.

Припустимо, байти для шифрування знаходяться в

byte[] input;

Далі вам знадобляться ключі та байти вектора ініціалізації

byte[] keyBytes;
byte[] ivBytes;

Тепер ви можете ініціалізувати Cipher для вибраного алгоритму:

// wrap key data in Key/IV specs to pass to cipher
SecretKeySpec key = new SecretKeySpec(keyBytes, "DES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// create the cipher with the algorithm you choose
// see javadoc for Cipher class for more info, e.g.
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");

Шифрування піде так:

cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted= new byte[cipher.getOutputSize(input.length)];
int enc_len = cipher.update(input, 0, input.length, encrypted, 0);
enc_len += cipher.doFinal(encrypted, enc_len);

І дешифрування так:

cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = new byte[cipher.getOutputSize(enc_len)];
int dec_len = cipher.update(encrypted, 0, enc_len, decrypted, 0);
dec_len += cipher.doFinal(decrypted, dec_len);

9
Чи можу я запропонувати вам оновити цей приклад для посилання на DESedeалгоритм? Оскільки це популярне питання (і відповідь), було б прикро спонукати людей до використання DES, оскільки шифр настільки слабкий за сьогоднішніми мірками.
Данкан Джонс

щось не так з javax.crypto.BadPaddingException: Дано, що остаточний блок не зафіксований належним чином під час розшифровки
цікавість

2
@Duncan Дійсно DES слабкий, але я думаю, що AES буде кращим перед DESede (він же TipleDES): http://security.stackexchange.com/a/26181/69785
Piovezan

2
Це має бути оновлено, щоб мати AES / GCM / NoPadding, DES вразливий для нападів грубої сили, TripleDes також не рекомендується
Костянтино Спаракіс

1
Відповідь Костянтино Спаракіса нижче - це набагато краще, ніж цей.
Стів

22

Увага

Не використовуйте це як якесь вимірювання безпеки.

Механізм шифрування в цій публікації є одноразовим майданчиком, що означає, що зловмисник може легко відновити секретний ключ, використовуючи 2 зашифровані повідомлення. XOR 2 зашифровані повідомлення, і ви отримаєте ключ. Це просто!

Вказаний Муссою


Я використовую Base64Encoder / декодер Sun, який можна знайти в JRE Sun, щоб уникнути ще одного JAR в lib. Це небезпечно з точки зору використання OpenJDK або якогось іншого JRE. Крім того, чи є ще одна причина, яку я повинен розглянути, як використовувати Apache commons lib з Encoder / Decoder?

public class EncryptUtils {
    public static final String DEFAULT_ENCODING = "UTF-8"; 
    static BASE64Encoder enc = new BASE64Encoder();
    static BASE64Decoder dec = new BASE64Decoder();

    public static String base64encode(String text) {
        try {
            return enc.encode(text.getBytes(DEFAULT_ENCODING));
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }//base64encode

    public static String base64decode(String text) {
        try {
            return new String(dec.decodeBuffer(text), DEFAULT_ENCODING);
        } catch (IOException e) {
            return null;
        }
    }//base64decode

    public static void main(String[] args) {
        String txt = "some text to be encrypted";
        String key = "key phrase used for XOR-ing";
        System.out.println(txt + " XOR-ed to: " + (txt = xorMessage(txt, key)));

        String encoded = base64encode(txt);       
        System.out.println(" is encoded to: " + encoded + " and that is decoding to: " + (txt = base64decode(encoded)));
        System.out.print("XOR-ing back to original: " + xorMessage(txt, key));
    }

    public static String xorMessage(String message, String key) {
        try {
            if (message == null || key == null) return null;

            char[] keys = key.toCharArray();
            char[] mesg = message.toCharArray();

            int ml = mesg.length;
            int kl = keys.length;
            char[] newmsg = new char[ml];

            for (int i = 0; i < ml; i++) {
                newmsg[i] = (char)(mesg[i] ^ keys[i % kl]);
            }//for i

            return new String(newmsg);
        } catch (Exception e) {
            return null;
        }
    }//xorMessage
}//class

1
Я також використовував цю пропозицію рішення через sun.misc.BASE64Encoder, але, використовуючи досить великі рядки для кодування, кодер повертав фрагменти (76 символів у кожному). Потім я перейшов на Apache Commons Codec Base64, який пропонує методи без кодування.
basZero

78
Механізм шифрування, який ви описали, ДУЖЕ ОПАСНИЙ, якщо використовується декілька разів. саме тому його називають одноразовим майданчиком. Таємний ключ зловмисник може легко відновити за допомогою 2 зашифрованих повідомлень. xor 2 зашифровані повідомлення, і ви отримаєте ключ. Це просто!
xtrem

3
Його ідея не бути важкою, а просто відштовхуватися від спроб прочитати те, що написано у 2D штрих-кодах PDF-417. І все одно, є лише деякі індекси, не важливі ні для кого ...
ante.sabo

2
ГАРАЗД. Тільки хвилює, що хтось використовує це як механізм шифрування.
xtrem

Для шифрування, кодером (наприклад.BASE64Encoder) можна уникнути нападів грубої сили.
Ягрут Далваді

13

дякую, я зробив цей клас за допомогою вашого коду, можливо, хтось вважає його корисним

об'єктний криптер

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class ObjectCrypter {

private Cipher deCipher;
private Cipher enCipher;
private SecretKeySpec key;
private IvParameterSpec ivSpec;


public ObjectCrypter(byte[] keyBytes,   byte[] ivBytes) {
    // wrap key data in Key/IV specs to pass to cipher


     ivSpec = new IvParameterSpec(ivBytes);
    // create the cipher with the algorithm you choose
    // see javadoc for Cipher class for more info, e.g.
    try {
         DESKeySpec dkey = new  DESKeySpec(keyBytes);
          key = new SecretKeySpec(dkey.getKey(), "DES");
         deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
         enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
public byte[] encrypt(Object obj) throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, IllegalBlockSizeException, ShortBufferException, BadPaddingException {
    byte[] input = convertToByteArray(obj);
    enCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

    return enCipher.doFinal(input);




//  cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
//  byte[] encypted = new byte[cipher.getOutputSize(input.length)];
//  int enc_len = cipher.update(input, 0, input.length, encypted, 0);
//  enc_len += cipher.doFinal(encypted, enc_len);
//  return encypted;


}
public Object decrypt( byte[]  encrypted) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException, ClassNotFoundException {
    deCipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

    return convertFromByteArray(deCipher.doFinal(encrypted));

}



private Object convertFromByteArray(byte[] byteObject) throws IOException,
        ClassNotFoundException {
    ByteArrayInputStream bais;

    ObjectInputStream in;
    bais = new ByteArrayInputStream(byteObject);
    in = new ObjectInputStream(bais);
    Object o = in.readObject();
    in.close();
    return o;

}



private byte[] convertToByteArray(Object complexObject) throws IOException {
    ByteArrayOutputStream baos;

    ObjectOutputStream out;

    baos = new ByteArrayOutputStream();

    out = new ObjectOutputStream(baos);

    out.writeObject(complexObject);

    out.close();

    return baos.toByteArray();

}


}

опублікував пов’язане запитання тут !
користувач2023507

Чи не повинно бути так, що передача різнихKeys під час шифрування та дешифрування не повинна повертати текст назад? Здається, тут не відбувається. PS: Я використовую різні об'єкти цього класу для виконання цього тесту.
instanceOfObject

6

Оновлення 12-грудня-2019

На відміну від деяких інших режимів, таких як CBC, режим GCM не вимагає, щоб IV був непередбачуваним. Єдина вимога - IV повинен бути унікальним для кожного виклику із заданим ключем. Якщо воно повториться один раз для заданого ключа, безпека може бути порушена. Простий спосіб досягти цього - використовувати випадковий ІV від сильного генератора псевдовипадкових чисел, як показано нижче.

Використання послідовності або часової позначки як IV також можливе, але це може бути не таким тривіальним, як це може звучати. Наприклад, якщо система неправильно відслідковує послідовності, які вже використовуються як IV у постійному сховищі, виклик може повторити IV після перезавантаження системи. Так само немає ідеального годинника. Регулювання годинника комп'ютера і т.д.

Також ключ слід повертати після кожні 2 ^ 32 виклику. Детальнішу інформацію про вимогу IV див. У цій відповіді та рекомендаціях NIST .


Це код шифрування та дешифрування, який я щойно написав на Java 8, враховуючи наступні моменти. Сподіваюсь, хтось знайде це корисним:

  1. Алгоритм шифрування : Блокувати шифр AES з 256 бітним ключем вважається досить безпечним. Для шифрування повного повідомлення потрібно вибрати режим. Рекомендується автентичне шифрування (яке забезпечує як конфіденційність, так і цілісність). GCM, CCM та EAX найчастіше використовують автентифіковані режими шифрування. Зазвичай GCM є кращим, і він добре працює в архітектурах Intel, які надають спеціальні інструкції для GCM. Усі ці три режими є режимами на основі CTR (на основі лічильника), тому вони не потребують набивання. Як результат, вони не вразливі до нападів, пов'язаних із забиванням

  2. Для GCM необхідний вектор ініціалізації (IV). IV не є секретом. Єдина вимога, яка полягає в тому, щоб вона була випадковою або непередбачуваною. У Java цей SecuredRandomклас призначений для отримання криптографічно сильних псевдовипадкових чисел. Алгоритм генерації псевдовипадкових чисел може бути визначений у getInstance()методі. Однак, оскільки Java 8, рекомендованим способом є використання getInstanceStrong()методу, який буде використовувати найсильніший алгоритм, налаштований і наданийProvider

  3. NIST рекомендує 96-бітний IV для GCM для сприяння сумісності, ефективності та простоті конструкції

  4. Щоб забезпечити додаткову безпеку, у наступній реалізації SecureRandomповторно засівають після створення кожні 2 ^ 16 байт генерації псевдо випадкових байтів

  5. Одержувач повинен знати IV, щоб мати можливість розшифрувати текст шифру. Тому IV потрібно передати разом із текстом шифру. Деякі реалізації передають IV як AD (Associated Data), що означає, що тег аутентифікації буде обчислюватися як на тексті шифру, так і на IV. Однак цього не потрібно. IV може бути просто заздалегідь зашифрований текстом шифру, тому що якщо IV змінився під час передачі через навмисну ​​атаку або помилку мережі / файлової системи, перевірка тегу аутентифікації все одно не вдасться

  6. Строки не повинні використовуватися для зберігання чіткого текстового повідомлення або ключа, оскільки рядки незмінні, і тому ми не можемо очистити їх після використання. Ці неочищені струни потім залишаються в пам'яті і можуть з’являтися на купі сміття. З тієї ж причини клієнт, що викликає ці методи шифрування чи дешифрування, повинен очистити всі змінні або масиви, що містять повідомлення або ключ після того, як вони більше не потрібні.

  7. Жоден постачальник не кодується в коді відповідно до загальних рекомендацій

  8. Нарешті, для передачі через мережу або сховище, ключ або текст шифру слід кодувати за допомогою кодування Base64. Деталі Base64 можна знайти тут . Слід дотримуватися підходу Java 8

Байтові масиви можна очистити за допомогою:

Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);

Однак, як в Java 8, немає простого способу очистити SecretKeyspecі SecretKeyяк реалізацію цих двох інтерфейсів , здається, не реалізував метод destroy()інтерфейсу Destroyable. У наступному коді записаний окремий метод для очищення SecretKeySpecтаSecretKey використання відображення.

Ключ повинен бути згенерований за допомогою одного з двох підходів, згаданих нижче.

Зауважте, що ключі є секретами, такими як паролі, але на відміну від паролів, призначених для використання людиною, ключі повинні використовуватися криптографічними алгоритмами, а значить, їх слід генерувати лише згаданим способом.

package com.sapbasu.javastudy;

import java.lang.reflect.Field;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

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

public class Crypto {

  private static final int AUTH_TAG_SIZE = 128; // bits

  // NIST recommendation: "For IVs, it is recommended that implementations
  // restrict support to the length of 96 bits, to
  // promote interoperability, efficiency, and simplicity of design."
  private static final int IV_LEN = 12; // bytes

  // number of random number bytes generated before re-seeding
  private static final double PRNG_RESEED_INTERVAL = Math.pow(2, 16);

  private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";

  private static final List<Integer> ALLOWED_KEY_SIZES = Arrays
      .asList(new Integer[] {128, 192, 256}); // bits

  private static SecureRandom prng;

  // Used to keep track of random number bytes generated by PRNG
  // (for the purpose of re-seeding)
  private static int bytesGenerated = 0;

  public byte[] encrypt(byte[] input, SecretKeySpec key) throws Exception {

    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Length of message cannot be 0");
    }

    if (!ALLOWED_KEY_SIZES.contains(key.getEncoded().length * 8)) {
      throw new IllegalArgumentException("Size of key must be 128, 192 or 256");
    }

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

    byte[] iv = getIV(IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    cipher.init(Cipher.ENCRYPT_MODE, key, gcmParamSpec);
    byte[] messageCipher = cipher.doFinal(input);

    // Prepend the IV with the message cipher
    byte[] cipherText = new byte[messageCipher.length + IV_LEN];
    System.arraycopy(iv, 0, cipherText, 0, IV_LEN);
    System.arraycopy(messageCipher, 0, cipherText, IV_LEN,
        messageCipher.length);
    return cipherText;
  }

  public byte[] decrypt(byte[] input, SecretKeySpec key) throws Exception {
    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Input array cannot be empty");
    }

    byte[] iv = new byte[IV_LEN];
    System.arraycopy(input, 0, iv, 0, IV_LEN);

    byte[] messageCipher = new byte[input.length - IV_LEN];
    System.arraycopy(input, IV_LEN, messageCipher, 0, input.length - IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
    cipher.init(Cipher.DECRYPT_MODE, key, gcmParamSpec);

    return cipher.doFinal(messageCipher);
  }

  public byte[] getIV(int bytesNum) {

    if (bytesNum < 1) throw new IllegalArgumentException(
        "Number of bytes must be greater than 0");

    byte[] iv = new byte[bytesNum];

    prng = Optional.ofNullable(prng).orElseGet(() -> {
      try {
        prng = SecureRandom.getInstanceStrong();
      } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Wrong algorithm name", e);
      }
      return prng;
    });

    if (bytesGenerated > PRNG_RESEED_INTERVAL || bytesGenerated == 0) {
      prng.setSeed(prng.generateSeed(bytesNum));
      bytesGenerated = 0;
    }

    prng.nextBytes(iv);
    bytesGenerated = bytesGenerated + bytesNum;

    return iv;
  }

  private static void clearSecret(Destroyable key)
      throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    Field keyField = key.getClass().getDeclaredField("key");
    keyField.setAccessible(true);
    byte[] encodedKey = (byte[]) keyField.get(key);
    Arrays.fill(encodedKey, Byte.MIN_VALUE);
  }
}

Ключ шифрування може генеруватися переважно двома способами:

  • Без пароля

    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong());
    SecretKey secretKey = keyGen.generateKey();
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);
  • З паролем

    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[32];
    random.nextBytes(salt);
    PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterations, 
       keyLength);
    SecretKeyFactory keyFactory = 
        SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    SecretKey secretKey = keyFactory.generateSecret(keySpec);
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);

Оновлення на основі коментарів

Як вказував @MaartenBodewes, моя відповідь не вирішила жодного питання, Stringяк того вимагає запитання. Тому я спробую заповнити цю прогалину на випадок, якщо хтось наткнеться на цю відповідь і залишиться цікавитись справою String.

Як було зазначено раніше у відповіді, поводження з конфіденційною інформацією в а- Stringце взагалі не є хорошою ідеєю, оскільки Stringнепорушна, і тому ми не можемо її очистити після використання. Як ми знаємо, навіть коли у Stringнього немає чіткої орієнтації, сміттєзбірник не відразу поспішає вивозити його з купи. Таким чиномString продовжує залишатися навколо в пам'яті протягом невідомого часу, хоча це не є доступним для програми. Проблема з цим полягає у тому, що купі звалища за цей проміжок часу виявлять конфіденційну інформацію. Тому завжди краще обробляти всю конфіденційну інформацію в байтовому масиві або масиві char, а потім заповнювати масив 0, як тільки їх призначення буде виконано.

Однак, маючи все це знання, якщо ми все-таки потрапимо в ситуацію, коли конфіденційна інформація, яка підлягає шифруванню, знаходиться в a String, спочатку нам потрібно перетворити її в байтовий масив і викликати encryptі decryptвведені вище функції. (Інший ключ введення можна згенерувати за допомогою фрагмента коду, наведеного вище).

A Stringможна перетворити в байти наступним чином:

byte[] inputBytes = inputString.getBytes(StandardCharsets.UTF_8);

За станом на Java 8, Stringвнутрішньо зберігається в купі з UTF-16кодуванням. Однак ми UTF-8тут використали, оскільки це зазвичай займає менше місця, ніж UTF-16, особливо для символів ASCII.

Аналогічно, зашифрований байтовий масив також може бути перетворений у рядок, як показано нижче:

String encryptedString = new String(encryptedBytes, StandardCharsets.UTF_8);

1
Наскільки я хочу підкріпити цю відповідь, оскільки це виглядає як дотримання сучасних крипто-практик, я взагалі не бачу жодної обробки рядків, що робить її більше як просто опис того, як використовувати режим GCM. Як такий, він не відповідає на питання .
Maarten Bodewes

1
@MaartenBodewes Дякую вам за те, що ви взяли час для перегляду та обміну відгуками. Я написав це з розумінням, що шифрування за Stringдопомогою функцій, створених вище, було б тривіальним. Однак, з другого погляду, прочитавши ваш коментар, я розумію, що це може бути не очевидно. Я обов'язково редагую, щоб додати ці деталі.
Саптарші Басу

5

Як щодо цього:

private static byte[] xor(final byte[] input, final byte[] secret) {
    final byte[] output = new byte[input.length];
    if (secret.length == 0) {
        throw new IllegalArgumentException("empty security key");
    }
    int spos = 0;
    for (int pos = 0; pos < input.length; ++pos) {
        output[pos] = (byte) (input[pos] ^ secret[spos]);
        ++spos;
        if (spos >= secret.length) {
            spos = 0;
        }
    }
    return output;
}

Для мене працює чудово і досить компактно.


що станеться, якщо параметр входу secret == null або input == null? Працювати з байтами, а не з рядками, це нормально, але в моєму випадку не має значення. Єдине, що важливо, це те, що це повинно бути читабельним і декодируемим на будь-якому пристрої, при будь-якому можливому кодуванні символів ...
ante.sabo

@ ante.sabo мабуть, це кине NPE. Це єдине, що стосується NULL.
Miha_x64

Поки input.length <= secret.lengthутримується і secretніколи не використовується повторно, це безпечно і називається a one-time-pad. У випадках із input.length > secret.lengthцим є варіант шифру Віньєра і вважається дуже слабким.
трихнер

5

Можна використовувати Jasypt

З Jasypt, шифрування та перевірка пароля може бути таким же простим, як ...

StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
textEncryptor.setPassword(myEncryptionPassword);

Шифрування:

String myEncryptedText = textEncryptor.encrypt(myText);

Розшифровка:

String plainText = textEncryptor.decrypt(myEncryptedText);

Gradle:

compile group: 'org.jasypt', name: 'jasypt', version: '1.9.2'

Особливості:

Jasypt надає вам прості однонаправлені (дайджест) та двонаправлені методи шифрування.

Відкрийте API для використання з будь-яким постачальником JCE, і не тільки з Java VM за замовчуванням. Jasypt можна легко використовувати з відомими постачальниками, такими як Замок Bouncy. Вчи більше.

Більш висока безпека паролів користувачів. Вчи більше.

Підтримка бінарного шифрування. Jasypt дозволяє дайджест і шифрування двійкових файлів (байтових масивів). Зашифруйте свої об’єкти або файли, коли це потрібно (наприклад, для надсилання через мережу).

Підтримка шифрування номерів. Окрім текстів та двійкових файлів, це дозволяє дайджест і шифрування числових значень (BigInteger та BigDecimal, інші числові типи підтримуються при шифруванні для збереження в режимі глибокого сну). Вчи більше.

Повністю захищений від потоку.

Підтримка об'єднання шифрів / дайджерів для досягнення високої продуктивності в багатопроцесорних / багатоядерних системах.

Включає легку ("легку") версію бібліотеки для кращого керування в обмежених розмірах середовищах, таких як мобільні платформи.

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

Hibernate 3 та 4 додаткової інтеграції для збережених полів ваших відображених об'єктів у зашифрованому вигляді. Шифрування полів визначено у файлах відображення в сплячому режимі, і воно залишається прозорим для решти програми (корисно для чутливих особистих даних, баз даних з багатьма користувачами з включеним читанням ...). Зашифруйте тексти, двійкові файли, числа, булеві дати, дати ... Дізнайтеся більше.

Безпроблемно інтегрується у додаток Spring із специфічними функціями інтеграції для Spring 2, Spring 3.0 та Spring 3.1. Усі копачі та шифри в ясипті розроблені для легкого використання (інстанції, введення залежностей ...) від Spring. І, оскільки вони не є безпечними для потоків, їх можна використовувати без турбот про синхронізацію в середовищі, орієнтованому на однотонну, як Spring. Дізнайтеся більше: Весна 2, Весна 3.0, Весна 3.1.

Spring Security (раніше Acegi Security) - додаткова інтеграція для виконання шифрування паролів та відповідності завдань для системи безпеки, поліпшення безпеки паролів користувачів за допомогою безпечніших механізмів шифрування паролів та надання вам більш високого ступеня конфігурації та контролю. Вчи більше.

Забезпечує розширену функціональність для шифрування всіх або частини файлів конфігурації програми, включаючи конфіденційну інформацію, наприклад паролі бази даних. Безпроблемно інтегрувати зашифровану конфігурацію в звичайні програми, засновані на веснах та / або в сплячку. Вчи більше.

Забезпечує прості у використанні інструменти CLI (інтерфейс командного рядка), що дозволяють розробникам ініціалізувати свої зашифровані дані та включати операції шифрування / розшифрування / дайджесту в завдання технічного обслуговування або сценарії. Вчи більше.

Інтегрується в Apache Wicket для більш надійного шифрування URL-адрес у захищених програмах.

Вичерпні посібники та документацію на javadoc, щоб розробники могли краще зрозуміти, що вони насправді роблять зі своїми даними.

Міцна підтримка набірних гарнітур, призначена для адекватного шифрування та засвоєння текстів незалежно від оригіналу. Повна підтримка таких мов, як японська, корейська, арабська ... без проблем з кодуванням або платформою.

Дуже високий рівень конфігураційних можливостей: Розробник може реалізувати такі хитрощі, як інструктаж "шифрувача" запитати, наприклад, віддалений сервер HTTPS щодо пароля, який буде використаний для шифрування. Це дозволяє вам задовольнити свої потреби в безпеці.


1
Але що забезпечує безпека Jasypt? Я не можу це зрозуміти з їх веб-сайту. Чи це не відрізняється від атак, що вибираються в прямому тексті? Цілісність? Конфіденційність?
трихнер

4

Ось моя реалізація від meta64.com як Spring Singleton. Якщо ви хочете створити екземпляр ciper для кожного дзвінка, який також працював, і ви можете видалити "синхронізовані" дзвінки, але остерігайтеся "шифру" не є безпечним для потоків.

import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

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

@Component
@Scope("singleton")
public class Encryptor {

    @Value("${aeskey}")
    private String keyStr;

    private Key aesKey = null;
    private Cipher cipher = null;

    synchronized private void init() throws Exception {
        if (keyStr == null || keyStr.length() != 16) {
            throw new Exception("bad aes key configured");
        }
        if (aesKey == null) {
            aesKey = new SecretKeySpec(keyStr.getBytes(), "AES");
            cipher = Cipher.getInstance("AES");
        }
    }

    synchronized public String encrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);
        return toHexString(cipher.doFinal(text.getBytes()));
    }

    synchronized public String decrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.DECRYPT_MODE, aesKey);
        return new String(cipher.doFinal(toByteArray(text)));
    }

    public static String toHexString(byte[] array) {
        return DatatypeConverter.printHexBinary(array);
    }

    public static byte[] toByteArray(String s) {
        return DatatypeConverter.parseHexBinary(s);
    }

    /*
     * DO NOT DELETE
     * 
     * Use this commented code if you don't like using DatatypeConverter dependency
     */
    // public static String toHexStringOld(byte[] bytes) {
    // StringBuilder sb = new StringBuilder();
    // for (byte b : bytes) {
    // sb.append(String.format("%02X", b));
    // }
    // return sb.toString();
    // }
    //
    // public static byte[] toByteArrayOld(String s) {
    // int len = s.length();
    // byte[] data = new byte[len / 2];
    // for (int i = 0; i < len; i += 2) {
    // data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i +
    // 1), 16));
    // }
    // return data;
    // }
}

3
Це зашифрує жахливий режим ECB. Вам слід встановити принаймні режим CBC або GCM Mode
Костянтино Спаракіс

Дякую за пропозицію Костянтинто, я погуглив це і знайшов якийсь код, який використовує "AES / CBC / PKCS5Padding" як рядок Init для Cipher, а не просто "AES", але я буду детальніше розглядати його. Або якщо ви хочете, ви можете надати фактичне виправлення, щоб інші могли бачити кращий спосіб. Однак, окрім деталей CBC, я вважаю, що моє рішення є найпростішим та найбезпечнішим, і воно заслуговує на те, щоб отримати перевагу понад усе.

Так, не хвилюйтесь, Crypto - складний предмет. На жаль, кожна реалізація на цій сторінці зламана, і, на жаль, це перша сторінка, яка з’являється під час використання Google для пошуку "як зробити шифрування Java". Коли я отримаю можливість, я спробую виправити їх усі.
Костянтино Спаракіс

Мій приклад такий же: docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/… За винятком того, що мені потрібен Cipher.getInstance ("AES / ECB / PKCS5Padding"); Мій код передбачає, що є файл властивостей з ідеально шифруваним ключем довгим 16 байтом, але для шифрування рядка з пароля "наданий користувачем" сторінка oracle (пов'язана вище) також показує спосіб зробити це.

1
Отже, проблема ЄЦБ полягає в тому, що вона надзвичайно вразлива для частотного аналізу. Існує відомий приклад пінгвіна Linux, blog.filippo.io/the-ecb-penguin дивіться, як, хоча зображення зашифроване, ви все ще можете сказати, що це пінгвін. Я пішов вперед і написав свої думки на тему внизу :) stackoverflow.com/a/43779197/2607972
Костянтино Sparakis

4

Тут просте рішення з лише java.*та javax.crypto.*залежностями для шифрування байтів, що забезпечує конфіденційність та цілісність . Він повинен відрізнятися від обраної атаки прямого тексту для коротких повідомлень у порядку кілобайт.

Він використовується AESв GCMрежимі без прокладки, 128-бітовий ключ виводиться за PBKDF2допомогою безлічі ітерацій та статичної солі з наданого пароля. Це гарантує, що жорстокі примусові паролі важкі та розподіляють ентропію по всьому ключу.

Генерується випадковий вектор ініціалізації (IV) і буде подано до шифротексту. Крім того, статичний байт 0x01вважається першим байтом як "версія".

Все повідомлення переходить у код автентифікації повідомлення (MAC), що генерується AES/GCM.

Ось, нульовий клас шифрування зовнішніх залежностей, що забезпечує конфіденційність та цілісність :

package ch.n1b.tcrypt.utils;

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * This class implements AES-GCM symmetric key encryption with a PBKDF2 derived password.
 * It provides confidentiality and integrity of the plaintext.
 *
 * @author Thomas Richner
 * @created 2018-12-07
 */
public class AesGcmCryptor {

    // /crypto/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode
    private static final byte VERSION_BYTE = 0x01;
    private static final int VERSION_BYTE_LENGTH = 1;
    private static final int AES_KEY_BITS_LENGTH = 128;


    // fixed AES-GCM constants
    private static final String GCM_CRYPTO_NAME = "AES/GCM/NoPadding";
    private static final int GCM_IV_BYTES_LENGTH = 12;
    private static final int GCM_TAG_BYTES_LENGTH = 16;

    // can be tweaked, more iterations = more compute intensive to brute-force password
    private static final int PBKDF2_ITERATIONS = 1024;

    // protects against rainbow tables
    private static final byte[] PBKDF2_SALT = hexStringToByteArray("4d3fe0d71d2abd2828e7a3196ea450d4");

    public String encryptString(char[] password, String plaintext) throws CryptoException {

        byte[] encrypted = null;
        try {
            encrypted = encrypt(password, plaintext.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException //
                | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException //
                | InvalidKeySpecException e) {
            throw new CryptoException(e);
        }
        return byteArrayToHexString(encrypted);
    }

    public String decryptString(char[] password, String ciphertext)
            throws CryptoException {

        byte[] ct = hexStringToByteArray(ciphertext);
        byte[] plaintext = null;
        try {
            plaintext = decrypt(password, ct);
        } catch (AEADBadTagException e) {
            throw new CryptoException(e);
        } catch ( //
                NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeySpecException //
                        | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException //
                        | BadPaddingException e) {
            throw new CryptoException(e);
        }
        return new String(plaintext, StandardCharsets.UTF_8);
    }

    /**
     * Decrypts an AES-GCM encrypted ciphertext and is
     * the reverse operation of {@link AesGcmCryptor#encrypt(char[], byte[])}
     *
     * @param password   passphrase for decryption
     * @param ciphertext encrypted bytes
     * @return plaintext bytes
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws IllegalArgumentException           if the length or format of the ciphertext is bad
     * @throws CryptoException
     */
    public byte[] decrypt(char[] password, byte[] ciphertext)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        // input validation
        if (ciphertext == null) {
            throw new IllegalArgumentException("ciphertext cannot be null");
        }

        if (ciphertext.length <= VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH + GCM_TAG_BYTES_LENGTH) {
            throw new IllegalArgumentException("ciphertext too short");
        }

        // the version must match, we don't decrypt other versions
        if (ciphertext[0] != VERSION_BYTE) {
            throw new IllegalArgumentException("wrong version: " + ciphertext[0]);
        }

        // input seems legit, lets decrypt and check integrity

        // derive key from password
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);

        // init cipher
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec params = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8,
                ciphertext,
                VERSION_BYTE_LENGTH,
                GCM_IV_BYTES_LENGTH
        );
        cipher.init(Cipher.DECRYPT_MODE, key, params);

        final int ciphertextOffset = VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH;

        // add version and IV to MAC
        cipher.updateAAD(ciphertext, 0, ciphertextOffset);

        // decipher and check MAC
        return cipher.doFinal(ciphertext, ciphertextOffset, ciphertext.length - ciphertextOffset);
    }

    /**
     * Encrypts a plaintext with a password.
     * <p>
     * The encryption provides the following security properties:
     * Confidentiality + Integrity
     * <p>
     * This is achieved my using the AES-GCM AEAD blockmode with a randomized IV.
     * <p>
     * The tag is calculated over the version byte, the IV as well as the ciphertext.
     * <p>
     * Finally the encrypted bytes have the following structure:
     * <pre>
     *          +-------------------------------------------------------------------+
     *          |         |               |                             |           |
     *          | version | IV bytes      | ciphertext bytes            |    tag    |
     *          |         |               |                             |           |
     *          +-------------------------------------------------------------------+
     * Length:     1B        12B            len(plaintext) bytes            16B
     * </pre>
     * Note: There is no padding required for AES-GCM, but this also implies that
     * the exact plaintext length is revealed.
     *
     * @param password  password to use for encryption
     * @param plaintext plaintext to encrypt
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidKeySpecException
     */
    public byte[] encrypt(char[] password, byte[] plaintext)
            throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException,
            InvalidKeySpecException {

        // initialise random and generate IV (initialisation vector)
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);
        final byte[] iv = new byte[GCM_IV_BYTES_LENGTH];
        SecureRandom random = SecureRandom.getInstanceStrong();
        random.nextBytes(iv);

        // encrypt
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);

        // add IV to MAC
        final byte[] versionBytes = new byte[]{VERSION_BYTE};
        cipher.updateAAD(versionBytes);
        cipher.updateAAD(iv);

        // encrypt and MAC plaintext
        byte[] ciphertext = cipher.doFinal(plaintext);

        // prepend VERSION and IV to ciphertext
        byte[] encrypted = new byte[1 + GCM_IV_BYTES_LENGTH + ciphertext.length];
        int pos = 0;
        System.arraycopy(versionBytes, 0, encrypted, 0, VERSION_BYTE_LENGTH);
        pos += VERSION_BYTE_LENGTH;
        System.arraycopy(iv, 0, encrypted, pos, iv.length);
        pos += iv.length;
        System.arraycopy(ciphertext, 0, encrypted, pos, ciphertext.length);

        return encrypted;
    }

    /**
     * We derive a fixed length AES key with uniform entropy from a provided
     * passphrase. This is done with PBKDF2/HMAC256 with a fixed count
     * of iterations and a provided salt.
     *
     * @param password passphrase to derive key from
     * @param salt     salt for PBKDF2 if possible use a per-key salt, alternatively
     *                 a random constant salt is better than no salt.
     * @param keyLen   number of key bits to output
     * @return a SecretKey for AES derived from a passphrase
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    private SecretKey deriveAesKey(char[] password, byte[] salt, int keyLen)
            throws NoSuchAlgorithmException, InvalidKeySpecException {

        if (password == null || salt == null || keyLen <= 0) {
            throw new IllegalArgumentException();
        }
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, keyLen);
        SecretKey pbeKey = factory.generateSecret(spec);

        return new SecretKeySpec(pbeKey.getEncoded(), "AES");
    }

    /**
     * Helper to convert hex strings to bytes.
     * <p>
     * May be used to read bytes from constants.
     */
    private static byte[] hexStringToByteArray(String s) {

        if (s == null) {
            throw new IllegalArgumentException("Provided `null` string.");
        }

        int len = s.length();
        if (len % 2 != 0) {
            throw new IllegalArgumentException("Invalid length: " + len);
        }

        byte[] data = new byte[len / 2];
        for (int i = 0; i < len - 1; i += 2) {
            byte b = (byte) toHexDigit(s, i);
            b <<= 4;
            b |= toHexDigit(s, i + 1);
            data[i / 2] = b;
        }
        return data;
    }

    private static int toHexDigit(String s, int pos) {
        int d = Character.digit(s.charAt(pos), 16);
        if (d < 0) {
            throw new IllegalArgumentException("Cannot parse hex digit: " + s + " at " + pos);
        }
        return d;
    }

    private static String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }

    public class CryptoException extends Exception {

        public CryptoException(Throwable cause) {
            super(cause);
        }
    }
}

Тут весь проект із приємним CLI: https://github.com/trichner/tcrypt

Редагувати: зараз з відповідним encryptStringіdecryptString


Це неймовірно. Дякую! Я багато чого навчився з вашого коду, і після створення класу BadVersionException Exception ваш перший код відмінно працював. Відмінно !!
Моркус

Мені подобається ця спроба. Це сказало ... Сіль повинна бути випадковою, а не статичною. Ітерації, ймовірно, також не повинні бути статичними. GCM вже включає IV у розрахунок тегу. Він не містить номера версії. Не слід вказувати провайдера для портативності, "SunJCE" буде типовим на платформах, які його підтримують. Цей код не містить обробки рядків повідомлень, необхідних для цього конкретного питання .
Maarten Bodewes

Гаразд, я прибрав це ще трохи і додав запитуване encryptStringі decryptString:)
трихнер

Це спрацювало дуже добре; ty для коду. Слід зазначити, що для правильного функціонування цього коду потрібен API 19 (Kit Kat) або вище.
PGMacDesign

3

Я б подумав використати щось на зразок https://www.bouncycastle.org/ Це попередньо створена бібліотека, яка дозволяє вам шифрувати все, що вам подобається, за допомогою декількох різних шифрів, я розумію, що ви хочете захиститись лише від проскоку, але якщо ви справді хочете захистити інформацію, використання Base64 насправді не захистить вас.


1
Просто рекомендувати довільну бібліотеку криптовалют із шифрами - це не відповідь на питання. Крім того, чому б не використовувати вбудовані шифри?
Maarten Bodewes

2

Ось декілька посилань, з яких можна прочитати, що підтримує Java

Шифрування / розшифрування потоку даних.

Цей приклад демонструє, як шифрувати (використовуючи симетричний алгоритм шифрування, такий як AES, Blowfish, RC2, 3DES тощо) великий обсяг даних. Дані передаються фрагментами одному з методів шифрування: EncryptBytes, EncryptString, EncryptBytesENC або EncryptStringENC. (Назва методу вказує на тип введення (рядок або байтовий масив) та тип повернення (кодований рядок або байтовий масив). Властивості FirstChunk та LastChunk використовуються для вказівки, чи фрагмент є першим, середнім чи останнім у потоці За замовчуванням і FirstChunk, і LastChunk однакові істинні - це означає, що передані дані - це вся сума.

JCERefGuide

Приклади шифрування Java


Так, є криптографія, що підтримується Java. Шифрування потоку - це не те, що запитували.
Maarten Bodewes

2

Як і багато хто з хлопців, які вже говорили, ви повинні використовувати стандартний циферблат, який надмірно використовується, як DES чи AES.

Простий приклад того, як можна зашифрувати та розшифрувати рядок у Java за допомогою AES .

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

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

public class EncryptorDemo {

    public static String encrypt(String key, String randomVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.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 text: "  + Base64.encodeBase64String(encrypted));
            return Base64.encodeBase64String(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String randomVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.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[] originalText = cipher.doFinal(Base64.decodeBase64(encrypted));
            System.out.println("decrypted text: "  + new String(originalText));
            return new String(originalText);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        String key = "JavasEncryptDemo"; // 128 bit key
        String randomVector = "RandomJavaVector"; // 16 bytes IV
        decrypt(key, randomVector, encrypt(key, randomVector, "Anything you want to encrypt!"));

    }
}

CBC більше не захищений режим. Прокладка вразлива для нападу Oracle. Також обробка ключа та повідомлень у String не є безпечною. Вони затримаються в басейні Стринг і з’являться на купі сміття
Саптарші Басу

2
Вдячний за коментар. Це був простий приклад методів шифрування та розшифрування Java, як запитував користувач. На це питання було задано дев'ять років тому, і на його відповідь відповіли. Дякую.
viveknaskar

2
Так, це здається простим способом введення шифру / дешифрування. Працювало як шарм для мене .... Дякую.
Codewrapper

0

Ось рішення для копіювання / вставки. Я також рекомендую читати та голосувати за відповідь @ Konstantino, хоча він не надає жодного коду. Вектор ініціалізації (IV) схожий на сіль - його не потрібно зберігати в секреті. Я новачок у GCM і, мабуть, AAD не є обов'язковим і використовується лише в певних обставинах. Встановіть ключ у змінній оточення SECRET_KEY_BASE. Використовуйте щось на зразок KeePass для створення пароля 32 символів. Це рішення моделюється після мого рішення Ruby.

    public static String encrypt(String s) {
        try {
            byte[] input = s.getBytes("UTF-8");
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            // generate IV
            SecureRandom secureRandom = SecureRandom.getInstanceStrong();
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            secureRandom.nextBytes(ivBytes);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes); // 96 bit tag length
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
            // generate AAD
//          byte[] aadBytes = new byte[cipher.getBlockSize()];
//          secureRandom.nextBytes(aadBytes);
//          cipher.updateAAD(aadBytes);
            // encrypt
            byte[] encrypted = cipher.doFinal(input);
            byte[] returnBytes = new byte[ivBytes.length + encrypted.length];
//          byte[] returnBytes = new byte[ivBytes.length + aadBytes.length + encrypted.length];
            System.arraycopy(ivBytes, 0, returnBytes, 0, ivBytes.length);
//          System.arraycopy(aadBytes, 0, returnBytes, ivBytes.length, aadBytes.length);
            System.arraycopy(encrypted, 0, returnBytes, ivBytes.length, encrypted.length);
//          System.arraycopy(encrypted, 0, returnBytes, ivBytes.length+aadBytes.length, encrypted.length);
            String encryptedString = Base64.getEncoder().encodeToString(returnBytes);
            return encryptedString;
        } catch (UnsupportedEncodingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "encrypt()", "Could not encrypt string: " + e.getMessage());
            return null;
        }
    }

    public static String decrypt(String s) {
        if (s == null || s.length() == 0) return "";
        try {
            byte[] encrypted = Base64.getDecoder().decode(s);
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            System.arraycopy(encrypted, 0, ivBytes, 0, ivBytes.length);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
//          cipher.updateAAD(encrypted, ivBytes.length, cipher.getBlockSize());
            byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize(), encrypted.length - cipher.getBlockSize());
//          byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize()*2, encrypted.length - cipher.getBlockSize()*2);
            String decryptedString = new String(decrypted, "UTF-8");
            return decryptedString;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedEncodingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "decrypt()", "Could not decrypt string: " + e.getMessage());
            return null;
        }
    }

Ось приклад:

    String s = "This is a test.";
    String enc = Utils.encrypt(s);
    System.out.println(enc);
    // fQHfYjbD+xAuN5XzH2ojk/EWNeKXUrKRSfx8LU+5dpuKkM/pueCMBjKCZw==
    String dec = Utils.decrypt(enc);
    System.out.println(dec);
    // This is a test.

-4

Ви можете розглянути якийсь автоматизований інструмент для генерації коду шифрування / дешифрування, наприклад. https://www.stringencrypt.com/java-encryption/

Він може генерувати різний код шифрування та дешифрування кожен раз для шифрування рядка або файлу.

Це досить зручно, якщо мова йде про швидке шифрування рядків без використання RSA, AES тощо.

Приклад результатів:

// encrypted with https://www.stringencrypt.com (v1.1.0) [Java]
// szTest = "Encryption in Java!"
String szTest = "\u9E3F\uA60F\uAE07\uB61B\uBE1F\uC62B\uCE2D\uD611" +
                "\uDE03\uE5FF\uEEED\uF699\uFE3D\u071C\u0ED2\u1692" +
                "\u1E06\u26AE\u2EDC";

for (int iatwS = 0, qUJQG = 0; iatwS < 19; iatwS++)
{
        qUJQG = szTest.charAt(iatwS);
        qUJQG ++;
        qUJQG = ((qUJQG << 5) | ( (qUJQG & 0xFFFF) >> 11)) & 0xFFFF;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 6) | (qUJQG << 10)) & 0xFFFF;
        qUJQG ^= iatwS;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 3) | (qUJQG << 13)) & 0xFFFF;
        qUJQG ^= 0xFFFF;
        qUJQG ^= 0xB6EC;
        qUJQG = ((qUJQG << 8) | ( (qUJQG & 0xFFFF) >> 8)) & 0xFFFF;
        qUJQG --;
        qUJQG = (((qUJQG & 0xFFFF) >> 5) | (qUJQG << 11)) & 0xFFFF;
        qUJQG ++;
        qUJQG ^= 0xFFFF;
        qUJQG += iatwS;
        szTest = szTest.substring(0, iatwS) + (char)(qUJQG & 0xFFFF) + szTest.substring(iatwS + 1);
}

System.out.println(szTest);

Ми постійно його використовуємо в нашій компанії.


Це безпека через невідомість і насправді не є безпечною.
Хлоя

Це питання вимагає фактичного сучасного криптовалютного силового шифрування на зразок AES, а не просто обдумування, щоб зробити струни важче статично витягнутими. Це, здається, навіть не підтримує жодного стану між символами, настільки його сприйнятливим до аналізу частоти. ( Шифр заміни одноабетки , за винятком кодових точок UTF-16 замість латинського алфавіту. Але якщо ви використовуєте його в англійському тексті ASCII, ви отримуєте лише кілька унікальних 16-бітових знаків символів, якщо я це неправильно не читаю)
Пітер корд

-4
String s1="arshad"; 
char[] s2=s1.toCharArray(); 
int s3= s2.length; 

  System.out.println(s3);
 int i=0; 

// for(int j=0;j<s3;j++) 
// System.out.println(s2[j]); 

for(i=0;i<((s3)/2);i++) { 

char z,f=10; 
z=(char) (s2[i] * f); 
s2[i]=s2[(s3-1)-i]; 
s2[(s3-1)-i]=z; 

String b=new String(s2);

 print(b);  }

Формально він шифрує дані у нечитабельному форматі. Для розшифрування використовуйте той самий код. І змінити s [i] * f на s [I] / f.
Аршад шейк

Це безпека через невідомість і насправді не є безпечною.
Хлоя

-5
public static String encryptParams(String myTextInput) {

        String myKey = "40674244454045cb9a70040a30e1c007";
        String myVector = "@1B2c3D4e5F6g7H8";

        String encData = "";

        try{
            JavaEncryprtionUtil encUtil = new JavaEncryprtionUtil();
            encData = Base64.encodeToString(encUtil.encrypt(myTextInput.getBytes("UTF-8"), myKey.getBytes("UTF-8"), myVector.getBytes("UTF-8")),Base64.DEFAULT);
            System.out.println(encData);
        }catch(NoSuchAlgorithmException ex){
            ex.printStackTrace();
        }catch(NoSuchPaddingException ex){
            ex.printStackTrace();
        }catch(InvalidKeyException ex){
            ex.printStackTrace();
        }catch(InvalidAlgorithmParameterException ex){
            ex.printStackTrace();
        }catch(IllegalBlockSizeException ex){
            ex.printStackTrace();
        }catch(BadPaddingException ex){
            ex.printStackTrace();
        }catch(UnsupportedEncodingException ex){
            ex.printStackTrace();
        }

        return encData;
    }

1
JavaEncryprtionUtil є частиною API JDK? якщо ні, то вам слід вказати назву бібліотеки.
Маленький студент Ферма

4
Неможливо знайти цей клас. Відчуває, що відповідь складається.
james.garriss
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.