Java AES та використання мого власного ключа


88

Я хочу зашифрувати рядок за допомогою AES за допомогою власного ключа. Але у мене проблеми з бітовою довжиною ключа. Чи можете ви переглянути мій код і побачити, що мені потрібно виправити / змінити.

public static void main(String[] args) throws Exception {
    String username = "bob@google.org";
    String password = "Password1";
    String secretID = "BlahBlahBlah";
    String SALT2 = "deliciously salty";

    // Get the Key
    byte[] key = (SALT2 + username + password).getBytes();
    System.out.println((SALT2 + username + password).getBytes().length);

    // Need to pad key for AES
    // TODO: Best way?

    // Generate the secret key specs.
    SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

    // Instantiate the cipher
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);

    byte[] encrypted = cipher.doFinal((secrectID).getBytes());
    System.out.println("encrypted string: " + asHex(encrypted));

    cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
    byte[] original = cipher.doFinal(encrypted);
    String originalString = new String(original);
    System.out.println("Original string: " + originalString + "\nOriginal string (Hex): " + asHex(original));
}

Зараз я отримую виняток " Недійсна довжина ключа AES: 86 байт ". Чи потрібно забивати ключ? Як мені це зробити?

Також чи потрібно мені щось встановлювати для ЄЦБ чи CBC?

Дякую



16
Ха-ха, смішно. У мене насправді є випадкова сіль, але я очистив свій код, щоб зробити своє питання більш зрозумілим. Ось чому змінна називається SALT2. Але хороший довідник для інших, хто стикається з цією ж проблемою і любить копіювати / вставляти код.
Берні Перес

Відповіді:


125

Редагувати:

Як написано в коментарях, старий кодекс не є "найкращою практикою". Ви повинні використовувати алгоритм генерації ключів, такий як PBKDF2, з великим числом ітерацій. Ви також повинні використовувати хоча б частково нестатичну (що означає для кожної "ідентичності" ексклюзивну) сіль. Якщо можливо, випадковим чином генерується і зберігається разом із зашифрованим текстом.

    SecureRandom sr = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[16];
    sr.nextBytes(salt);

    PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 1000, 128 * 8);
    SecretKey key = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec);
    Cipher aes = Cipher.getInstance("AES");
    aes.init(Cipher.ENCRYPT_MODE, key);

===========

Стара відповідь

Вам слід використовувати SHA-1, щоб генерувати хеш із вашого ключа і обрізати результат до 128 біт (16 байт).

Крім того, не генеруйте байтові масиви з рядків через getBytes (), він використовує стандартну кодировку платформи. Тож пароль "blaöä" призводить до різних байтових масивів на різних платформах.

byte[] key = (SALT2 + username + password).getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit

SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

Редагувати: Якщо вам потрібні 256 біт як ключові розміри, вам потрібно завантажити посилання для завантаження файлів Oracle з необмеженою силою юрисдикції Java Cryptography Extension (JCE) , використовуйте SHA-256 як хеш і видаліть рядок Arrays.copyOf . "ECB" - це режим шифру за замовчуванням, а "PKCS5Padding" - заповнення за замовчуванням. Ви можете використовувати різні режими шифрування та режими заповнення через рядок Cipher.getInstance, використовуючи такий формат: "Шифр / Режим / Заповнення"

Для AES із використанням CTS та PKCS5Padding рядок: "AES / CTS / PKCS5Padding"


Це спрацює, але при хешуванні мого пароля використовуються лише перші кілька бітів. Немає кращого способу це зробити?
Берні Перес

4
Немає кращого способу згенерувати ключ, оскільки AES потребує ключ 128/192/256 біт. Якщо ви не хеш-ключ і обрізати лише вхід, він буде використовувати лише перші 16/24/32 байт. Отже, генерування хешу - це єдиний розумний спосіб.
mknjc

13
Зауважте, що у цій відповіді не використовується хороша функція виведення ключів і, отже, вона не настільки безпечна, як мала б бути . Дивіться іншу відповідь щодо дещо застарілої функції виведення ключів - і, на жаль, все ще статичної солі.
Maarten Bodewes

2
Я можу запропонувати видалити цю відповідь, оскільки це надзвичайно погана практика. Слід використовувати належну функцію виведення ключів - принаймні PBKDF2.
Борис Павук

