Как правильно воссоздать SecretKey из строки
Я пытаюсь сделать приложение шифрования-дешифрования. У меня есть два класса - один с функциями для генерации ключа, шифрования и дешифрования, второй для JavaFX GUI. В классе GUI у меня есть 4 текстовые области: 1-й для записи текста для шифрования, 2-й для зашифрованного текста, 3-й для ключа (String encodedKey = Base64.getEncoder().encodeToString(klucz.getEncoded());
) и 4-й для расшифрованного текста.
Проблема в том, что я не могу расшифровать текст. Я пытаюсь воссоздать SecretKey так:
String encodedKey = textAreaKey.getText();
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
SecretKey klucz = new SecretKeySpec(decodedKey, "DESede");
Когда я шифрую ключ выглядит так: com.sun.crypto.provider.DESedeKey@4f964d80
и когда я пытаюсь воссоздать его: javax.crypto.spec.SecretKeySpec@4f964d80
и я получаю javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher
Вот мой первый класс:
public class Encryption {
public static SecretKey generateKey() throws NoSuchAlgorithmException {
Security.addProvider(new com.sun.crypto.provider.SunJCE());
KeyGenerator keygen = KeyGenerator.getInstance("DESede");
keygen.init(168);
SecretKey klucz = keygen.generateKey();
return klucz;
}
static byte[] encrypt(byte[] plainTextByte, SecretKey klucz)
throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, klucz);
byte[] encryptedBytes = cipher.doFinal(plainTextByte);
return encryptedBytes;
}
static byte[] decrypt(byte[] encryptedBytes, SecretKey klucz)
throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, klucz);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return decryptedBytes;
}
}
редактировать
btnEncrypt.setOnAction((ActionEvent event) -> {
try {
String plainText = textAreaToEncrypt.getText();
SecretKey klucz = Encryption.generateKey();
byte[] plainTextByte = plainText.getBytes();
byte[] encryptedBytes = Encryption.encrypt(plainTextByte, klucz);
String encryptedText = Base64.getEncoder().encodeToString(encryptedBytes);
textAreaEncryptedText.setText(encryptedText);
byte[] byteKey = klucz.getEncoded();
String stringKey = Base64.getEncoder().encodeToString(byteKey);
textAreaKey.setTextstringKey
} catch (Exception ex) {
ex.printStackTrace();
}
});
btnDecrypt.setOnAction((ActionEvent event) -> {
try {
String stringKey = textAreaKey.getText();
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
SecretKey klucz2 = new SecretKeySpec(decodedKey, "DESede");
String encryptedText = textAreaEncryptedText.getText();
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText.getBytes());
byte[] decryptedBytes = Encryption.decrypt(encryptedBytes, klucz2;
String decryptedText = Base64.getEncoder().encodeToString(decryptedBytes);
textAreaDecryptedText.setText(decryptedText);
} catch (Exception ex) {
ex.printStackTrace();
}
});
2 ответа
Одна из ваших проблем здесь:
String encryptedText = new String(encryptedBytes, "UTF8");
Как правило, многие последовательности байтов в зашифрованном тексте не являются допустимыми символами в кодировке UTF-8. Когда вы пытаетесь создать String
эта искаженная последовательность будет заменена на "символ замены", а затем информация из зашифрованного текста будет безвозвратно потеряна. Когда вы конвертируете String
обратно в байты и попытаться расшифровать его, поврежденный текст шифра вызывает ошибку.
Если вам нужно представить зашифрованный текст в виде строки символов, используйте кодировку base-64, так же, как вы делаете это для ключа.
Другая главная проблема заключается в том, что вы не указываете полное преобразование. Вы должны явно указать "режим" и "заполнение" шифра, например "DESede/ECB/PKCS5Padding".
Правильный режим будет зависеть от вашего назначения. ECB, как правило, небезопасен, но более безопасные режимы добавляют немного сложности, которая может выходить за рамки вашего назначения. Изучите ваши инструкции и уточните требования с вашим учителем, если это необходимо.
Есть два основных вопроса:
Вы не должны использовать введенный пользователем пароль в качестве ключа (между ними есть разница). Ключ должен иметь определенный размер в зависимости от шифра (16 или 24 байта для 3des)
Direct 3DES (DESede) - это блочный шифр, шифрующий 8 байтов одновременно. Чтобы зашифровать несколько блоков, есть несколько способов, определяющих, как это сделать правильно. Это вызывает режим блочного шифрования.
Для правильного шифрования нужно позаботиться еще о нескольких вещах
Создание ключа из пароля
Предположим, вы хотите использовать DESede (3des). Ключ должен иметь фиксированный размер - 16 или 24 байта. Чтобы правильно сгенерировать ключ из пароля, вы должны использовать PBKDF. Некоторые люди чувствительны к "необходимо использовать", однако пренебрежение этим шагом действительно ставит под угрозу безопасность шифрования, в основном с использованием введенных пользователем паролей.
Для 3DES вы можете использовать:
int keySize = 16*8;
int iterations = 800000;
char[] password = "password".toCharArray();
SecureRandom random = new SecureRandom();
byte[] salt = random.generateSeed(8);
SecretKeyFactory secKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(password, salt, iterations, keySize);
SecretKey pbeSecretKey = secKeyFactory.generateSecret(spec);
SecretKey desSecret = new SecretKeySpec(pbeSecretKey.getEncoded(), "DESede");
// iv needs to have block size
// we will use the salt for simplification
IvParameterSpec ivParam = new IvParameterSpec(salt);
Cipher cipher = Cipher.getInstance("DESEde/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, desSecret, ivParam);
System.out.println("salt: "+Base64.getEncoder().encodeToString(salt));
System.out.println(cipher.getIV().length+" iv: "+Base64.getEncoder().encodeToString(cipher.getIV()));
byte[] ciphertext = cipher.doFinal("plaintext input".getBytes());
System.out.println("encrypted: "+Base64.getEncoder().encodeToString(ciphertext));
если вы можете гарантировать, что ваш пароль имеет хорошую энтропию (достаточно длинный и случайный), вы можете использовать простой хеш
MessageDigest dgst = MessageDigest.getInstance("sha-1");
byte[] hash = dgst.digest("some long, complex and random password".getBytes());
byte[] keyBytes = new byte[keySize/8];
System.arraycopy(hash, 0, keyBytes, 0, keySize/8);
SecretKey desSecret = new SecretKeySpec(keyBytes, "DESede");
Соль служит для рандомизации выхода и должна использоваться.
Вывод шифрования должен быть salt | cipthertext | tag
(не обязательно в этом порядке, но вам потребуется все это для правильного шифрования).
Чтобы расшифровать вывод, вам нужно разделить вывод на соль, зашифрованный текст и тег.
Я вижу нулевые векторы (статическая соль или iv) очень часто в примерах из Stackru, но во многих случаях это может привести к тому, что сломанные ключи или открытый текст будут раскрыты.
Вектор инициализации iv необходим для режимов цепочки блоков (шифрование более длинного ввода, чем один блок), мы могли бы также использовать соль из ключа, когда он имеет такой же размер (в нашем случае 8 байт). Для действительно безопасного решения соль пароля должна быть длиннее.
Тег является тегом аутентификации, чтобы никто не манипулировал с зашифрованным текстом. Вы можете использовать HMAC открытого текста или зашифрованного текста. Важно, чтобы вы использовали другой ключ для HMAC, чем для шифрования. Однако - я верю, что в вашем случае ваша домашняя работа будет в порядке, даже без тега hmac