Как зашифровать строку в Java


150

Что мне нужно, так это зашифровать строку, которая будет отображаться в 2D штрих-коде (PDF-417), чтобы, когда у кого-то появилась идея отсканировать, ничего не читалось.

Другие требования:

  • не должно быть сложным
  • он не должен состоять из RSA, инфраструктуры PKI, пар ключей и т. д.

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

Вероятно, эти компании могут использовать разные технологии, поэтому было бы хорошо придерживаться какого-то стандарта, который не привязан к какой-либо специальной платформе или технологии.

Что ты предлагаешь? Есть ли какой-то класс Java, выполняющий encrypt()и decrypt()без особых сложностей в достижении высоких стандартов безопасности?



Предупреждение . Во многих ответах ниже показан тот или иной метод для выполнения любого вида криптографии на Java. Ответы могут не отражать надлежащую криптографическую практику и не могут быть хорошо изучены; нет такой вещи как безопасность копирования / вставки . Ответы должны по крайней мере принимать во внимание преобразование строк. Фактический вопрос с включенным 2D штрих-кодом слишком широк и должен требовать конкретного решения для клиента.
Мартен Бодьюс

Ответы:


156

Это первая страница, которая появляется через Google, и уязвимости безопасности во всех реализациях заставляют меня съеживаться, поэтому я публикую это, чтобы добавить информацию, касающуюся шифрования для других, как это было 7 лет от первоначального поста. Я получил степень магистра в области вычислительной техники и провел много времени, изучая и изучая криптографию, поэтому я бросаю два цента, чтобы сделать Интернет более безопасным.

Кроме того, обратите внимание, что многие реализации могут быть безопасными для данной ситуации, но зачем использовать их и потенциально случайно совершить ошибку? Используйте самые сильные инструменты, которые у вас есть, если у вас нет особых причин не делать этого. В целом, я настоятельно советую использовать библиотеку и держаться подальше от мельчайших деталей, если можете.

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

предисловие

Ниже я опишу основы безопасной симметричной криптографии и укажу на распространенные ошибки, которые я вижу в Интернете, когда люди самостоятельно внедряют криптографию со стандартной библиотекой Java. Если вы хотите просто пропустить все детали, перейдите в новую библиотеку Google. Импортируйте Tink в ваш проект и используйте режим AES-GCM для всех ваших шифрований, и вы будете в безопасности.

Теперь, если вы хотите узнать подробности о том, как зашифровать в Java, читайте дальше :)

Блочные шифры

Первым делом сначала нужно выбрать симметричный ключ Block Cipher. Блочный шифр - это компьютерная функция / программа, используемая для создания псевдослучайности. Псевдослучайность - это ложная случайность, которую ни один компьютер, кроме Квантового Компьютера, не сможет отличить от реальной случайности. Блочный шифр подобен строительному блоку для криптографии, и при использовании с различными режимами или схемами мы можем создавать шифрование.

Что касается алгоритмов блочного шифрования, доступных сегодня, убедитесь, что НИКОГДА , я повторяю, НИКОГДА не используйте DES , я бы даже сказал, НИКОГДА не используйте 3DES . Единственный блочный шифр, который даже в выпуске NSA Сноудена смог подтвердить, что он действительно настолько близок к псевдослучайному, насколько это возможно, - это AES 256 . Там также существует AES 128; Разница в том, что AES 256 работает в 256-битных блоках, а AES 128 работает в 128 блоках. В целом, AES 128 считается безопасным, хотя некоторые недостатки были обнаружены, но 256 настолько же надежен, насколько это возможно.

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

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

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

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

Режим ЕЦБ

Режимы шифрования, которые вы чаще всего видите в Интернете, следующие:

ECB CTR, CBC, GCM

Существуют и другие способы, помимо перечисленных, и исследователи всегда работают над новыми способами, чтобы улучшить существующие проблемы.

Теперь давайте перейдем к реализации и что является безопасным. НИКОГДА не используйте ECB, это плохо для сокрытия повторяющихся данных, как показал знаменитый пингвин Linux .Пример Linux Penguin

При реализации в Java обратите внимание, что если вы используете следующий код, режим ECB устанавливается по умолчанию:

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

