256-бітове шифрування на основі AES Java


390

Мені потрібно реалізувати 256-бітове шифрування AES, але всі знайдені в Інтернеті приклади "KeyGenerator" використовуються для генерування 256-бітного ключа, але я хотів би використовувати свою власну ключ доступу. Як я можу створити власний ключ? Я спробував доповнити його на 256 біт, але потім я отримав помилку, сказавши, що ключ занадто довгий. У мене встановлений необмежений патч юрисдикції, так що це не проблема :)

Тобто KeyGenerator виглядає так ...

// Get the KeyGenerator
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may not be available

// Generate the secret key specs.
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();

Код, взятий звідси

EDIT

Я фактично додав пароль до 256 байтів, а не біт, що занадто довго. Далі наведено деякий код, який я зараз використовую, коли у мене є ще досвід з цим.

byte[] key = null; // TODO
byte[] input = null; // TODO
byte[] output = null;
SecretKeySpec keySpec = null;
keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
output = cipher.doFinal(input)

Біти "TODO", які потрібно зробити самостійно :-)


Чи можете ви уточнити: чи працює дзвінок kgen.init (256)?
Мітч Пшеничний

2
Так, але це автоматично генерує ключ ... але оскільки я хочу зашифрувати дані між двома місцями, мені потрібно знати ключ заздалегідь, тому мені потрібно вказати його замість "генерувати". Я можу вказати 16-бітний, який працює для 128-бітового шифрування, який працює. Я спробував 32-бітний для шифрування 256 біт, але він не працював так, як очікувалося.
Nippysaurus

4
Якщо я правильно розумію, ви намагаєтесь використовувати заздалегідь упорядкований 256-бітний ключ, вказаний, наприклад, як масив байтів. Якщо так, підхід DarkSquid із використанням SecretKeySpec повинен працювати. Також можна отримати ключ AES з пароля; якщо ви це хочете, будь ласка, повідомте мене, і я покажу вам правильний спосіб зробити це; просто хешування пароля - не найкраща практика.
еріксон

Будьте обережні щодо прокладки номера, можливо, ви зробите ваш AES менш захищеним.
Джошуа

1
@erickson: це саме те, що мені потрібно зробити (вивести ключ AES з пароля).
Nippysaurus

Відповіді:


475

Поділіться password(a char[]) та salt( byte[]8 байтів, вибраних SecureRandomзначком, що є доброю сіллю, яку не потрібно зберігати в таємниці) з одержувачем поза межами діапазону. Тоді, щоб отримати хороший ключ від цієї інформації:

/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

Магічні числа (які десь можна визначити як константи) 65536 та 256 - це ітерація підрахунку ключових значень та розмір ключа відповідно.

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

Розмір ключа можна зменшити до 128 біт, що все ще вважається "сильним" шифруванням, але це не дає великої межі безпеки, якщо будуть виявлені атаки, які послаблюють AES.

Використовується у правильному режимі блокування ланцюга, той самий похідний ключ може бути використаний для шифрування багатьох повідомлень. У ланцюзі блоків шифрів (СРС) генерується випадковий вектор ініціалізації (IV) для кожного повідомлення, даючи різний текст шифру, навіть якщо звичайний текст однаковий. CBC може бути не найбільш безпечним для вас режимом (див. AEAD нижче); Є багато інших режимів з різними властивостями безпеки, але всі вони використовують подібний випадковий вхід. У будь-якому випадку, результатами кожної операції шифрування є текст шифру та вектор ініціалізації:

/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal("Hello, World!".getBytes("UTF-8"));

Зберігайте ciphertextта iv. При дешифруванні SecretKeyрегенерується точно таким же чином, використовуючи пароль з однаковими параметрами солі та ітерації. Ініціалізуйте шифр за допомогою цього ключа та вектор ініціалізації, що зберігається з повідомленням:

