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 байт ». Мне нужно набить ключ? как мне это сделать?

Также мне нужно что-то установить для ECB или CBC?

Благодарность

Берни Перес
источник
6
Меня беспокоит ваше отсутствие случайной соли . А теперь серьезно: в контексте криптографии ОСВ должна быть случайной
Жоау Портела
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, используя следующий формат: «Cipher / Mode / Padding»

Для AES с использованием CTS и PKCS5Padding строка выглядит так: «AES / CTS / PKCS5Padding»

mknjc
источник
Это будет работать, но мой пароль хешируется, а затем используются только первые несколько бит. Нет лучшего способа сделать это?
Берни Перес,
4
Нет лучшего способа сгенерировать ключевую причину, по которой AES нужен ключ размером 128/192/256 бит. Если вы не хешируете свой ключ и только обрезаете ввод, он будет использовать только первые 16/24/32 байта. Так что создание хэша - единственный разумный способ.
mknjc
13
Обратите внимание, что в этом ответе не используется хорошая функция вывода ключей, и поэтому он не так безопасен, как должен быть . См. Другой ответ для немного устаревшей функции вывода ключей - и, к сожалению, все еще статической солью.
Maarten Bodewes
2
Могу ли я предложить удалить этот ответ, поскольку это крайне плохая практика. Должна использоваться правильная функция получения ключа - по крайней мере, PBKDF2.
Паук Борис
1
Да, ответ очень плохой, как сказал Маартен много лет назад. Пожалуйста, проверьте этот ответ на сайте Cryptography and Key Derivation Function
kelalaka
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 являются слабыми наборами шифров, и их следует ИЗБЕГАТЬ
atom88
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();
Sonnykwe
источник