1
Так, відповідь дуже погана, як казав Маартен багато років тому. Будь ласка, перевірте цю відповідь у Криптографії та функції
виведення

14

Ви повинні використовувати KeyGenerator для генерації ключа,

Довжина клавіш AES становить 128, 192 і 256 біт, залежно від шифру, який ви хочете використовувати.

Погляньте на підручник тут

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

        PBEKeySpec pbeKeySpec;
        PBEParameterSpec pbeParamSpec;
        SecretKeyFactory keyFac;

        // Salt
        byte[] salt = {
            (byte)0xc7, (byte)0x73, (byte)0x21, (byte)0x8c,
            (byte)0x7e, (byte)0xc8, (byte)0xee, (byte)0x99
        };

        // Iteration count
        int count = 20;

        // Create PBE parameter set
        pbeParamSpec = new PBEParameterSpec(salt, count);

        // Prompt user for encryption password.
        // Collect user password as char array (using the
        // "readPassword" method from above), and convert
        // it into a SecretKey object, using a PBE key
        // factory.
        System.out.print("Enter encryption password:  ");
        System.out.flush();
        pbeKeySpec = new PBEKeySpec(readPassword(System.in));
        keyFac = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);

        // Create PBE Cipher
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");

        // Initialize PBE Cipher with key and parameters
        pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);

        // Our cleartext
        byte[] cleartext = "This is another example".getBytes();

        // Encrypt the cleartext
        byte[] ciphertext = pbeCipher.doFinal(cleartext);

3
Як згенерувати свій ключ за допомогою пароля за допомогою KeyGenerator? Я хочу сформувати той самий ключ на основі пароля. Тож я можу розшифрувати рядок пізніше.
Берні Перес

Про що ви говорите, це шифрування на основі пароля, а не AES. Я оновив свою відповідь зразком програми для PBE
Кейбош

5
Спробуйте використати генератор ключів PBEKDF2 замість цього, використовуючи рядок "PBKDF2WithHmacSHA1" SecretKeyFactoryдля більш сучасного шифрування.
Maarten Bodewes

12
Насправді всі використані криптографічні примітиви у цій відповіді застарілі , точно MD5 та DES. Слідкуйте.
Maarten Bodewes

MD5 і DES є слабкими
шифровими наборами,

6
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.*;
import java.io.BufferedReader;
import java.io.FileReader;

public class AESFile 
{
private static String algorithm = "AES";
private static byte[] keyValue=new byte[] {'0','2','3','4','5','6','7','8','9','1','2','3','4','5','6','7'};// your key

    // Performs Encryption
    public static String encrypt(String plainText) throws Exception 
    {
            Key key = generateKey();
            Cipher chiper = Cipher.getInstance(algorithm);
            chiper.init(Cipher.ENCRYPT_MODE, key);
            byte[] encVal = chiper.doFinal(plainText.getBytes());
            String encryptedValue = new BASE64Encoder().encode(encVal);
            return encryptedValue;
    }

    // Performs decryption
    public static String decrypt(String encryptedText) throws Exception 
    {
            // generate key 
            Key key = generateKey();
            Cipher chiper = Cipher.getInstance(algorithm);
            chiper.init(Cipher.DECRYPT_MODE, key);
            byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedText);
            byte[] decValue = chiper.doFinal(decordedValue);
            String decryptedValue = new String(decValue);
            return decryptedValue;
    }

//generateKey() is used to generate a secret key for AES algorithm
    private static Key generateKey() throws Exception 
    {
            Key key = new SecretKeySpec(keyValue, algorithm);
            return key;
    }

    // performs encryption & decryption 
    public static void main(String[] args) throws Exception 
    {
        FileReader file = new FileReader("C://myprograms//plaintext.txt");
        BufferedReader reader = new BufferedReader(file);
        String text = "";
        String line = reader.readLine();
    while(line!= null)
        {
            text += line;
    line = reader.readLine();
        }
        reader.close();
    System.out.println(text);

            String plainText = text;
            String encryptedText = AESFile.encrypt(plainText);
            String decryptedText = AESFile.decrypt(encryptedText);

            System.out.println("Plain Text : " + plainText);
            System.out.println("Encrypted Text : " + encryptedText);
            System.out.println("Decrypted Text : " + decryptedText);
    }
}

