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) 호출이 작동합니까?
Mitch Wheat

2
예, 그러나 이것은 자동으로 키를 생성합니다 ...하지만 두 곳의 데이터를 암호화하려면 미리 키를 알아야하므로 "생성"대신 하나를 지정해야합니다. 128 비트 암호화에서 작동하는 16 비트를 지정할 수 있습니다. 256 비트 암호화를 위해 32 비트를 시도했지만 예상대로 작동하지 않았습니다.
Nippysaurus

4
올바르게 이해하면 예를 들어 바이트 배열로 지정된 미리 정렬 된 256 비트 키를 사용하려고합니다. 그렇다면 SecretKeySpec을 사용한 DarkSquid의 접근 방식이 작동합니다. 암호에서 AES 키를 파생시킬 수도 있습니다. 그것이 당신이 추구하는 것이라면, 알려주세요. 올바른 방법을 보여 드리겠습니다. 단순히 암호를 해싱하는 것이 가장 좋은 방법은 아닙니다.
erickson 2016 년

숫자를 채울 때주의하십시오. AES 보안 수준이 떨어질 수 있습니다.
Joshua

1
@ erickson : 그것은 exatly 내가해야 할 일입니다 (암호에서 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를 약화시키는 것으로 밝혀지면 안전성이 크게 향상되지 않습니다.

적절한 블록 체인 모드와 함께 사용하면 동일한 파생 키를 사용하여 많은 메시지를 암호화 할 수 있습니다. 에 암호 블록 체인 (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"));

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에는 AEAD 암호 모드에 대한 API 지원이 포함되어 있으며 OpenJDK 및 Oracle 배포에 포함 된 "SunJCE"공급자는 Java 8부터 이러한 구현을 구현합니다. 이러한 모드 중 하나는 CBC 대신 권장됩니다. 데이터의 무결성과 개인 정보를 보호합니다.


java.security.InvalidKeyException"잘못된 키 크기 또는 기본 매개 변수"메시지와 함께 A 는 암호화 강도 제한됨을 의미합니다 . 무제한 강도 관할 정책 파일이 올바른 위치에 있지 않습니다. JDK에서는 아래에 배치해야합니다.${jdk}/jre/lib/security

문제점 설명에 따라 정책 파일이 올바르게 설치되지 않은 것 같습니다. 시스템은 여러 Java 런타임을 쉽게 가질 수 있습니다. 올바른 위치를 사용하고 있는지 다시 확인하십시오.


29
@Nick : PKCS # 5를 읽습니다. PBKDF2에는 솔트가 필요하므로 비밀번호 기반 암호화 API에서 키 파생을위한 입력으로 필요합니다. 솔트가 없으면 사전 공격을 사용하여 대칭형 암호화 키의 사전 계산 된 목록을 사용할 수 있습니다. 암호 IV와 키 파생 염은 다른 목적으로 사용됩니다. IV는 여러 메시지에 대해 동일한 키를 재사용 할 수 있도록합니다. 소금은 키에 대한 사전 공격을 방지합니다.
erickson 2016 년

2
먼저 AES가 아닌 DES 암호화입니다. 대부분의 공급자는 PBEwith<prf>and<encryption>알고리즘 을 제대로 지원하지 않습니다 . 예를 들어 SunJCE는 AES에 PBE를 제공하지 않습니다. 둘째, jasypt 활성화는 목표가 아닙니다. 기본 원칙을 이해하지 않고도 보안을 제공하기 위해 제공되는 패키지는 위험한 기본 요소로 보입니다.
erickson

6
나는 클래스와 에릭슨의 대답 @ 구현했습니다 : github.com/mrclay/jSecureEdit/tree/master/src/org/mrclay/crypto (PBE이 작업을 수행, PBEStorage 함께 IV / 암호문을 저장하기위한 값 오브젝트입니다.)
Steve Clay

3
@AndyNuss이 예는 일반적으로 암호에 사용해서는 안되는 가역 암호화 용입니다. PBKDF2 키 파생을 사용하여 비밀번호를 안전하게 "해시" 있습니다 . 즉, 위의 예에서는 결과를 tmp.getEncoded()해시로 저장한다는 의미입니다 . 또한 salt누군가 인증을 시도 할 때 해시를 다시 계산할 수 있도록 및 반복 (이 예제에서는 65536)을 저장해야 합니다. 이 경우 비밀번호가 변경 될 때마다 암호화 난수 생성기를 사용하여 솔트를 생성하십시오.
erickson

6
이 코드를 실행하려면 ngs.ac.uk/tools/jcepolicyfiles
Amir Moghimi에서

75

스프링 보안 암호화 모듈 사용을 고려하십시오

Spring Security Crypto 모듈은 대칭 암호화, 키 생성 및 비밀번호 인코딩을 지원합니다. 이 코드는 핵심 모듈의 일부로 배포되지만 다른 스프링 보안 (또는 스프링) 코드에는 의존하지 않습니다.

암호화에 대한 간단한 추상화를 제공하며 여기에 필요한 것과 일치하는 것 같습니다.

"표준"암호화 방법은 PKCS # 5의 PBKDF2 (비밀번호 기반 키 파생 함수 # 2)를 사용하는 256 비트 AES입니다. 이 방법에는 Java 6이 필요합니다. SecretKey를 생성하는 데 사용되는 비밀번호는 안전한 곳에 보관하고 공유하지 않아야합니다. 솔트는 암호화 된 데이터가 손상된 경우 키에 대한 사전 공격을 방지하는 데 사용됩니다. 16 바이트 랜덤 초기화 벡터도 적용되므로 각 암호화 된 메시지는 고유합니다.

상기 봐 내부는 유사한 구조를 보여 에릭슨의 답변을 .

질문에서 언급했듯이 이것은 또한 자바 암호화 확장 기능 (JCE) 무제한 강도 관할 정책 (다른 접하게을 InvalidKeyException: Illegal Key Size). Java 6 , Java 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"
원문 : "* royal secrets *"
암호화 된 텍스트 : "7c73c5a83fa580b5d6f8208768adc931ef3123291ac8bc335a1277a39d256d9a" 
해독 된 텍스트 : "* 왕의 비밀 *"
성공 : 해독 된 텍스트 일치

모든 Spring을로드하지 않고 해당 모듈을 사용할 수 있습니까? jar 파일을 다운로드 할 수없는 것으로 보입니다.
theglauber

5
@theglauber 그렇습니다. Spring Security 나 Spring 프레임 워크없이 모듈을 사용할 수 있습니다. pom을 살펴보면 런타임 종속성 만 아파치 커먼즈 로깅 1.1.1 입니다. 당신은 할 수 받는다는와 함께 항아리에 끌어 또는 공식 바이너리의 repo에서 직접 다운로드 (참조 봄 4 바이너리 다운로드 봄 바이너리에 대한 추가 정보를).
John McCarthy

1
키 길이를 128 비트로 설정할 수 있습니까? 모든 PC에서 보안 폴더를 수정하는 것은 옵션이 아닙니다.
IvanRF

1
@IvanRF 죄송합니다. 256은 소스
John McCarthy

2
NULL_IV_GENERATOR봄 유틸리티 사용은 안전하지 않습니다. 응용 프로그램이 IV를 제공하지 않으면 공급자가 IV를 선택하도록하고 초기화 후에 쿼리하도록합니다.
erickson 2016 년

32

erickson의 제안을 읽고 몇 가지 다른 게시물 과이 예제 에서 얻을 수있는 것을 얻은 후에 Doug의 코드를 권장 변경 사항으로 업데이트하려고 시도했습니다. 더 나은 편집을 위해 자유롭게 편집하십시오.

  • 초기화 벡터가 더 이상 고정되지 않습니다
  • 암호화 키는 erickson의 코드를 사용하여 파생됩니다.
  • SecureRandom ()을 사용하여 setupEncrypt ()에서 8 바이트 솔트가 생성됩니다.
  • 암호 해독 키는 암호화 솔트 및 암호에서 생성됩니다.
  • 암호 해독 암호는 암호 해독 키와 초기화 벡터에서 생성됩니다.
  • org.apache.commons 코덱 16 진수 루틴 대신 16 진수 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
이것은 기본적으로 Erickson의 답변과 동일하며 잘 프로그래밍되지 않은 나의 의견-래퍼로 둘러싸여 있습니다. printStackTrace()
Maarten Bodewes

2
@owlstead-이것은 훌륭한 답변입니다. 메모리에 모든 것을 넣지 않고 바이트 버퍼를 암호화하여 스트림을 암호화하는 방법을 보여줍니다. Erickson의 대답은 메모리에 맞지 않는 큰 파일에는 작동하지 않습니다. wufoo에 +1. :)
dynamokaj

2
의 사용을 @dynamokaj CipherInputStream하고 CipherOutputStream많은 문제가되지 않습니다. 테이블 아래의 모든 예외를 섞는 것은 문제입니다. 소금이 갑자기 필드가되었고 IV가 필요하다는 사실은 문제입니다. Java 코딩 규칙을 따르지 않는다는 사실은 문제입니다. 요청되지 않은 파일에서만 작동한다는 사실은 문제입니다. 그리고 나머지 코드는 기본적으로 사본이지만 도움이되지 않습니다. 그러나 아마 제안한대로 그것을 개선하기 위해 그것을 조정할 것입니다 ...
Maarten Bodewes

@owlstead 나는 코딩이 더 좋게 보일 수 있음에 동의합니다. 1/4 또는 그 이상으로 줄였습니다. 그러나 CipherInputStream 및 CipherOutputStream을 소개했습니다. ;)
dynamokaj