... ОПАСНОСТЬ ЭТО УЯЗВИМОСТЬ! и, к сожалению, это видно по всему StackOverflow и онлайн в учебниках и примерах.

Одноразовые и IVs

В ответ на проблему, обнаруженную в режиме ECB, были созданы существительные, также известные как IV. Идея состоит в том, что мы генерируем новую случайную переменную и прикрепляем ее к каждому шифрованию, чтобы при шифровании двух одинаковых сообщений они получались разными. Красота этого заключается в том, что IV или nonce - это общедоступное знание. Это означает, что злоумышленник может получить доступ к этому, но пока у него нет вашего ключа, он ничего не может сделать с этим знанием.

Общие проблемы, которые я увижу, состоят в том, что люди будут устанавливать 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

Если вы решили реализовать режим CBC, сделайте это с PKCS7Padding следующим образом:

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

Уязвимость CBC и CTR и почему вы должны использовать GCM

Хотя некоторые другие режимы, такие как CBC и CTR, являются безопасными, они сталкиваются с проблемой, когда злоумышленник может перевернуть зашифрованные данные, изменив их значение при расшифровке. Допустим, вы зашифровали воображаемое банковское сообщение «Sell 100», ваше зашифрованное сообщение выглядит так: «eu23ng», злоумышленник меняет один бит на «eu53ng», и внезапно, когда расшифровывает ваше сообщение, оно читается как «Sell 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

Один важный момент, на который следует обратить внимание, это то, что вы знаете, что ваш android-код работает в обратном порядке, и в большинстве случаев большинство java-кодов тоже. Это означает, что если вы храните пароль в виде обычного текста в вашем коде. Хакер может легко получить его. Обычно для такого типа шифрования вы хотите использовать асимметричную криптографию и так далее. Это выходит за рамки этого поста, поэтому я не буду вдаваться в подробности.

Интересное чтение с 2013 года : указывает, что 88% реализаций Crypto в Android было сделано неправильно.

Последние мысли

Еще раз я бы рекомендовал избегать реализации библиотеки java для crypto напрямую и использовать Google Tink , это избавит вас от головной боли, поскольку они действительно проделали хорошую работу по реализации всех алгоритмов должным образом. И даже тогда убедитесь, что вы проверили проблемы, поднятые на GitHub Tink, уязвимости всплывают здесь и там.

Если у вас есть какие-либо вопросы или отзывы, не стесняйтесь комментировать! Безопасность всегда меняется, и вам нужно делать все возможное, чтобы не отставать от нее :)


15
Это самая чистая вещь, которую я когда-либо видел.
Сераф

1
@SabirKhan Это может быть поводом для беспокойства, но основные алгоритмы все еще не сломаны, поэтому я не буду слишком беспокоиться об этом. В случае, если вы не доверяете ему, также посетите github.com/google/keyczar , он был разработан командой безопасности googles.
Константино Спаракис

1
@KonstantinoSparakis: Если я не неправильно истолковал документацию для BasicTextEncryptor и StrongTextEncryptor Jasypt, эти классы используют DES и 3DES для шифрования, что вы и говорите читателям не использовать. IMO, вы должны заменить приведенные примеры кода на тот, который использует StandardPBEStringEncryptor Jasypt и вручную определяет используемый алгоритм AES.
xpages-noob

1
@ xpages-noob Я обновил пост. Я на самом деле нашел Google Tink, который является новейшей поддерживаемой библиотекой для шифрования, так что вы должны проверить это!
Константино Спаракис

2
Размер блока AES составляет 128 бит. В AES 256 размер ключа составляет 256 бит. Аналогично, AES 192 и AES 128. Кроме того, начиная с Java 8, getInstanceStrong()метод Cipherпредпочтительнее, чем SHA1PRNG
Саптарши Басу

110

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

Давайте предположим, что байты для шифрования находятся в

byte[] input;

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

byte[] keyBytes;
byte[] ivBytes;

Теперь вы можете инициализировать шифр для выбранного вами алгоритма:

// 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 зашифрованные сообщения и вы получите ключ. Так просто!

Указанный Мусой


Я использую Sun Base64Encoder / Decoder, который можно найти в Sun JRE, чтобы избежать еще одного 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
Описанный вами механизм шифрования ОЧЕНЬ ОПАСЕН, если используется более одного раза. Вот почему он называется «Одноразовый блокнот». Секретный ключ может быть легко восстановлен злоумышленником с помощью двух зашифрованных сообщений. 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();

}


}