5
Можливо, додати ще якийсь пояснювальний текст.
ChrisG

Питання, який сенс мати keyValueз байтовим масивом? Я бачу, як його використовують для виготовлення Ключа, чому? Чи можна щось зробити, використовуючи SecretKeyзамість цього? Якщо так, то як?
Остін

@Mandrek, вміст файлу "plaintext.txt" буде зашифровано. Вищевказана логіка шифрує дані / повідомлення у файлі, який читається як аргумент у конструкторі FileReader.
Шанкар Мурті

2

Це буде працювати.

public class CryptoUtils {

    private  final String TRANSFORMATION = "AES";
    private  final String encodekey = "1234543444555666";
    public  String encrypt(String inputFile)
            throws CryptoException {
        return doEncrypt(encodekey, inputFile);
    }


    public  String decrypt(String input)
            throws CryptoException {
    // return  doCrypto(Cipher.DECRYPT_MODE, key, inputFile);
    return doDecrypt(encodekey,input);
    }

    private  String doEncrypt(String encodekey, String inputStr)   throws CryptoException {
        try {

            Cipher cipher = Cipher.getInstance(TRANSFORMATION);

            byte[] key = encodekey.getBytes("UTF-8");
            MessageDigest sha = MessageDigest.getInstance("SHA-1");
            key = sha.digest(key);
            key = Arrays.copyOf(key, 16); // use only first 128 bit

            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);

            byte[] inputBytes = inputStr.getBytes();     
            byte[] outputBytes = cipher.doFinal(inputBytes);

            return Base64Utils.encodeToString(outputBytes);

        } catch (NoSuchPaddingException | NoSuchAlgorithmException
                | InvalidKeyException | BadPaddingException
                | IllegalBlockSizeException | IOException ex) {
            throw new CryptoException("Error encrypting/decrypting file", ex);
       }
     }


    public  String doDecrypt(String encodekey,String encrptedStr) { 
          try {     

              Cipher dcipher = Cipher.getInstance(TRANSFORMATION);
              dcipher = Cipher.getInstance("AES");
              byte[] key = encodekey.getBytes("UTF-8");
              MessageDigest sha = MessageDigest.getInstance("SHA-1");
              key = sha.digest(key);
              key = Arrays.copyOf(key, 16); // use only first 128 bit

              SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

              dcipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
            // decode with base64 to get bytes

              byte[] dec = Base64Utils.decode(encrptedStr.getBytes());  
              byte[] utf8 = dcipher.doFinal(dec);

              // create new string based on the specified charset
              return new String(utf8, "UTF8");

          } catch (Exception e) {

            e.printStackTrace();

          }
      return null;
      }
 }

2

MD5, AES, без заповнення

import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static org.apache.commons.io.Charsets.UTF_8;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class PasswordUtils {

    private PasswordUtils() {}

    public static String encrypt(String text, String pass) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            Key key = new SecretKeySpec(messageDigest.digest(pass.getBytes(UTF_8)), "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(ENCRYPT_MODE, key);

            byte[] encrypted = cipher.doFinal(text.getBytes(UTF_8));
            byte[] encoded = Base64.getEncoder().encode(encrypted);
            return new String(encoded, UTF_8);

        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
            throw new RuntimeException("Cannot encrypt", e);
        }
    }

    public static String decrypt(String text, String pass) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            Key key = new SecretKeySpec(messageDigest.digest(pass.getBytes(UTF_8)), "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(DECRYPT_MODE, key);

            byte[] decoded = Base64.getDecoder().decode(text.getBytes(UTF_8));
            byte[] decrypted = cipher.doFinal(decoded);
            return new String(decrypted, UTF_8);

        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
            throw new RuntimeException("Cannot decrypt", e);
        }
    }
}

Як створити захищений ключ, такий як SecretKeySpec в angular (ionic 4);
Нітін Карале

0
    byte[] seed = (SALT2 + username + password).getBytes();
    SecureRandom random = new SecureRandom(seed);
    KeyGenerator generator;
    generator = KeyGenerator.getInstance("AES");
    generator.init(random);
    generator.init(256);
    Key keyObj = generator.generateKey();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.