Шифрование Ява пароль

Я пытаюсь узнать, как сделать шифрование на основе парольной фразы с помощью Java. Я нахожу несколько примеров в интернете, но ни один (пока) при переполнении стека. Примеры немного освещают объяснение для меня, особенно в отношении выбора алгоритма. Кажется, есть много передаваемых строк, чтобы сказать, какие алгоритмы использовать, но мало документации о том, откуда взялись строки и что они означают. И также кажется, что различные алгоритмы могут требовать разных реализации класса KeySpec, поэтому я не уверен, какие алгоритмы могут использовать класс PBEKeySpec, на который я смотрю. Кроме того, все примеры кажутся немного устаревшими, многие требуют, чтобы вы получили более старый пакет криптографии, который раньше не был частью JDK или даже сторонней реализацией.

может ли кто-нибудь предоставить простое введение в то, что мне нужно сделать для реализации шифрования (строковые данные, строковая парольная фраза) и расшифровки (байт [] данных, строка пароль)?

7 ответов


Я буду осторожен в предоставлении или принятии связанных с безопасностью советов с форума... специфика довольно запутанная, и часто быстро устаревает.

сказав это, я думаю, что Sun's Java Cryptography Architecture (JCA) справочное руководство является хорошей отправной точкой. Проверьте сопровождающие пример кода иллюстрация шифрования на основе пароля (PBE).

Btw, стандартный JRE обеспечивает только несколько вариантов из коробки для PBE ("PBEWithMD5AndDES" является одним из них). Для большего выбора вам понадобится "сильный пакет шифрования" или какой-либо сторонний поставщик, например Надувной Замок. Другой альтернативой было бы реализовать свой собственный PBE, используя алгоритмы хэша и шифрования, предоставленные в JRE. Вы можете реализовать PBE с SHA-256 и AES-128 таким образом (пример шифрования/дешифрования методов).

Короче говоря, метод шифрования для PBE может включать следующие шаги:

  1. вам пароль и открытый текст от пользователя, и преобразовать их в массивы байтов.
  2. создать безопасный случайный соль.
  3. добавьте соль к паролю и вычислите его криптографический хэш. Повторите это много раз.
  4. зашифровать открытый текст, используя полученный хэш в качестве вектор инициализации и/или секрет ключ.
  5. сохраните соль и полученный зашифрованный текст.

использовать RFC2898 для генерации ключей из паролей. Это не включено в JRE или JCE, насколько я знаю, но оно включено в серверы J2EE, такие как JBoss, Oracle и WebSphere. Он также включен в библиотеку базовых классов .NET (Rfc2898DeriveBytes).

есть некоторые реализации LGPL в Java, но на быстрый взгляд этот выглядит немного более сложным. Существует также хороший версия javascript. (Я произвел модифицированная версия этого и упаковал его как компонент скрипта Windows)

Не имея хорошей реализации с соответствующей лицензией, я упаковал некоторый код от Маттиаса Гартнера. Это код во всей его полноте. Коротко, просто, легко понять. Он лицензирован под MS Public License.

// PBKDF2.java
// ------------------------------------------------------------------
//
// RFC2898 PBKDF2 in Java.  The RFC2898 defines a standard algorithm for
// deriving key bytes from a text password.  This is sometimes
// abbreviated "PBKDF2", for Password-based key derivation function #2.
//
// There's no RFC2898-compliant PBKDF2 function in the JRE, as far as I
// know, but it is available in many J2EE runtimes, including those from
// JBoss, IBM, and Oracle.
//
// It's fairly simple to implement, so here it is. 
// 
// Created Sun Aug 09 01:06:57 2009
//
// last saved: 
// Time-stamp: <2009-August-09 02:19:50>
// ------------------------------------------------------------------
//
// code thanks to Matthias Gartner
//
// ------------------------------------------------------------------

package cheeso.examples;


import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;