/* Decrypt the message, given derived key and initialization vector. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
String plaintext = new String(cipher.doFinal(ciphertext), "UTF-8");
System.out.println(plaintext);

Java 7 включала підтримку API для шифруючих режимів AEAD , а постачальник "SunJCE", що входить до дистрибутивів OpenJDK та Oracle, реалізує це починаючи з Java 8. Один із цих режимів настійно рекомендується замість CBC; це захистить цілісність даних, а також їх конфіденційність.


java.security.InvalidKeyExceptionЗ повідомленням «Незаконні розмір ключа або по стандартних параметрів» означає , що сила криптографія є обмеженим; файли політики необмеженої юрисдикції не в правильному місці. У JDK вони повинні бути розміщені під${jdk}/jre/lib/security

На підставі опису проблеми здається, що файли політики неправильно встановлені. Системи можуть легко мати кілька режимів виконання Java; двічі перевірте, щоб переконатися, що використовується правильне місцеположення.


29
@Nick: Прочитайте ПКС №5. Солі необхідні для PBKDF2, саме тому API для шифрування на основі пароля вимагає їх як введення для виведення ключа. Без солей може бути використана атака словника, що дозволяє попередньо обчислити список найбільш ймовірних симетричних ключів шифрування. Шифри IV та солі ключового походження служать різному призначенню. IV дозволяють одноразово використовувати один і той же ключ для декількох повідомлень. Солі запобігають атакам словника на ключ.
erickson

2
По-перше, це було б шифрування DES, а не AES. Більшість провайдерів не мають належної підтримки PBEwith<prf>and<encryption>алгоритмів; наприклад, SunJCE не забезпечує і PBE для AES. По-друге, включення ясипта - це не мета. Пакет, який хоче запропонувати безпеку, не вимагаючи розуміння основних принципів, здається небезпечним.
еріксон

6
Я реалізував відповідь @ erickson як клас: github.com/mrclay/jSecureEdit/tree/master/src/org/mrclay/crypto (PBE працює, PBEStorage є об'єктом значення для зберігання разом IV / ciphertext.)
Стів Клей

3
@AndyNuss Цей приклад стосується зворотного шифрування, яке зазвичай не слід використовувати для паролів. Ви можете використовувати виведення ключа PBKDF2 для безпечного "хешування" паролів. Це означає, що у наведеному вище прикладі ви зберігаєте результат tmp.getEncoded()як хеш. Ви також повинні зберігати saltі ітерації (65536 у цьому прикладі), щоб ви могли перерахувати хеш, коли хтось намагається пройти автентифікацію. У цьому випадку генеруйте сіль за допомогою криптографічного генератора випадкових чисел щоразу, коли пароль змінюється.
erickson

6
Для запуску цього коду переконайтеся, що у вашому JRE є правильні файли політики щодо необмеженої сили, як зазначено в ngs.ac.uk/tools/jcepolicyfiles
Амір Могімі

75

Подумайте про використання криптомодуля Spring Security

Крипто модуль Spring Security забезпечує підтримку симетричного шифрування, генерації ключів та кодування паролів. Код поширюється як частина основного модуля, але не має залежності від будь-якого іншого коду Spring Security (або Spring).

Це забезпечує просту абстракцію для шифрування і, здається, відповідає тому, що потрібно тут,

"Стандартним" методом шифрування є 256-бітний AES за допомогою PBKDF2 PKCS # 5 (Функція виведення ключів на основі пароля №2). Цей метод вимагає Java 6. Пароль, що використовується для створення SecretKey, повинен зберігатися в безпечному місці і не повинен надаватися їм спільним доступом. Сіль використовується для запобігання атаки словника на ключ у випадку, якщо ваші зашифровані дані будуть порушені. Також застосовується 16-байтний випадковий вектор ініціалізації, щоб кожне зашифроване повідомлення було унікальним.

Погляд на внутрішні органи виявляє структуру, подібну до відповіді Еріксона .

Як зазначається в запитанні, для цього також потрібна необмежена сила юрисдикційної політики розширення криптографії Java (JCE) (ще ви стикаєтесь InvalidKeyException: Illegal Key Size). Це можна завантажити для Java 6 , Java 7 та Java 8 .

Приклад використання

import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.crypto.keygen.KeyGenerators;

public class CryptoExample {
    public static void main(String[] args) {
        final String password = "I AM SHERLOCKED";  
        final String salt = KeyGenerators.string().generateKey();

        TextEncryptor encryptor = Encryptors.text(password, salt);      
        System.out.println("Salt: \"" + salt + "\"");

        String textToEncrypt = "*royal secrets*";
        System.out.println("Original text: \"" + textToEncrypt + "\"");

        String encryptedText = encryptor.encrypt(textToEncrypt);
        System.out.println("Encrypted text: \"" + encryptedText + "\"");

        // Could reuse encryptor but wanted to show reconstructing TextEncryptor
        TextEncryptor decryptor = Encryptors.text(password, salt);
        String decryptedText = decryptor.decrypt(encryptedText);
        System.out.println("Decrypted text: \"" + decryptedText + "\"");

        if(textToEncrypt.equals(decryptedText)) {
            System.out.println("Success: decrypted text matches");
        } else {
            System.out.println("Failed: decrypted text does not match");
        }       
    }
}

І вибірки,

Сіль: "feacbc02a3a697b0"
Оригінальний текст: "* королівські таємниці *"
Зашифрований текст: "7c73c5a83fa580b5d6f8208768adc931ef3123291ac8bc335a1277a39d256d9a" 
Розшифрований текст: "* королівські таємниці *"
Успіх: розшифровані відповідність тексту

Чи можете ви використовувати цей модуль, не завантажуючи всю Spring? Здається, вони не зробили доступними файли jar для завантаження.
theglauber

5
@theglauber Так, ви можете використовувати модуль без Spring Security або Spring Framework. З погляду на пом , єдина залежність від виконання - apache commons-logging 1.1.1 . Ви можете потягнути в банку з maven або завантажити його безпосередньо з офіційного бінарного репо (див. Для завантаження бінарних файлів весни 4 для отримання додаткової інформації про весняні бінарні файли).
Джон Маккарті

1
Чи можна встановити довжину ключа на 128 біт? Змінення папки безпеки на кожному ПК не є для мене варіантом.
IvanRF

1
@IvanRF вибачте, не схоже на це. 256 важко закодовано у джерелі
Джон Маккарті

2
NULL_IV_GENERATORВикористовується утилітою Spring не є безпечним. Якщо додаток не надає IV, дозвольте провайдеру вибрати його та здійснити запит після ініціалізації.
еріксон

32

Ознайомившись із пропозиціями Еріксона та ознайомившись із тим, що я міг із кількох інших публікацій, і цей приклад тут , я спробував оновити код Дуга з рекомендованими змінами. Не соромтеся редагувати, щоб зробити це краще.

  • Вектор ініціалізації вже не зафіксовано
  • ключ шифрування виводиться за допомогою коду від erickson
  • 8 байт-сіль генерується в setupEncrypt () за допомогою SecureRandom ()
  • ключ дешифрування генерується із солі та пароля шифрування
  • шифр дешифрування генерується з ключа дешифрування та вектора ініціалізації
  • вилучено шістнадцятковий подвійний замість кодека org.apache.commons Шестнадцятковий режим

Деякі зауваження: тут використовується 128-бітний ключ шифрування - Java, очевидно, не буде робити 256-бітове шифрування поза коробкою. Реалізація 256 вимагає встановлення додаткових файлів у каталог встановлення Java.

Також я не криптовалюта. Прислухайтеся.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

public class Crypto
{
    String mPassword = null;
    public final static int SALT_LEN = 8;
    byte [] mInitVec = null;
    byte [] mSalt = null;
    Cipher mEcipher = null;
    Cipher mDecipher = null;
    private final int KEYLEN_BITS = 128; // see notes below where this is used.
    private final int ITERATIONS = 65536;
    private final int MAX_FILE_BUF = 1024;

    /**
     * create an object with just the passphrase from the user. Don't do anything else yet 
     * @param password
     */
    public Crypto (String password)
    {
        mPassword = password;
    }

    /**
     * return the generated salt for this object
     * @return
     */
    public byte [] getSalt ()
    {
        return (mSalt);
    }

    /**
     * return the initialization vector created from setupEncryption
     * @return
     */
    public byte [] getInitVec ()
    {
        return (mInitVec);
    }

    /**
     * debug/print messages
     * @param msg
     */
    private void Db (String msg)
    {
        System.out.println ("** Crypt ** " + msg);
    }

    /**
     * this must be called after creating the initial Crypto object. It creates a salt of SALT_LEN bytes
     * and generates the salt bytes using secureRandom().  The encryption secret key is created 
     * along with the initialization vectory. The member variable mEcipher is created to be used
     * by the class later on when either creating a CipherOutputStream, or encrypting a buffer
     * to be written to disk.
     *  
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidParameterSpecException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     * @throws InvalidKeyException
     */
    public void setupEncrypt () throws NoSuchAlgorithmException, 
                                                           InvalidKeySpecException, 
                                                           NoSuchPaddingException, 
                                                           InvalidParameterSpecException, 
                                                           IllegalBlockSizeException, 
                                                           BadPaddingException, 
                                                           UnsupportedEncodingException, 
                                                           InvalidKeyException
    {
        SecretKeyFactory factory = null;
        SecretKey tmp = null;

        // crate secureRandom salt and store  as member var for later use
         mSalt = new byte [SALT_LEN];
        SecureRandom rnd = new SecureRandom ();
        rnd.nextBytes (mSalt);
        Db ("generated salt :" + Hex.encodeHexString (mSalt));

        factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

        /* Derive the key, given password and salt. 
         * 
         * in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
         * The end user must also install them (not compiled in) so beware. 
         * see here:  http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
         */
        KeySpec spec = new PBEKeySpec (mPassword.toCharArray (), mSalt, ITERATIONS, KEYLEN_BITS);
        tmp = factory.generateSecret (spec);
        SecretKey secret = new SecretKeySpec (tmp.getEncoded(), "AES");

        /* Create the Encryption cipher object and store as a member variable
         */
        mEcipher = Cipher.getInstance ("AES/CBC/PKCS5Padding");
        mEcipher.init (Cipher.ENCRYPT_MODE, secret);
        AlgorithmParameters params = mEcipher.getParameters ();

        // get the initialization vectory and store as member var 
        mInitVec = params.getParameterSpec (IvParameterSpec.class).getIV();

        Db ("mInitVec is :" + Hex.encodeHexString (mInitVec));
    }



    /**
     * If a file is being decrypted, we need to know the pasword, the salt and the initialization vector (iv). 
     * We have the password from initializing the class. pass the iv and salt here which is
     * obtained when encrypting the file initially.
     *   
     * @param initvec
     * @param salt
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     * @throws DecoderException
     */
    public void setupDecrypt (String initvec, String salt) throws NoSuchAlgorithmException, 
                                                                                       InvalidKeySpecException, 
                                                                                       NoSuchPaddingException, 
                                                                                       InvalidKeyException, 
                                                                                       InvalidAlgorithmParameterException, 
                                                                                       DecoderException
    {
        SecretKeyFactory factory = null;
        SecretKey tmp = null;
        SecretKey secret = null;

        // since we pass it as a string of input, convert to a actual byte buffer here
        mSalt = Hex.decodeHex (salt.toCharArray ());
       Db ("got salt " + Hex.encodeHexString (mSalt));

        // get initialization vector from passed string
        mInitVec = Hex.decodeHex (initvec.toCharArray ());
        Db ("got initvector :" + Hex.encodeHexString (mInitVec));


        /* Derive the key, given password and salt. */
        // in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
        // The end user must also install them (not compiled in) so beware. 
        // see here: 
      // http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
        factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec spec = new PBEKeySpec(mPassword.toCharArray (), mSalt, ITERATIONS, KEYLEN_BITS);

        tmp = factory.generateSecret(spec);
        secret = new SecretKeySpec(tmp.getEncoded(), "AES");

        /* Decrypt the message, given derived key and initialization vector. */
        mDecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        mDecipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(mInitVec));
    }


    /**
     * This is where we write out the actual encrypted data to disk using the Cipher created in setupEncrypt().
     * Pass two file objects representing the actual input (cleartext) and output file to be encrypted.
     * 
     * there may be a way to write a cleartext header to the encrypted file containing the salt, but I ran
     * into uncertain problems with that. 
     *  
     * @param input - the cleartext file to be encrypted
     * @param output - the encrypted data file
     * @throws IOException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public void WriteEncryptedFile (File input, File output) throws 
                                                                                          IOException, 
                                                                                          IllegalBlockSizeException, 
                                                                                          BadPaddingException
    {
        FileInputStream fin;
        FileOutputStream fout;
        long totalread = 0;
        int nread = 0;
        byte [] inbuf = new byte [MAX_FILE_BUF];

        fout = new FileOutputStream (output);
        fin = new FileInputStream (input);

        while ((nread = fin.read (inbuf)) > 0 )
        {
            Db ("read " + nread + " bytes");
            totalread += nread;

            // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
            // and results in full blocks of MAX_FILE_BUF being written. 
            byte [] trimbuf = new byte [nread];
            for (int i = 0; i < nread; i++)
                trimbuf[i] = inbuf[i];

            // encrypt the buffer using the cipher obtained previosly
            byte [] tmp = mEcipher.update (trimbuf);

            // I don't think this should happen, but just in case..
            if (tmp != null)
                fout.write (tmp);
        }

        // finalize the encryption since we've done it in blocks of MAX_FILE_BUF
        byte [] finalbuf = mEcipher.doFinal ();
        if (finalbuf != null)
            fout.write (finalbuf);

        fout.flush();
        fin.close();
        fout.close();

        Db ("wrote " + totalread + " encrypted bytes");
    }


    /**
     * Read from the encrypted file (input) and turn the cipher back into cleartext. Write the cleartext buffer back out
     * to disk as (output) File.
     * 
     * I left CipherInputStream in here as a test to see if I could mix it with the update() and final() methods of encrypting
     *  and still have a correctly decrypted file in the end. Seems to work so left it in.
     *  
     * @param input - File object representing encrypted data on disk 
     * @param output - File object of cleartext data to write out after decrypting
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     */
    public void ReadEncryptedFile (File input, File output) throws 
                                                                                                                                            IllegalBlockSizeException, 
                                                                                                                                            BadPaddingException, 
                                                                                                                                            IOException
    {
        FileInputStream fin; 
        FileOutputStream fout;
        CipherInputStream cin;
        long totalread = 0;
        int nread = 0;
        byte [] inbuf = new byte [MAX_FILE_BUF];

        fout = new FileOutputStream (output);
        fin = new FileInputStream (input);

        // creating a decoding stream from the FileInputStream above using the cipher created from setupDecrypt()
        cin = new CipherInputStream (fin, mDecipher);

        while ((nread = cin.read (inbuf)) > 0 )
        {
            Db ("read " + nread + " bytes");
            totalread += nread;

            // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
            byte [] trimbuf = new byte [nread];
            for (int i = 0; i < nread; i++)
                trimbuf[i] = inbuf[i];

            // write out the size-adjusted buffer
            fout.write (trimbuf);
        }

        fout.flush();
        cin.close();
        fin.close ();       
        fout.close();   

        Db ("wrote " + totalread + " encrypted bytes");
    }


    /**
     * adding main() for usage demonstration. With member vars, some of the locals would not be needed
     */
    public static void main(String [] args)
    {

        // create the input.txt file in the current directory before continuing
        File input = new File ("input.txt");
        File eoutput = new File ("encrypted.aes");
        File doutput = new File ("decrypted.txt");
        String iv = null;
        String salt = null;
        Crypto en = new Crypto ("mypassword");

        /*
         * setup encryption cipher using password. print out iv and salt
         */
        try
      {
          en.setupEncrypt ();
          iv = Hex.encodeHexString (en.getInitVec ()).toUpperCase ();
          salt = Hex.encodeHexString (en.getSalt ()).toUpperCase ();
      }
      catch (InvalidKeyException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchAlgorithmException e)
      {
          e.printStackTrace();
      }
      catch (InvalidKeySpecException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchPaddingException e)
      {
          e.printStackTrace();
      }
      catch (InvalidParameterSpecException e)
      {
          e.printStackTrace();
      }
      catch (IllegalBlockSizeException e)
      {
          e.printStackTrace();
      }
      catch (BadPaddingException e)
      {
          e.printStackTrace();
      }
      catch (UnsupportedEncodingException e)
      {
          e.printStackTrace();
      }

        /*
         * write out encrypted file
         */
        try
      {
          en.WriteEncryptedFile (input, eoutput);
          System.out.printf ("File encrypted to " + eoutput.getName () + "\niv:" + iv + "\nsalt:" + salt + "\n\n");
      }
      catch (IllegalBlockSizeException e)
      {
          e.printStackTrace();
      }
      catch (BadPaddingException e)
      {
          e.printStackTrace();
      }
      catch (IOException e)
      {
          e.printStackTrace();
      }


        /*
         * decrypt file
         */
        Crypto dc = new Crypto ("mypassword");
        try
      {
          dc.setupDecrypt (iv, salt);
      }
      catch (InvalidKeyException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchAlgorithmException e)
      {
          e.printStackTrace();
      }
      catch (InvalidKeySpecException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchPaddingException e)
      {
          e.printStackTrace();
      }
      catch (InvalidAlgorithmParameterException e)
      {
          e.printStackTrace();
      }
      catch (DecoderException e)
      {
          e.printStackTrace();
      }

        /*
         * write out decrypted file
         */
        try
      {
          dc.ReadEncryptedFile (eoutput, doutput);
          System.out.println ("decryption finished to " + doutput.getName ());
      }
      catch (IllegalBlockSizeException e)
      {
          e.printStackTrace();
      }
      catch (BadPaddingException e)
      {
          e.printStackTrace();
      }
      catch (IOException e)
      {
          e.printStackTrace();
      }
   }


}