왜 두 번? 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클래스에서 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 14:01에

최신 버전의 OpenJDK (및 Android)에는 자체 보안 / 암호화 공급자를 추가하는 데 제한이 없습니다. 그러나 물론 당신은 자신의 책임하에 그렇게합니다. 라이브러리를 최신 상태로 유지하지 않으면 보안 위험에 노출 될 수 있습니다.
Maarten Bodewes 2014 년

1
@ MaartenBodewes + OpenJDK는 처음에는 '제한된 암호화 정책'문제가 없었으며 Oracle JDK는 1u 전에 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의 블록 암호 작동 모드를 읽으십시오.
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으로 끝납니다 : 잘못된 키 크기 JCE 정책 파일을 다운로드해야합니까?
Niranjan Subramanian

2
모든 유형의 프로덕션 환경에서이 방법을 사용하지 마십시오. 암호 기반 암호화로 시작할 때 많은 사용자가 많은 코드에 압도 당하고 사전 공격 및 기타 간단한 해킹의 작동 방식을 이해하지 못합니다. 배우는 것이 실망 스럽지만 이것을 연구하는 것은 가치있는 투자입니다. 다음은 훌륭한 초보자 기사입니다. adambard.com/blog/3-wrong-ways-to-store-a-password
IcedDante

1

@Wufoo의 편집에 추가 한 다음 버전은 파일이 아닌 InputStream을 사용하여 다양한 파일 작업을보다 쉽게합니다. 또한 파일 시작 부분에 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-CBCJava에서 암호화 및 암호 해독 을 사용해야하는 비슷한 요구 사항이 있습니다 .

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 키를 사용할 수 있도록 먼저 진행하기 전에 Unlimited Strength Jurisdiction Policy 파일이 설치되어 있는지 확인하십시오 .

그런 다음 다음을 수행하십시오.

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는 0으로 초기화됩니다. BEAST 및 ACPA 공격을 검색하십시오.
Michele Giuseppe Fadda

wazoo, "랜덤"키를 생성하는 방법 및 IV가 0 인 예외는이 구현의 문제이지만 이러한 문제는 해결하기가 쉽지 않습니다. +1.
Phil
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.