Java 256ビットAESパスワードベースの暗号化


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();

ここから取得したコード

編集

私は実際には、長すぎてビットではなく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
はい、ただし、これは自動的にキーを生成します...しかし、2つの場所の間でデータを暗号化するため、事前にキーを知っておく必要があるため、「生成」する代わりにキーを指定する必要があります。機能する128ビット暗号化で機能する16ビットを指定できます。私は256ビット暗号化に32ビット暗号化を試みましたが、期待どおりに機能しませんでした。
ニッピサウルス2009年

4
私が正しく理解している場合は、たとえばバイトの配列として指定されている、事前に配置された256ビットのキーを使用しようとしています。その場合、DarkSquidのSecretKeySpecを使用したアプローチが機能するはずです。パスワードからAESキーを導出することもできます。それがあなたの目的である場合は、私に知らせてください。そうするための正しい方法を紹介します。単にパスワードをハッシュ化することはベストプラクティスではありません。
エリクソン09年

数値の埋め込みに注意してください。AESの安全性が低下する可能性があります。
ジョシュア

1
@erickson:それはまさに私がする必要があることです(パスワードからAESキーを導出します)。
ニッピサウルス09年

回答:


476

password(a char[])とsalt(a byte[]—aによって選択された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を弱める攻撃が発見された場合の安全マージンはあまりありません。

適切なブロックチェーンモードで使用すると、同じ派生キーを使用して多くのメッセージを暗号化できます。暗号ブロック連鎖(CBC) 、ランダムな初期化ベクトル(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"));

保管ciphertextiv。復号化時に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にはAEAD暗号モードの API サポートが含まれており、OpenJDKとOracleディストリビューションに含まれる「SunJCE」プロバイダーはこれらをJava 8以降で実装します。CBCの代わりにこれらのモードの1つを強くお勧めします。データの整合性とプライバシーを保護します。


java.security.InvalidKeyException「不正なキーサイズまたはデフォルトパラメータ」というメッセージが表示されたA は、暗号強度制限されていることを意味します。無制限の強度の管轄ポリシーファイルが正しい場所にありません。JDKでは、以下に配置する必要があります${jdk}/jre/lib/security

問題の説明によると、ポリシーファイルが正しくインストールされていないようです。システムは複数のJavaランタイムを簡単に持つことができます。正しい場所が使用されていることを確認するために再確認してください。


29
@ニック:PKCS#5を読んでください。PBKDF2にはソルトが必要です。そのため、パスワードベースの暗号化のAPIは、キー導出の入力としてソルトを必要とします。ソルトなしでは、辞書攻撃が使用される可能性があり、最も可能性の高い対称暗号化キーの事前計算済みリストが有効になります。暗号IVとキー派生ソルトは、異なる目的に役立ちます。IVでは、複数のメッセージに同じキーを再利用できます。ソルトは、キーに対する辞書攻撃を防ぎます。
エリクソン09年

2
まず、それはAESではなくDES暗号化です。ほとんどのプロバイダーはPBEwith<prf>and<encryption>アルゴリズムを適切にサポートしていません。たとえば、SunJCEはAES用のPBEを提供していません。次に、jasyptを有効にすることは目的ではありません。根本的な原則を理解することなくセキュリティを提供することを目的としたパッケージは、一見危険なようです。
エリクソン2009年

6
@ericksonの回答をクラスとして実装しました:github.com/mrclay/jSecureEdit/tree/master/src/org/mrclay/crypto(PBEが機能し、PBEStorageはIV /暗号文を一緒に格納するための値オブジェクトです。)
Steve Clay、

3
@AndyNussこの例は、通常はパスワードに使用すべきではない、可逆暗号化のためのものです。あなたはできるしっかり「ハッシュ」、パスワードにPBKDF2鍵導出を使用しています。つまり、上の例では、結果をtmp.getEncoded()ハッシュとして保存します。またsalt、誰かが認証を試みたときにハッシュを再計算できるように、と反復(この例では65536)も保存する必要があります。この場合、パスワードが変更されるたびに暗号化乱数ジェネレータでソルトを生成します。
エリクソン2012

6
このコードを実行するには、ngs.ac.uk / tools / jcepolicyfiles
Amir Moghimi

75

Spring Security Crypto Moduleの使用を検討してください

Spring Security Cryptoモジュールは、対称暗号化、キー生成、およびパスワードエンコーディングのサポートを提供します。コードはコアモジュールの一部として配布されますが、他のSpring Security(またはSpring)コードに依存しません。

暗号化の単純な抽象化を提供し、ここで必要なものと一致しているようです。

「標準」の暗号化方式は、PKCS#5のPBKDF2(パスワードベースの鍵導出関数#2)を使用する256ビットのAESです。このメソッドにはJava 6が必要です。SecretKeyの生成に使用されるパスワードは安全な場所に保管し、共有しないでください。ソルトは、暗号化されたデータが侵害された場合に、キーに対する辞書攻撃を防ぐために使用されます。暗号化された各メッセージが一意になるように、16バイトのランダム初期化ベクトルも適用されます。

内部を見ると、エリクソンの答えに似た構造が明らかになります

質問で述べたように、これにはJava暗号化拡張機能(JCE)の無制限強度管轄ポリシーも必要です(それ以外の場合はInvalidKeyException: Illegal Key Size)。Java 6Java 7Java 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 14年

5
@theglauberはい、Spring SecurityやSpringフレームワークがなくてもモジュールを使用できます。pomを見ると、唯一のランタイム依存関係はapache commons-logging 1.1.1です。あなたはできるのmavenで瓶に引っ張ったり、公式バイナリレポから直接ダウンロード(参照春4つのバイナリダウンロード春のバイナリ詳細はを)。
John McCarthy

1
鍵の長さを128ビットに設定することは可能ですか?すべてのPCでセキュリティフォルダーを変更することは、私には選択肢ではありません。
IvanRF 2015

1
@IvanRF申し訳ありません、そのようには見えません。ソース
John McCarthy、

2
NULL_IV_GENERATOR春のユーティリティで使用は安全ではありません。アプリケーションがIVを提供しない場合は、プロバイダーにそれを選択させ、初期化後にそれを照会します。
エリクソン2016年

32

エリクソンの提案を読んで、他のいくつかの投稿とこの例からここで私が何ができるかを調べた後、推奨される変更でDougのコードを更新しようとしました。それをより良くするために自由に編集してください。

  • 初期化ベクトルは修正されなくなりました
  • 暗号化キーはエリクソンのコードを使用して導出されます
  • SecureRandom()を使用してsetupEncrypt()で8バイトのソルトが生成されます
  • 復号化キーは、暗号化ソルトとパスワードから生成されます
  • 復号化暗号は、復号化キーと初期化ベクトルから生成されます
  • org.apache.commons コーデック Hexルーチンの代わりにhex twiddlingを削除

いくつかの注記:これは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 2014年

2
@owlstead-これは素晴らしい答えです。すべてをメモリに保持するのではなく、バイトバッファを暗号化してストリームを暗号化する方法を示します。エリクソンの答えは、メモリに収まらない大きなファイルでは機能しません。したがって、+ 1をwufooにします。:)
dynamokaj 2014年

2
@dynamokajの使用CipherInputStreamCipherOutputStreamそれほど問題ではありません。テーブルの下のすべての例外をシャッフルすることは問題です。塩がいきなり畑になってIVが必要なのが問題です。それがJavaコーディング規約に従っていないという事実は問題です。そして、これが要求されていないときにファイルでのみ機能するという事実は問題です。そして、残りのコードは基本的にコピーであることも役に立ちません。しかし、提案されているように、それをより良くするために微調整するかもしれません...
Maarten Bodewes 2014年

@owlstead私はコーディングがもっと良く見えるかもしれないと私は同意します、私はそれを1/4か何かに減らしました、しかし私は彼がCipherInputStreamとCipherOutputStreamを私に紹介したのが好きです、それは私が昨日必要だったものだったからです!;)
dynamokaj 2014年