public class PBKDF2
{
    public static byte[] deriveKey( byte[] password, byte[] salt, int iterationCount, int dkLen )
        throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException
    {
        SecretKeySpec keyspec = new SecretKeySpec( password, "HmacSHA1" );
        Mac prf = Mac.getInstance( "HmacSHA1" );
        prf.init( keyspec );

        // Note: hLen, dkLen, l, r, T, F, etc. are horrible names for
        //       variables and functions in this day and age, but they
        //       reflect the terse symbols used in RFC 2898 to describe
        //       the PBKDF2 algorithm, which improves validation of the
        //       code vs. the RFC.
        //
        // dklen is expressed in bytes. (16 for a 128-bit key)

        int hLen = prf.getMacLength();   // 20 for SHA1
        int l = Math.max( dkLen, hLen); //  1 for 128bit (16-byte) keys
        int r = dkLen - (l-1)*hLen;      // 16 for 128bit (16-byte) keys
        byte T[] = new byte[l * hLen];
        int ti_offset = 0;
        for (int i = 1; i <= l; i++) {
            F( T, ti_offset, prf, salt, iterationCount, i );
            ti_offset += hLen;
        }

        if (r < hLen) {
            // Incomplete last block
            byte DK[] = new byte[dkLen];
            System.arraycopy(T, 0, DK, 0, dkLen);
            return DK;
        }
        return T;
    } 


    private static void F( byte[] dest, int offset, Mac prf, byte[] S, int c, int blockIndex ) {
        final int hLen = prf.getMacLength();
        byte U_r[] = new byte[ hLen ];
        // U0 = S || INT (i);
        byte U_i[] = new byte[S.length + 4];
        System.arraycopy( S, 0, U_i, 0, S.length );
        INT( U_i, S.length, blockIndex );
        for( int i = 0; i < c; i++ ) {
            U_i = prf.doFinal( U_i );
            xor( U_r, U_i );
        }

        System.arraycopy( U_r, 0, dest, offset, hLen );
    }

    private static void xor( byte[] dest, byte[] src ) {
        for( int i = 0; i < dest.length; i++ ) {
            dest[i] ^= src[i];
        }
    }

    private static void INT( byte[] dest, int offset, int i ) {
        dest[offset + 0] = (byte) (i / (256 * 256 * 256));
        dest[offset + 1] = (byte) (i / (256 * 256));
        dest[offset + 2] = (byte) (i / (256));
        dest[offset + 3] = (byte) (i);
    } 

    // ctor
    private PBKDF2 () {}

}

в очень полезном ответе Чизо выше есть ошибка плохой производительности.

строку

int l = Math.max( dkLen, hLen)

не должен высчитать максимальное, но потолок дивизии, так

int l = ((dkLen - 1) / hLen) + 1; // >= ceil(dkLen / hLen), == for dkLen =>1

это ускорит вычисление в 20 раз для 16 байтовых ключей.


вам нужна библиотека шифрования, которая расскажет вам, как ее настроить.
Мне нравятся вещи из bouncycastle.org. Вы можете найти их как здесь DES, на который ссылаются в примере 5.1, является одним из шифров, которые они предлагают. Что означает фактическая строка, будет зависеть от поставщика. По сути, вы загружаете библиотеку.

Security.addProvider(new BouncyCastleProvider());

а то только используйте интерфейсы JCE, чтобы делать все, что вы хотите:

 keyGen = KeyGenerator.getInstance("DES", "BC");

Java обрабатывает привязку библиотеки и интерфейсов для вас, вам не нужно этого делать. Я был бы более чем счастлив объяснить больше, если у вас есть какие-либо вопросы. К сожалению, в данный момент я страдаю от болезни "я не могу вспомнить, как я этому научился", поэтому, пожалуйста, не стесняйтесь спрашивать.


вы можете использовать алгоритм хэша (несколько раз, если необходимо), чтобы получить от парольной фразы некоторые необработанные данные, которые вы можете использовать в качестве ключа (+ вектор инициализации, если алгоритм вызывает один).

затем вы можете использовать этот ключ с любым симметричным алгоритмом , таким как 3DES-CBC или AES-CBC (DES считается устаревшим в наши дни).

