Как зашифровать строку в Java - PullRequest
127 голосов
/ 30 июля 2009

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

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

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

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

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

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

Ответы [ 16 ]

111 голосов
/ 30 июля 2009

Я бы рекомендовал использовать какой-нибудь стандартный симметричный шифр, который широко доступен, например 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);
100 голосов
/ 04 мая 2017

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

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

ОБНОВЛЕНИЕ 4/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 Mode

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

ECB CTR, CBC, GCM

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

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

При реализации в 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 , это избавит вас от головной боли, поскольку они действительно проделали хорошую работу по реализации всех алгоритмов должным образом. И даже после этого убедитесь, что вы проверили проблемы, поднятые на Tink GitHub, уязвимости всплывают тут и там.

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

20 голосов
/ 31 июля 2009

Внимание

Не используйте это как измерение безопасности.

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

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


Я использую Sun Base64Encoder / Decoder, который можно найти в 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
10 голосов
/ 18 сентября 2010

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

объект crypter

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();

}


}
5 голосов
/ 09 января 2017

Вы можете использовать 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-сервере пароль, который будет использоваться для шифрования. Это позволяет вам удовлетворить ваши потребности в области безопасности.

5 голосов
/ 03 августа 2015

Вот моя реализация от 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;
    // }
}
5 голосов
/ 26 декабря 2012

Как насчет этого:

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;
}

Хорошо работает для меня и довольно компактен.

4 голосов
/ 26 октября 2018

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

Однако, при всем этом знании, если мы все же окажемся в ситуации, когда конфиденциальная информация для шифрования находится в 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);
3 голосов
/ 07 января 2015

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

2 голосов
/ 30 июля 2009

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

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

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

JCERefGuide

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

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