なぜ二度?fout.close(); fout.close();
MarianPaździoch2017

7

バイト配列から独自のキーを生成するのは簡単です:

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

ただし、256ビットのキーを作成するだけでは不十分です。キージェネレーターが256ビットキーを生成できない場合、CipherクラスはおそらくAES 256ビットもサポートしていません。あなたは無制限の管轄区域パッチがインストールされていると言っているので、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、プロバイダー自体ではなくの実装にあります。Java 8以下ではAES-256を使用できますが、独自のAPIを使用する必要があります。またはもちろん、キーサイズに制限を課さないランタイム。
Maarten Bodewes 2014年

OpenJDK(およびAndroid)の最近のバージョンでは、独自のセキュリティ/暗号プロバイダーの追加に制限はありません。もちろん、それはあなた自身の責任で行ってください。ライブラリを最新の状態に保つことを忘れると、セキュリティリスクにさらされる可能性があります。
Maarten Bodewes

1
@ MaartenBodewes + OpenJDKは、最初に「限定暗号化ポリシー」の問題を抱えていなかったため、Oracle JDK は、1 年以上前に8u161と9以降でそれを削除しました(現在、一部の今だけの支払い専用バージョンですが、チェックしていません)。
dave_thompson_085

6

私が過去にやったことは、SHA256のようなものを介してキーをハッシュし、そのハッシュからバイトを抽出してバイト[]に入れます。

