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

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

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

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

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

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

170
ante.sabo 30 Июл 2009 в 12:13

16 ответов

Лучший ответ

Предупреждение

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

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

На что указал Мусса


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

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
24
Community 20 Июн 2020 в 09:12

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

Объект-шифровальщик

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

}


}
13
sherif 18 Сен 2010 в 18:59

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

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);
10
Saptarshi Basu 11 Дек 2019 в 23:49

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

9
Community 20 Июн 2020 в 09:12

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

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

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

5
yegor256 26 Дек 2012 в 15:59

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

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

Генерируется случайный вектор инициализации (IV), который будет добавлен к зашифрованному тексту. Кроме того, статический байт 0x01 добавляется к первому байту как «версия».

Все сообщение переходит в код аутентификации сообщения (MAC), сгенерированный AES/GCM.

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

package ch.n1b.tcrypt.utils;

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

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

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

    // https://crypto.stackexchange.com/questions/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

5
trichner 8 Дек 2018 в 03:40

Вот моя реализация с сайта 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;
    // }
}
4
mkobit 3 Авг 2015 в 18:17

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

3
Artjom B. 6 Янв 2015 в 21:26

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

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

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

JCERefGuide

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

2
Markus Lausberg 30 Июл 2009 в 08:28

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

    }
}
2
viveknaskar 6 Сен 2018 в 15:10

Вот решение для копирования / вставки. Я также рекомендую прочитать и проголосовать за @ ответ Константино, даже если он не предоставляет никакого кода. Вектор инициализации (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.
0
Chloe 9 Мар 2019 в 06:41

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

Мы постоянно им пользуемся в нашей компании.

-4
Bartosz Wójcik 26 Окт 2015 в 18:51
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;
    }
-5
Community 24 Апр 2014 в 19:36
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);  }
-5
Cardinal System 1 Сен 2017 в 18:38

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

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

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

< Сильный > Предисловие

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

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

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

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

Что касается алгоритмов блочного шифрования, доступных сегодня, убедитесь, что НИКОГДА , я повторяю, НИКОГДА не используйте 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 < / a>. Пример Linux Penguin

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

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

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

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

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

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

Создание случайного IV

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

Примечание. SHA1 не работает, но я не мог найти, как правильно реализовать SHA256 в этом варианте использования, поэтому, если кто-то захочет взломать это и обновить, это будет здорово! Кроме того, атаки SHA1 по-прежнему являются нетрадиционными, поскольку для взлома огромного кластера может потребоваться несколько лет. Подробности здесь.

Реализация CTR

Для режима CTR заполнение не требуется.

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

Реализация CBC

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

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

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

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

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

182
Adam Lee 30 Окт 2019 в 16:06

Я бы рекомендовал использовать стандартный симметричный шифр, который широко доступен, например 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);
110
KyleMit 5 Ноя 2013 в 16:34