разместил связанный вопрос здесь !
user2023507

Не должно ли быть так, что передача различных ключей во время шифрования и дешифрования не должна возвращать текст обратно? Это, кажется, не происходит здесь. PS: я использую различные объекты этого класса для выполнения этого теста.
instanceOfObject

6

Обновление от 12 декабря 2019 года

В отличие от некоторых других режимов, таких как CBC, режим GCM не требует, чтобы IV был непредсказуемым. Единственное требование заключается в том, что IV должен быть уникальным для каждого вызова с данным ключом. Если это повторяется один раз для данного ключа, безопасность может быть поставлена ​​под угрозу. Простой способ добиться этого - использовать случайный IV от сильного генератора псевдослучайных чисел, как показано ниже.

Использование последовательности или метки времени в качестве IV также возможно, но это может быть не так тривиально, как может показаться. Например, если система неправильно отслеживает последовательности, уже использованные в качестве IV, в постоянном хранилище, вызов может повторить IV после перезагрузки системы. Точно так же нет идеальных часов. Компьютерные часы перенастраиваются и т. Д.

Кроме того, ключ должен поворачиваться после каждых 2 ^ 32 вызовов. Для получения дополнительной информации о требованиях IV, обратитесь к этому ответу и рекомендациям NIST .


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

  1. Алгоритм шифрования : блочный шифр AES с 256-битным ключом считается достаточно безопасным. Чтобы зашифровать полное сообщение, необходимо выбрать режим. Рекомендуется использовать аутентифицированное шифрование (которое обеспечивает как конфиденциальность, так и целостность). GCM, CCM и EAX являются наиболее часто используемыми аутентифицированными режимами шифрования. GCM обычно предпочтительнее, и он хорошо работает в архитектурах Intel, которые предоставляют специальные инструкции для GCM. Все эти три режима являются режимами на основе CTR (счетчик) и, следовательно, они не требуют заполнения. В результате они не уязвимы для атак, связанных с отступами.

  2. Вектор инициализации (IV) требуется для GCM. 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.

Как указывалось ранее в ответе, обработка конфиденциальной информации в a String, как правило, не очень хорошая идея, поскольку Stringона неизменна, и поэтому мы не можем ее очистить после использования. И, как мы знаем, даже когда a Stringне имеет сильной ссылки, сборщик мусора не сразу бросается удалять его из кучи. Таким образом, Stringпродолжает оставаться в памяти в течение неизвестного периода времени, даже если это не доступно для программы. Проблема в том, что дамп кучи в течение этого периода времени может раскрыть конфиденциальную информацию. Поэтому всегда лучше обрабатывать всю конфиденциальную информацию в байтовом массиве или массиве символов, а затем заполнять массив нулями, как только их цель будет достигнута.

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

А 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. Как таковой он не может ответить на вопрос .
Мартен Бодьюс

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используется повторно, это безопасно и называется one-time-pad. В случаях input.length > secret.lengthэто вариант шифра Vigenère и считается очень слабым.
Трихнер

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 Castle. Выучить больше.

Повышенная безопасность паролей ваших пользователей. Выучить больше.

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

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

Полностью поточно-ориентированный

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

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

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

Hibernate 3 и 4 дополнительная интеграция для сохранения полей ваших сопоставленных объектов в зашифрованном виде. Шифрование полей определяется в файлах отображения Hibernate, и оно остается прозрачным для остальной части приложения (полезно для конфиденциальных личных данных, баз данных со многими пользователями с поддержкой чтения ...). Шифрование текстов, двоичных файлов, чисел, логических значений, дат ... Подробнее.

Легко интегрируется в приложение Spring со специальными функциями интеграции для Spring 2, Spring 3.0 и Spring 3.1. Все дайджесты и шифраторы в jasypt предназначены для легкого использования (создания экземпляров, внедрения зависимостей ...) из Spring. А благодаря тому, что они поточно-ориентированы, их можно использовать без проблем синхронизации в одно-ориентированной среде, такой как Spring. Узнать больше: Spring 2, Spring 3.0, Spring 3.1.

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