13
Це в основному та сама відповідь, що і Еріксон, оточений обгорткою, що не добре програмується, на мій погляд. printStackTrace()
Maarten Bodewes

2
@owlstead - це чудова відповідь. Він показує, як зашифрувати потік за допомогою шифрування байтового буфера, замість того, щоб мати все в пам'яті. Відповідь Еріксона не працює для великих файлів, які не вміщуються в пам'яті. Так +1 для вуха. :)
dynamokaj

2
@dynamokaj Використання CipherInputStreamта CipherOutputStreamне є великою проблемою. Переміщення всіх винятків під таблицею - проблема. Факт, що сіль раптом перетворилася на поле і що потрібен IV - це проблема. Факт, що він не відповідає умовам кодування Java, є проблемою. І той факт, що це працює лише на файли, поки його не вимагали, - це проблема. І що решта коду - це, в основному, копія, теж не допомагає. Але, можливо, я підправлю це, щоб зробити його краще, як було запропоновано ...
Maarten Bodewes

@owlstead Я погоджуюся, що кодування могло виглядати краще, я скоротив його на 1/4 або щось подібне, але мені подобається, що він представив мене CipherInputStream і CipherOutputStream, оскільки це було саме те, що мені потрібно було вчора! ;)
динамокай

чому двічі? fout.close (); fout.close ();
Marian Paździoch

