Я пытаюсь реализовать шифрование / дешифрование сообщений с паролем в 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 ответа
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. Но в этом случае это все равно не требуется.