byte []を取得したら、次のように簡単に実行できます。

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

12
その他の場合:これは非常に安全な方法ではありません。PKCS#5で指定されているPBKDF 2を使用する必要があります。エリクソンはこれを上で行う方法を言った。DarkSquidの方法はパスワード攻撃に対して脆弱であり、プレーンテキストのサイズがAESのブロックサイズ(128ビット)の倍数でない限り機能しません。また、モードを指定しません。懸念については、WikipediaのBlock Cipher Modes of Operationを参照してください。
Hut8

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:Illegal key sizeが発生しますJCEポリシーファイルのダウンロードは必須ですか?
Niranjan Subramanian 2014

2
この方法は、どのような実稼働環境でも使用しないでください。パスワードベースの暗号化を使い始めると、多くのユーザーがコードの壁に圧倒され、辞書攻撃やその他の単純なハッキングのしくみを理解できません。学ぶのはイライラするかもしれませんが、これを研究することは価値のある投資です。これが良い初心者向けの記事です。adambard.com
blog

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 2016年

1

(おそらく同じような要件を持つ他の人にとって役立つでしょう)

AES-256-CBCJavaで暗号化と復号化を使用するための同様の要件がありました。

256バイトの暗号化/復号化を実現(または指定)するには、Java Cryptography Extension (JCE)ポリシーを次のように設定する必要があります"Unlimited"

(JDKの場合)または(JREの場合)のjava.securityファイルで設定できます。$JAVA_HOME/jre/lib/security$JAVA_HOME/lib/security

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);

これで、暗号化機能を使用してメッセージを暗号化できます。必要に応じて、ストリーミング暗号化を実行することもできます。自動的に安全なIVが生成され、付加されます。

それが圧縮したいファイルである場合は、この回答を参照してください 。さらに簡単な方法として、JAVA使用してAESで大きなファイルを暗号化します


2
こんにちはマーティン。指摘したい場合は、常にライブラリの作成者であることを示す必要があります。物事を簡単にしようとする暗号ラッパーがたくさんあります。これはセキュリティペーパーを持っていますか、それとも私たちの価値があるようにするためのレビューを受けましたか?
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 2014年

2
IVはゼロに初期化されます。BEASTおよびACPA攻撃を検索します。
ミケーレジュゼッペファダ2015年

wazoo、「ランダム」キーを生成する方法、およびゼロIVの例外は、この実装の問題ですが、これらの問題は簡単に修正できます。+1。
Phil
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.