Предоставляет расширенные функциональные возможности для шифрования всех или части файлов конфигурации приложения, включая конфиденциальную информацию, такую ​​как пароли базы данных. Беспроблемная интеграция зашифрованной конфигурации в простые, основанные на Spring и / или Hibernate-приложения. Выучить больше.

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

Интегрируется в Apache Wicket для более надежного шифрования URL-адресов в ваших защищенных приложениях.

Подробные руководства и документация по Javadoc, позволяющие разработчикам лучше понять, что они на самом деле делают со своими данными.

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

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


1
Но что обеспечивает безопасность Jasypt? Я не могу понять это с их сайта. Это неразличимо при атаках с открытым текстом? Целостность? Конфиденциальность?
trichner

4

Вот моя реализация от meta64.com как Spring Singleton. Если вы хотите создать экземпляр Ciper для каждого вызова, который также будет работать, а затем вы можете удалить «синхронизированные» вызовы, но будьте осторожны, «Cipher» не является поточно-ориентированным.

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
Константино Спаракис

Спасибо за предложение Konstantinto, я погуглил его и нашел некоторый код, который использует «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 байт, но для шифрования строки из «предоставленного пользователем» пароля страница оракула (ссылка выше) также показывает способ сделать это.

1
Таким образом, проблема с ЕЦБ заключается в том, что он чрезвычайно уязвим для частотного анализа. Вот знаменитый пример Linux-пингвина, blog.filippo.io/the-ecb-penguin. Посмотрите, как, несмотря на то, что изображение зашифровано, вы все равно можете сказать, что это пингвин. Я пошел дальше и написал свои мысли по этому вопросу ниже :) stackoverflow.com/a/43779197/2607972
Константино Спаракис

4

Здесь простое решение только с java.*и javax.crypto.*зависимостями для шифрования байтов, обеспечивающее конфиденциальность и целостность . Он должен быть неотличим при выбранной атаке открытым текстом для коротких сообщений порядка килобайт.

Он использует AESв GCMрежиме без заполнения, ключ 128bit получается путем 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 ваш код работал отлично с первого раза. Превосходно!!
Моркус

Мне нравится эта попытка. Тем не менее ... Соль должна быть случайной, а не статичной. Итерации, вероятно, также не должны быть статичными. GCM уже включает IV в расчет тега. Это не содержит номер версии, хотя. Вы не должны указывать провайдера для переносимости, "SunJCE" будет по умолчанию на платформах, которые его поддерживают. Этот код не содержит какой-либо обработки строки сообщения, которая требуется для этого конкретного вопроса .
Мартен Бодьюс

Хорошо, я убрал это немного больше и добавил запрашиваемое encryptStringи decryptString:)
trichner

Это сработало очень хорошо; Ты за код. Следует отметить, что этот код требует API 19 (Kit Kat) или выше для правильной работы.
PGMacDesign

3

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


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

2

Вот несколько ссылок, которые вы можете прочитать о том, что поддерживает Java

Шифрование / дешифрование потока данных.

Этот пример демонстрирует, как зашифровать (используя симметричный алгоритм шифрования, такой как AES, Blowfish, RC2, 3DES и т. Д.) Большой объем данных. Данные передаются частями в один из методов шифрования: EncryptBytes, EncryptString, EncryptBytesENC или EncryptStringENC. (Имя метода указывает тип ввода (строковый или байтовый массив) и тип возвращаемого значения (закодированная строка или байтовый массив). Свойства FirstChunk и LastChunk используются для указания того, является ли чанк первым, средним или последним в потоке для шифрования. По умолчанию, FirstChunk и LastChunk равны true - это означает, что переданные данные являются всей суммой.

JCERefGuide

Примеры шифрования Java


Да, криптография поддерживается Java. Шифрование потока - это не то, о чем просили.
Мартен Бодьюс

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. Кроме того, обработка ключа и сообщений в строке не является безопасной. Они задержатся в пуле
Струн

2
Цените комментарий. Это был простой пример методов шифрования и дешифрования Java, как просил пользователь. Вопрос был задан около 9 лет назад, и на него был дан ответ. Спасибо.
Вивекнаскар

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 является частью JDK API? если нет, вы должны указать название библиотеки.
Маленькая ученица Ферма

4
Не могу найти этот класс. Чувствуется, что ответ придуман.
james.garriss
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.