7

Створення власного ключа з масиву байтів легко:

byte[] raw = ...; // 32 bytes in size for a 256 bit key
Key skey = new javax.crypto.spec.SecretKeySpec(raw, "AES");

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

Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skey);
byte[] encrypted = cipher.doFinal(plainText.getBytes());

Вирішенням проблеми відсутності підтримки AES-256 є використання вільно доступної реалізації AES-256 та використання її як власного постачальника. Це передбачає створення власного Providerпідкласу та використання його з Cipher.getInstance(String, Provider). Але це може бути залученим процесом.


5
Ви завжди повинні вказувати алгоритм режиму та прокладки. Java за замовчуванням використовує небезпечний режим ECB.
Maarten Bodewes

Ви не можете створити власного провайдера, провайдери повинні бути підписані (не можу повірити, що я прочитав цю помилку спочатку). Навіть якщо ви могли, обмеження розміру ключа полягає в реалізації Cipher, а не в самому провайдері. Ви можете використовувати AES-256 в Java 8 і новіших версій, але вам потрібно використовувати фірмовий API. Або час виконання, який не створює обмежень щодо розміру ключа, звичайно.
Maarten Bodewes

Останні версії OpenJDK (і Android) не обмежують додавання власного постачальника послуг безпеки / криптовалют. Але ви робите це на свій страх і ризик, звичайно. Якщо ви забудете постійно оновлювати свої бібліотеки, ви можете піддавати себе ризикам безпеки.
Maarten Bodewes