в зависимости от JCE, который у вас есть, у вас могут быть разные алгоритмы в вашем распоряжении, но AES, вероятно, то, что вы хотите. Выбор алгоритм и точно, как его использовать, - это несколько религиозная проблема, и вам было бы неразумно пытаться свернуть свой собственный или даже попытаться построить собственную схему шифрования с использованием стандартных алгоритмов. Вы почти наверняка ошибетесь, если не изучили его, и, возможно, даже если изучили.

Если безопасность так важна для вас, что вы рассматриваете шифрование, то вы должны рассмотреть также глядя на инженерную книгу безопасности, как применяется Криптография Брюса Шнайера или техника безопасности Росса Андерсона-есть много подводных камней реализации. Например, использование парольной фразы в качестве ключа-это не такая уж хорошая идея, поскольку она существенно уменьшает размер вашего ключа.

вы также можете посмотреть на проекты, которые сделали другие люди, есть много в IETF, например: http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha1-00


Если вам не надо расшифровывать пароль, а просто генерируют ключ шифрования на основе пароля/пароль, вы можете использовать тег стандарт PKCS#5, используя классы JCE Cipher и MessageDigest.


преобразуйте строку в массив байтов во время шифрования. Преобразовать обратно в строку после расшифровки.

/**
 * Creates a cipher for encryption or decryption.
 * 
 * @param algorithm  PBE algorithm like "PBEWithMD5AndDES" or "PBEWithMD5AndTripleDES".
 * @param mode Encyrption or decyrption.
 * @param password Password
 * @param salt Salt usable with algorithm.
 * @param count Iterations.
 * @return Ready initialized cipher.
 * @throws GeneralSecurityException Error creating the cipher.
 */
private static Cipher createCipher(final String algorithm, final int mode, final char[] password, final byte[] salt, final int count) throws GeneralSecurityException {
    final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
    final PBEKeySpec keySpec = new PBEKeySpec(password);
    final SecretKey key = keyFactory.generateSecret(keySpec);
    final Cipher cipher = Cipher.getInstance(algorithm);
    final PBEParameterSpec params = new PBEParameterSpec(salt, count);
    cipher.init(mode, key, params);
    return cipher;
}

/**
 * Encrypts some data based on a password.
 * @param algorithm PBE algorithm like "PBEWithMD5AndDES" or "PBEWithMD5AndTripleDES"
 * @param data Data to encrypt
 * @param password Password
 * @param salt Salt usable with algorithm
 * @param count Iterations.
 * @return Encrypted data.
 */
public static byte[] encryptPasswordBased(final String algorithm, final byte[] data, final char[] password, final byte[] salt, final int count) {
    Validate.notNull(algorithm);
    Validate.notNull(data);
    Validate.notNull(password);
    Validate.notNull(salt);
    try {
        final Cipher cipher = createCipher(algorithm, Cipher.ENCRYPT_MODE, password, salt, count);
        return cipher.doFinal(data);
    } catch (final Exception ex) {
        throw new RuntimeException("Error encrypting the password!", ex);
    }
}

/**
 * Decrypts some data based on a password.
 * @param algorithm PBE algorithm like "PBEWithMD5AndDES" or "PBEWithMD5AndTripleDES"
 * @param encryptedData Data to decrypt
 * @param password Password
 * @param salt Salt usable with algorithm
 * @param count Iterations.
 * @return Encrypted data.
 */
public static byte[] decryptPasswordBased(final String algorithm, final byte[] encryptedData, final char[] password, final byte[] salt, final int count) {
    Validate.notNull(algorithm);
    Validate.notNull(encryptedData);
    Validate.notNull(password);
    Validate.notNull(salt);
    try {
        final Cipher cipher = createCipher(algorithm, Cipher.DECRYPT_MODE, password, salt, count);
        return cipher.doFinal(encryptedData);
    } catch (final Exception ex) {
        throw new RuntimeException("Error decrypting the password!", ex);
    }
}