Шифрование / дешифрование сообщений в java

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

import java.security.MessageDigest;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class Cryptography {

  public static final int SECRET_KEY_ITERATIONS = 45928;
  public static final int SALT_ITERATIONS = 11879;
  public static final int IV_ITERATIONS = 13275;
  public static final int KEY_LENGTH = 256;

  private static Cipher cipher;
  private static MessageDigest sha256;
  private static SecretKeyFactory secretKeyFactory;

  static {
    try {
      cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
      sha256 = MessageDigest.getInstance("SHA-256");
      secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public static String encryptText(String password, String plaintext) throws Exception {
    byte[] passwordBytes = password.getBytes();
    SecretKey secretKey = getKeyFromPassword(password, getSaltFromPassword(passwordBytes));
    IvParameterSpec ivParameterSpec = getIvFromPassword(passwordBytes);
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
    return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes()));
  }

  public static String decryptText(String password, String ciphertext) throws Exception {
    byte[] passwordBytes = password.getBytes();
    SecretKey secretKey = getKeyFromPassword(password, getSaltFromPassword(passwordBytes));
    IvParameterSpec ivParameterSpec = getIvFromPassword(passwordBytes);
    cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
    return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)));
  }

  private static SecretKey getKeyFromPassword(String password, byte[] salt)
      throws InvalidKeySpecException {
    PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, SECRET_KEY_ITERATIONS, KEY_LENGTH);
    byte[] bytes = secretKeyFactory.generateSecret(spec).getEncoded();
    return new SecretKeySpec(bytes, "AES");
  }

  private static byte[] getSaltFromPassword(byte[] passwordByteArray) {
    byte[] bytes = sha256.digest(passwordByteArray);
    for (int i = 0; i < SALT_ITERATIONS; i++) {
      bytes = sha256.digest(bytes);
    }
    return bytes;
  }

  private static IvParameterSpec getIvFromPassword(byte[] passwordByteArray) {
    byte[] bytes = sha256.digest(passwordByteArray);
    for (int i = 0; i < IV_ITERATIONS; i++) {
      bytes = sha256.digest(bytes);
    }
    byte[] ivBuffer = new byte[16];
    System.arraycopy(bytes, 0, ivBuffer, 0, 16);
    return new IvParameterSpec(ivBuffer);
  }
}

Это безопасный способ шифрования / дешифрования или у него есть серьезные проблемы?

2 ответа
2

  private static byte[] getSaltFromPassword(byte[] passwordByteArray) {
    byte[] bytes = sha256.digest(passwordByteArray);
    for (int i = 0; i < SALT_ITERATIONS; i++) {
      bytes = sha256.digest(bytes);
    }
    return bytes;
  }

эта часть – НЕТ НЕТ. соль должна быть случайной. пожалуйста, обратитесь к этому
старый вопрос
Соль должна быть независимой и случайной, иначе она бесполезна.

    Да, это имеет [serious issues].

    Например, поскольку соль не уникальна, код полностью детерминирован. Это означает, что вы напрямую теряете информацию, если шифруете строки одним и тем же паролем (аналогично тому, как ECB утекает информацию).

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


    Я проведу обзор кода с комментариями под кодом.

    public static final int SECRET_KEY_ITERATIONS = 45928;
    public static final int SALT_ITERATIONS = 11879;
    public static final int IV_ITERATIONS = 13275;
    

    Пароли нуждаются в итерациях, а ключи – нет. Может быть KEY_DERIVATION_ITERATIONS было бы приемлемо.

    Соли можно использовать в итерациях, но не существует такой вещи, как солевые итерации.

    IV не имеют ничего общего с итерациями.

    private static Cipher cipher;
    

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

    static {
    

    Старайтесь избегать статических блоков, просто используйте класс без static.

    public static String encryptText(String password, String plaintext) throws Exception {
    

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

    private static byte[] getSaltFromPassword(byte[] passwordByteArray) {
    

    Соль не может быть получена из пароля, поскольку она используется для создания уникального ключа, полученного из пароля; в этом весь смысл. Это просто должно быть случайным. В противном случае нет смысла использовать соль.

    private static IvParameterSpec getIvFromPassword(byte[] passwordByteArray) {
    

    Аналогичная проблема, значение ключа / IV должно быть уникальным. Если бы соль была случайной и хранилась с зашифрованным текстом, вы даже могли бы использовать статический IV. Несколько приятнее использовать случайный IV, но тогда вам придется сохранить и его.


    Обратите внимание, что вы изобретаете колесо, если используете итерации SHA-256; вы также можете повторно использовать PBKDF2. Но в этом случае это все равно не требуется.

      Добавить комментарий

      Ваш адрес email не будет опубликован. Обязательные поля помечены *