1
@ MaartenBodewes + OpenJDK ніколи не мав проблеми з обмеженою крипто-політикою, і Oracle JDK видалив її понад рік тому за 8u161 і 9 вгору (і, можливо, деякі нижчі версії, які тепер платять, але я не перевіряв їх)
dave_thompson_085

6

Що я робив у минулому, це хеш-ключ через щось на зразок SHA256, а потім витягнути байти з хеша в байт ключа [].

Після того, як у вас буде байт [], ви можете просто зробити:

SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(clearText.getBytes());

12
Для інших: це не дуже безпечний метод. Вам слід використовувати PBKDF 2, зазначений у PKCS №5. Еріксон сказав, як це зробити вище. Метод DarkSquid вразливий до атак паролів, а також не працює, якщо розмір вашого простого тексту не є кратним розміру блоку AES (128 біт), оскільки він пропустив прокладку. Також він не визначає режим; читайте питання щодо блокових шифрових режимів роботи Вікіпедії.
Хата8

1
@DarkSquid Cipher aes256 = Cipher.getInstance("AES/OFB/NoPadding"); MessageDigest keyDigest = MessageDigest.getInstance("SHA-256"); byte[] keyHash = keyDigest.digest(secret.getBytes("UTF-8")); SecretKeySpec key = new SecretKeySpec(keyHash, "AES"); aes256.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(initializationVector)); Я також роблю те саме, що було запропоновано у вашій відповіді, але я все-таки закінчуюсь цим java.security.InvalidKeyException: Незаконне розмір ключа Чи завантаження файлу політики JCE обов'язкове?
Ніранян Субраманійський

2
НЕ ВИКОРИСТОВУЙТЕ цей спосіб у будь-яких виробничих умовах. Починаючи з шифрування на основі пароля, багато користувачів переповнюються стінами коду і не розуміють, як працюють атаки на словники та інші прості хаки. Навчатися в цьому може бути неприємно, але для цього слід вкласти гроші. Ось хороша стаття для початківців: adambard.com/blog/3-wrong-ways-to-store-a-password
IcedDante

1

Додаючи до редагувань @ Wufoo, наступна версія використовує InputStreams, а не файли, щоб полегшити роботу з різними файлами. Він також зберігає IV і Salt на початку файлу, завдяки чому потрібно відстежувати лише пароль. Оскільки IV та Сіль не повинні бути секретними, це полегшує життя.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
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 AES {
    public final static int SALT_LEN     = 8;
    static final String     HEXES        = "0123456789ABCDEF";
    String                  mPassword    = null;
    byte[]                  mInitVec     = null;
    byte[]                  mSalt        = new byte[SALT_LEN];
    Cipher                  mEcipher     = null;
    Cipher                  mDecipher    = null;
    private final int       KEYLEN_BITS  = 128;    // see notes below where this is used.
    private final int       ITERATIONS   = 65536;
    private final int       MAX_FILE_BUF = 1024;

    /**
     * create an object with just the passphrase from the user. Don't do anything else yet
     * @param password
     */
    public AES(String password) {
        mPassword = password;
    }

    public static String byteToHex(byte[] raw) {
        if (raw == null) {
            return null;
        }

        final StringBuilder hex = new StringBuilder(2 * raw.length);

        for (final byte b : raw) {
            hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
        }

        return hex.toString();
    }

    public static byte[] hexToByte(String hexString) {
        int    len = hexString.length();
        byte[] ba  = new byte[len / 2];

        for (int i = 0; i < len; i += 2) {
            ba[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
                                + Character.digit(hexString.charAt(i + 1), 16));
        }

        return ba;
    }

    /**
     * debug/print messages
     * @param msg
     */
    private void Db(String msg) {
        System.out.println("** Crypt ** " + msg);
    }

    /**
     * This is where we write out the actual encrypted data to disk using the Cipher created in setupEncrypt().
     * Pass two file objects representing the actual input (cleartext) and output file to be encrypted.
     *
     * there may be a way to write a cleartext header to the encrypted file containing the salt, but I ran
     * into uncertain problems with that.
     *
     * @param input - the cleartext file to be encrypted
     * @param output - the encrypted data file
     * @throws IOException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public void WriteEncryptedFile(InputStream inputStream, OutputStream outputStream)
            throws IOException, IllegalBlockSizeException, BadPaddingException {
        try {
            long             totalread = 0;
            int              nread     = 0;
            byte[]           inbuf     = new byte[MAX_FILE_BUF];
            SecretKeyFactory factory   = null;
            SecretKey        tmp       = null;

            // crate secureRandom salt and store  as member var for later use
            mSalt = new byte[SALT_LEN];

            SecureRandom rnd = new SecureRandom();

            rnd.nextBytes(mSalt);
            Db("generated salt :" + byteToHex(mSalt));
            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

            /*
             *  Derive the key, given password and salt.
             *
             * in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
             * The end user must also install them (not compiled in) so beware.
             * see here:  http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
             */
            KeySpec spec = new PBEKeySpec(mPassword.toCharArray(), mSalt, ITERATIONS, KEYLEN_BITS);

            tmp = factory.generateSecret(spec);

            SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

            /*
             *  Create the Encryption cipher object and store as a member variable
             */
            mEcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            mEcipher.init(Cipher.ENCRYPT_MODE, secret);

            AlgorithmParameters params = mEcipher.getParameters();

            // get the initialization vectory and store as member var
            mInitVec = params.getParameterSpec(IvParameterSpec.class).getIV();
            Db("mInitVec is :" + byteToHex(mInitVec));
            outputStream.write(mSalt);
            outputStream.write(mInitVec);

            while ((nread = inputStream.read(inbuf)) > 0) {
                Db("read " + nread + " bytes");
                totalread += nread;

                // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
                // and results in full blocks of MAX_FILE_BUF being written.
                byte[] trimbuf = new byte[nread];

                for (int i = 0; i < nread; i++) {
                    trimbuf[i] = inbuf[i];
                }

                // encrypt the buffer using the cipher obtained previosly
                byte[] tmpBuf = mEcipher.update(trimbuf);

                // I don't think this should happen, but just in case..
                if (tmpBuf != null) {
                    outputStream.write(tmpBuf);
                }
            }

            // finalize the encryption since we've done it in blocks of MAX_FILE_BUF
            byte[] finalbuf = mEcipher.doFinal();

            if (finalbuf != null) {
                outputStream.write(finalbuf);
            }

            outputStream.flush();
            inputStream.close();
            outputStream.close();
            outputStream.close();
            Db("wrote " + totalread + " encrypted bytes");
        } catch (InvalidKeyException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidParameterSpecException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchPaddingException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Read from the encrypted file (input) and turn the cipher back into cleartext. Write the cleartext buffer back out
     * to disk as (output) File.
     *
     * I left CipherInputStream in here as a test to see if I could mix it with the update() and final() methods of encrypting
     *  and still have a correctly decrypted file in the end. Seems to work so left it in.
     *
     * @param input - File object representing encrypted data on disk
     * @param output - File object of cleartext data to write out after decrypting
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     */
    public void ReadEncryptedFile(InputStream inputStream, OutputStream outputStream)
            throws IllegalBlockSizeException, BadPaddingException, IOException {
        try {
            CipherInputStream cin;
            long              totalread = 0;
            int               nread     = 0;
            byte[]            inbuf     = new byte[MAX_FILE_BUF];

            // Read the Salt
            inputStream.read(this.mSalt);
            Db("generated salt :" + byteToHex(mSalt));

            SecretKeyFactory factory = null;
            SecretKey        tmp     = null;
            SecretKey        secret  = null;

            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

            KeySpec spec = new PBEKeySpec(mPassword.toCharArray(), mSalt, ITERATIONS, KEYLEN_BITS);

            tmp    = factory.generateSecret(spec);
            secret = new SecretKeySpec(tmp.getEncoded(), "AES");

            /* Decrypt the message, given derived key and initialization vector. */
            mDecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

            // Set the appropriate size for mInitVec by Generating a New One
            AlgorithmParameters params = mDecipher.getParameters();

            mInitVec = params.getParameterSpec(IvParameterSpec.class).getIV();

            // Read the old IV from the file to mInitVec now that size is set.
            inputStream.read(this.mInitVec);
            Db("mInitVec is :" + byteToHex(mInitVec));
            mDecipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(mInitVec));

            // creating a decoding stream from the FileInputStream above using the cipher created from setupDecrypt()
            cin = new CipherInputStream(inputStream, mDecipher);

            while ((nread = cin.read(inbuf)) > 0) {
                Db("read " + nread + " bytes");
                totalread += nread;

                // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
                byte[] trimbuf = new byte[nread];

                for (int i = 0; i < nread; i++) {
                    trimbuf[i] = inbuf[i];
                }

                // write out the size-adjusted buffer
                outputStream.write(trimbuf);
            }

            outputStream.flush();
            cin.close();
            inputStream.close();
            outputStream.close();
            Db("wrote " + totalread + " encrypted bytes");
        } catch (Exception ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * adding main() for usage demonstration. With member vars, some of the locals would not be needed
     */
    public static void main(String[] args) {

        // create the input.txt file in the current directory before continuing
        File   input   = new File("input.txt");
        File   eoutput = new File("encrypted.aes");
        File   doutput = new File("decrypted.txt");
        String iv      = null;
        String salt    = null;
        AES    en      = new AES("mypassword");

        /*
         * write out encrypted file
         */
        try {
            en.WriteEncryptedFile(new FileInputStream(input), new FileOutputStream(eoutput));
            System.out.printf("File encrypted to " + eoutput.getName() + "\niv:" + iv + "\nsalt:" + salt + "\n\n");
        } catch (IllegalBlockSizeException | BadPaddingException | IOException e) {
            e.printStackTrace();
        }

        /*
         * decrypt file
         */
        AES dc = new AES("mypassword");

        /*
         * write out decrypted file
         */
        try {
            dc.ReadEncryptedFile(new FileInputStream(eoutput), new FileOutputStream(doutput));
            System.out.println("decryption finished to " + doutput.getName());
        } catch (IllegalBlockSizeException | BadPaddingException | IOException e) {
            e.printStackTrace();
        }
    }
}

1
Це рішення, як видається, використовує деякі незручні операції з буфером і абсолютно підпунктивні обробки винятків, в основному реєструючи їх, а потім забувши про них. Попереджуйте, що використовувати CBC добре для файлів, але не для безпеки транспорту. Використання PBKDF2 та AES, звичайно, може бути захищено, в цьому сенсі це може бути хорошою основою для рішення.
Maarten Bodewes

1

(Можливо, корисно для інших із подібною вимогою)

У мене була схожа вимога використовувати AES-256-CBCшифрування та дешифрування на Java.

Щоб досягти (або вказати) 256-байтне шифрування / дешифрування, Java Cryptography Extension (JCE)політика повинна встановити значення"Unlimited"

Його можна встановити у java.securityфайлі під $JAVA_HOME/jre/lib/security(для JDK) або $JAVA_HOME/lib/security(для JRE)

crypto.policy=unlimited

Або в коді як

Security.setProperty("crypto.policy", "unlimited");

У версії Java 9 та новіших версій це налаштування за умовчанням увімкнено.


0

Подумайте про використання Encryptor4j, яким я є автор.

Спочатку переконайтесь, що у вас встановлені файли політики необмеженої сили юрисдикції перед початком роботи, щоб ви могли використовувати 256-бітні ключі AES.

Потім виконайте наступне:

String password = "mysupersecretpassword"; 
Key key = KeyFactory.AES.keyFromPassword(password.toCharArray());
Encryptor encryptor = new Encryptor(key, "AES/CBC/PKCS7Padding", 16);

Тепер ви можете використовувати шифр для шифрування свого повідомлення. Ви також можете виконати потокове шифрування, якщо хочете. Це автоматично генерує та попереджає безпечний ІV для вашої зручності.

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


2
Привіт Мартіне, ти завжди повинен вказувати, що ти письменник бібліотеки, якщо ти хочеш вказати на це. Є oodles із криптовалют, які намагаються полегшити справи. Чи є цей папір із захистом або він отримав будь-які відгуки, щоб зробити його вартим свого часу?
Maarten Bodewes

-1

Використовуйте цей клас для шифрування. Це працює.

public class ObjectCrypter {


    public static byte[] encrypt(byte[] ivBytes, byte[] keyBytes, byte[] mes) 
            throws NoSuchAlgorithmException,
            NoSuchPaddingException,
            InvalidKeyException,
            InvalidAlgorithmParameterException,
            IllegalBlockSizeException,
            BadPaddingException, IOException {

        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = null;
        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
        return  cipher.doFinal(mes);

    }

    public static byte[] decrypt(byte[] ivBytes, byte[] keyBytes, byte[] bytes) 
            throws NoSuchAlgorithmException,
            NoSuchPaddingException,
            InvalidKeyException,
            InvalidAlgorithmParameterException,
            IllegalBlockSizeException,
            BadPaddingException, IOException, ClassNotFoundException {

        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
        return  cipher.doFinal(bytes);

    }
}

А це ivBytes та випадковий ключ;

String key = "e8ffc7e56311679f12b6fc91aa77a5eb";

byte[] ivBytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
keyBytes = key.getBytes("UTF-8");

10
"це працює" .... так, але це не відповідає вимогам створення криптографічно безпечного рішення (а також не відповідає стандартам кодування Java щодо обробки винятків, на мою думку).
Maarten Bodewes

2
IV ініціалізується до нуля. Шукайте атаки BEAST та ACPA.
Мішель Джузеппе Фадда

Винятки становлять wazoo, метод генерації "випадкового" ключа та нульовий IV - проблема з цією реалізацією, але ці проблеми тривіально виправити. +1.
Філ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.