การเข้ารหัสด้วยรหัสผ่าน AES Java 256 บิต


390

ฉันต้องใช้การเข้ารหัส AES 256 บิต แต่ตัวอย่างทั้งหมดที่ฉันพบออนไลน์ใช้ "KeyGenerator" เพื่อสร้างคีย์ 256 บิต แต่ฉันต้องการใช้รหัสผ่านของฉันเอง ฉันจะสร้างรหัสของตัวเองได้อย่างไร ฉันได้ลองขยายไปถึง 256 บิต แต่แล้วฉันก็พบข้อผิดพลาดที่บอกว่ากุญแจยาวเกินไป ฉันมีการติดตั้ง patch แบบไม่ จำกัด เขตอำนาจศาลดังนั้นจึงไม่ใช่ปัญหา :)

กล่าวคือ 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)

บิต "สิ่งที่ต้องทำ" ที่คุณต้องทำด้วยตัวเอง :-)


คุณช่วยอธิบายได้ไหม: การโทร kgen.init (256) ใช้งานได้หรือไม่
Mitch Wheat

2
ใช่ แต่สิ่งนี้จะสร้างคีย์โดยอัตโนมัติ ... แต่เนื่องจากฉันต้องการเข้ารหัสข้อมูลระหว่างสองแห่งฉันจำเป็นต้องรู้รหัสล่วงหน้าดังนั้นฉันจึงต้องระบุหนึ่งแทนที่ "สร้าง" หนึ่ง ฉันสามารถระบุหนึ่งบิตที่ทำงานกับการเข้ารหัส 128 บิตซึ่งทำงานได้ ฉันลองรุ่น 32 บิตสำหรับการเข้ารหัส 256 บิตแล้ว แต่มันไม่ทำงานอย่างที่คาดไว้
Nippysaurus

4
หากฉันเข้าใจอย่างถูกต้องคุณกำลังพยายามใช้คีย์ 256 บิตที่กำหนดไว้ล่วงหน้าซึ่งระบุไว้เป็นอาร์เรย์ไบต์ ถ้าเป็นเช่นนั้นวิธีการของ DarkSquid ที่ใช้ SecretKeySpec ควรใช้งานได้ อาจเป็นไปได้ที่จะรับคีย์ AES จากรหัสผ่าน หากนั่นคือสิ่งที่คุณเป็นอยู่โปรดแจ้งให้เราทราบและฉันจะแสดงวิธีที่ถูกต้องให้คุณ เพียงแค่การ hashing รหัสผ่านไม่ใช่วิธีปฏิบัติที่ดีที่สุด
erickson

ระมัดระวังเกี่ยวกับการเติมตัวเลขคุณอาจทำให้ AES ปลอดภัยน้อยลง
Joshua

1
@erickson: นั่นคือสิ่งที่ฉันต้องทำจริงๆ (รับคีย์ AES จากรหัสผ่าน)
Nippysaurus

คำตอบ:


475

แบ่งปันpassword(a char[]) และsalt(a byte[]—8 bytes ที่เลือกโดย a 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 ลดลง

ใช้กับโหมดการผูกบล็อกที่เหมาะสมสามารถใช้คีย์ที่ได้รับเดียวกันเพื่อเข้ารหัสข้อความจำนวนมาก ในCipher Block Chaining (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 รวมการสนับสนุน API สำหรับโหมดเข้ารหัส AEADและผู้ให้บริการ "SunJCE" ที่มาพร้อมกับ OpenJDK และ Oracle ดิสทริบิวชันใช้การเริ่มต้นเหล่านี้ด้วย Java 8 หนึ่งในโหมดเหล่านี้ขอแนะนำอย่างยิ่งแทน CBC มันจะปกป้องความถูกต้องของข้อมูลเช่นเดียวกับความเป็นส่วนตัวของพวกเขา


java.security.InvalidKeyExceptionกับข้อความ "ที่ผิดกฎหมายหรือขนาดของคีย์เริ่มต้นพารามิเตอร์" หมายความว่าความแรงของการเข้ารหัสถูกจำกัด ; ไฟล์นโยบายเขตอำนาจศาลไม่ จำกัด กำลังไม่อยู่ในตำแหน่งที่ถูกต้อง ใน JDK ควรวางไว้ใต้${jdk}/jre/lib/security

ตามคำอธิบายปัญหาดูเหมือนว่าไฟล์นโยบายติดตั้งไม่ถูกต้อง ระบบสามารถมีรันไทม์ Java หลายตัวได้อย่างง่ายดาย ตรวจสอบอีกครั้งเพื่อให้แน่ใจว่ามีการใช้ตำแหน่งที่ถูกต้อง


29
@Nick: อ่าน PKCS # 5 เกลือมีความจำเป็นสำหรับ PBKDF2 ซึ่งเป็นเหตุผลว่าทำไม API สำหรับการเข้ารหัสที่ใช้รหัสผ่านจำเป็นต้องใช้มันเป็นอินพุตสำหรับการได้มาของคีย์ หากไม่มีการใช้เกลือการโจมตีพจนานุกรมสามารถใช้งานได้เปิดใช้งานรายการคีย์การเข้ารหัสแบบสมมาตรที่น่าจะคำนวณล่วงหน้า รหัส IV และเกลือที่ได้มาจากคีย์มีจุดประสงค์ที่แตกต่างกัน IV อนุญาตให้ใช้คีย์เดียวกันซ้ำได้หลายข้อความ เกลือป้องกันการโจมตีพจนานุกรมบนกุญแจ
erickson

2
อย่างแรกนั่นก็คือการเข้ารหัส DES ไม่ใช่ AES ผู้ให้บริการส่วนใหญ่ไม่มีการสนับสนุนPBEwith<prf>and<encryption>ขั้นตอนวิธีที่ดี ตัวอย่างเช่น SunJCE ไม่ได้จัดเตรียมและ PBE สำหรับ AES ประการที่สองการเปิดใช้ jasypt ไม่ใช่เป้าหมาย แพคเกจที่อ้างว่าให้ความปลอดภัยโดยไม่ต้องมีความเข้าใจในหลักการพื้นฐานที่ดูเหมือนจะเป็นอันตรายเบื้องต้น
307 erickson

6
ฉันใช้คำตอบของ @ erickson เป็นคลาส: github.com/mrclay/jSecureEdit/tree/master/src/org/mrclay/crypto (PBE ทำงานได้ PBEStorage เป็นวัตถุที่มีค่าสำหรับการจัดเก็บ IV / ciphertext ด้วยกัน)
Steve Clay

3
@AndyNuss ตัวอย่างนี้ใช้สำหรับการเข้ารหัสที่สามารถย้อนกลับได้ซึ่งโดยทั่วไปไม่ควรใช้รหัสผ่าน คุณสามารถใช้รหัส PBKDF2 เพื่อรับรหัสผ่าน "hash" ได้อย่างปลอดภัย นั่นหมายความว่าในตัวอย่างข้างต้นคุณจะเก็บผลลัพธ์ของtmp.getEncoded()การแฮช คุณควรจัดเก็บsaltและทำซ้ำ (65536 ในตัวอย่างนี้) เพื่อให้คุณสามารถคำนวณแฮชใหม่เมื่อมีคนพยายามพิสูจน์ตัวตน ในกรณีนี้สร้างเกลือด้วยเครื่องสร้างตัวเลขสุ่มเข้ารหัสในแต่ละครั้งที่มีการเปลี่ยนรหัสผ่าน
erickson

6
สำหรับการเรียกใช้รหัสนี้ตรวจสอบให้แน่ใจว่าคุณมีไฟล์นโยบายเขตอำนาจศาลไม่ จำกัด ความแข็งแกร่งใน JRE ของคุณตามที่ระบุไว้ในngs.ac.uk/tools/jcepolicyfiles
Amir Moghimi

75

พิจารณาใช้Spring Security Crypto Module

โมดูล Spring Security Crypto ให้การสนับสนุนการเข้ารหัสแบบสมมาตรการสร้างคีย์และการเข้ารหัสรหัสผ่าน รหัสถูกแจกจ่ายเป็นส่วนหนึ่งของโมดูลหลัก แต่ไม่มีการพึ่งพารหัสอื่น ๆ ของ Spring Security (หรือ Spring)

มันให้สิ่งที่เป็นนามธรรมอย่างง่ายสำหรับการเข้ารหัสและดูเหมือนจะตรงกับสิ่งที่ต้องการที่นี่

วิธีการเข้ารหัส "มาตรฐาน" คือ AES 256 บิตโดยใช้ PBKDF2 ของ PKCS # 5 (ฟังก์ชั่นการรับรหัสผ่านตามคีย์ # 2) วิธีนี้ต้องใช้ Java 6 รหัสผ่านที่ใช้ในการสร้าง SecretKey ควรเก็บไว้ในที่ปลอดภัยและไม่ถูกแชร์ เกลือถูกใช้เพื่อป้องกันการโจมตีด้วยพจนานุกรมกับกุญแจในกรณีที่ข้อมูลที่เข้ารหัสของคุณถูกบุกรุก นอกจากนี้ยังมีการนำเวกเตอร์เริ่มต้นแบบสุ่มขนาด 16 ไบต์มาใช้เพื่อให้แต่ละข้อความที่เข้ารหัสไม่ซ้ำกัน

ดูที่internalsเผยให้เห็นโครงสร้างคล้ายกับคำตอบของเอริก

ดังที่ระบุไว้ในคำถามสิ่งนี้ยังต้องใช้นโยบายการจำกัดความแข็งแกร่งของพลังการเข้ารหัสแบบไม่ จำกัดของJava (JCE) (ไม่เช่นนั้นคุณจะพบInvalidKeyException: Illegal Key Size) มันสามารถดาวน์โหลดได้สำหรับJava 6 , Java 7และJava 8

ตัวอย่างการใช้งาน

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

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

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

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

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

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

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

และตัวอย่างผลลัพธ์

เกลือ: "feacbc02a3a697b0"
ข้อความต้นฉบับ: "* secret secret *"
ข้อความที่เข้ารหัส: "7c73c5a83fa580b5d6f8208768adc931ef3123291ac8bc335a1277a3977d256d9a" 
ข้อความที่ถอดรหัส: "* Royal secret *"
สำเร็จ: การถอดรหัสข้อความตรงกัน

คุณสามารถใช้โมดูลนั้นได้โดยไม่ต้องโหลดสปริงทั้งหมดหรือไม่ ดูเหมือนว่าพวกเขาจะไม่ทำให้ไฟล์ jar พร้อมให้ดาวน์โหลด
theglauber

5
@theglauber ใช่คุณสามารถใช้โมดูลโดยไม่มี Spring Security หรือ Spring Framework จากการมองที่pomที่พึ่งพารันไทม์เพียงอย่างเดียวคือ Apache คอมมอนส์เข้าสู่ระบบ 1.1.1 คุณสามารถดึง jar ด้วย mavenหรือดาวน์โหลดโดยตรงจากไบนารี repo อย่างเป็นทางการ (ดูดาวน์โหลด Spring 4 ไบนารีสำหรับข้อมูลเพิ่มเติมเกี่ยวกับ Spring ไบนารี)
John McCarthy

1
เป็นไปได้หรือไม่ที่จะตั้งความยาวของคีย์เป็น 128- บิต? การปรับเปลี่ยนโฟลเดอร์ความปลอดภัยในพีซีทุกเครื่องไม่ใช่ตัวเลือกสำหรับฉัน
IvanRF

1
@IvanRF ขออภัยดูไม่เหมือน 256 เป็นรหัสยากในแหล่งที่มา
จอห์นแม็กคาร์ธี

2
การNULL_IV_GENERATORใช้งานยูทิลิตี้ของ Spring ไม่ปลอดภัย หากแอปพลิเคชันไม่มี IV ให้ผู้ให้บริการเลือกและสอบถามหลังจากเริ่มต้น
erickson

32

หลังจากอ่านคำแนะนำของ erickson และรวบรวมสิ่งที่ฉันทำได้จากการโพสต์อื่น ๆ และตัวอย่างที่นี่ฉันพยายามอัปเดตโค้ดของ Doug ด้วยการเปลี่ยนแปลงที่แนะนำ รู้สึกอิสระที่จะแก้ไขเพื่อให้ดีขึ้น

  • การเริ่มต้นเวกเตอร์ไม่ได้รับการแก้ไขอีกต่อไป
  • คีย์การเข้ารหัสได้มาโดยใช้รหัสจาก erickson
  • เกลือ 8 ไบต์ถูกสร้างขึ้นใน setupEncrypt () โดยใช้ SecureRandom ()
  • คีย์ถอดรหัสนั้นสร้างขึ้นจากเกลือเข้ารหัสและรหัสผ่าน
  • รหัสลับถอดรหัสถูกสร้างขึ้นจากคีย์ถอดรหัสและเวกเตอร์เริ่มต้น
  • ลบ hex twiddling แทน org.apache.commons codec Hex

หมายเหตุบางประการ: นี่ใช้คีย์เข้ารหัส 128 บิตซึ่งเห็นได้ชัดว่าจาวาจะไม่เข้ารหัส 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 จะใช้งานไม่ได้กับไฟล์ขนาดใหญ่ที่ไม่เหมาะกับหน่วยความจำ ดังนั้น +1 ถึงวูฟู :)
dynamokaj

2
@dynamokaj การใช้งานCipherInputStreamและCipherOutputStreamปัญหาไม่มาก การสลับข้อยกเว้นทั้งหมดใต้โต๊ะเป็นปัญหา ความจริงที่ว่าเกลือกลายเป็นสนามทันทีและจำเป็นต้องมีเกลือเป็นปัญหา ความจริงที่ว่ามันไม่เป็นไปตามอนุสัญญาการเข้ารหัส Java เป็นปัญหา และความจริงที่ว่าการทำงานกับไฟล์ในขณะที่ไม่ได้ขอเป็นปัญหา และส่วนที่เหลือของรหัสนั้นเป็นสำเนาก็ไม่ได้ช่วยอะไรเช่นกัน แต่บางทีฉันอาจจะปรับแต่งให้ดีขึ้นตามที่แนะนำ ...
มาร์ติน Bodewes

@ Wowstead ฉันเห็นด้วยว่าการเข้ารหัสอาจดูดีกว่าฉันตัดมันลงเหลือ 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 บิต คุณบอกว่าคุณมีการติดตั้ง patch แบบไม่ จำกัด เขตอำนาจศาลดังนั้น AES-256 cipher ควรได้รับการสนับสนุน (แต่จากนั้น 256-bit keys ควรจะมากเกินไปดังนั้นอาจเป็นปัญหาการกำหนดค่า)

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

วิธีแก้ปัญหาสำหรับการขาดการสนับสนุน AES-256 คือการใช้งานการใช้งาน AES-256 ได้อย่างอิสระและใช้เป็นผู้ให้บริการที่กำหนดเอง นี้เกี่ยวข้องกับการสร้างของคุณเองProvidersubclass Cipher.getInstance(String, Provider)และใช้กับ แต่นี่อาจเป็นกระบวนการที่เกี่ยวข้อง


5
คุณควรระบุโหมดและอัลกอริธึมการขยายเสมอ Java ใช้โหมด ECB ที่ไม่ปลอดภัยตามค่าเริ่มต้น
Maarten Bodewes

คุณไม่สามารถสร้างผู้ให้บริการของคุณเองได้ผู้ให้บริการจะต้องลงชื่อ (ไม่น่าเชื่อว่าฉันจะอ่านข้อผิดพลาดนี้ในตอนแรก) แม้ว่าคุณจะสามารถทำได้ข้อ จำกัด ของขนาดคีย์คือการนำไปใช้งานCipherไม่ใช่ในตัวให้บริการเอง คุณสามารถใช้ AES-256 ใน Java 8 และต่ำกว่า แต่คุณต้องใช้ API ที่เป็นกรรมสิทธิ์ หรือรันไทม์ที่ไม่มีข้อ จำกัด เกี่ยวกับขนาดหลักของหลักสูตร
Maarten Bodewes

OpenJDK เวอร์ชันล่าสุด (และ Android) ไม่มีข้อ จำกัด ในการเพิ่มผู้ให้บริการความปลอดภัย / การเข้ารหัสของคุณเอง แต่คุณต้องยอมรับความเสี่ยงด้วยตนเอง หากคุณลืมปรับปรุงห้องสมุดให้ทันสมัยอยู่เสมอคุณอาจเสี่ยงต่อความปลอดภัย
Maarten Bodewes

1
@ MaartenBodewes + OpenJDK ไม่เคยมีปัญหา 'จำกัด การเข้ารหัสลับนโยบาย' ในตอนแรกและOracle JDK ลบมันเมื่อปีที่แล้วสำหรับ 8u161 และ 9 ขึ้นไป (และอาจจะบางรุ่นที่จ่ายน้อยกว่าตอนนี้ แต่ฉันยังไม่ได้ตรวจสอบ)
dave_thompson_085

6

สิ่งที่ฉันทำไปแล้วในอดีตคือการแฮ็ชกุญแจผ่านบางอย่างเช่น SHA256 จากนั้นแยกไบต์จากแฮชไปยังคีย์ไบต์ []

หลังจากที่คุณมีไบต์ [] คุณสามารถทำได้:

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

12
สำหรับคนอื่น: นี่ไม่ใช่วิธีที่ปลอดภัยมาก คุณควรใช้ PBKDF 2 ที่ระบุใน PKCS # 5 erickson กล่าวว่าวิธีการดังกล่าวข้างต้น วิธีการของ DarkSquid มีความเสี่ยงต่อการโจมตีด้วยรหัสผ่านและยังไม่สามารถใช้งานได้เว้นแต่ขนาดของข้อความธรรมดาของคุณคือขนาดบล็อกของ AES (128 บิต) หลายอันเนื่องจากเขาไม่มีการเว้นช่องว่างไว้ นอกจากนี้มันไม่ได้ระบุโหมด อ่านโหมดปฏิบัติการ Crypt Block ของวิกิพีเดียเพื่อหาข้อกังวล
Hut8 8

1
@DarkSquid Cipher aes256 = Cipher.getInstance("AES/OFB/NoPadding"); MessageDigest keyDigest = MessageDigest.getInstance("SHA-256"); byte[] keyHash = keyDigest.digest(secret.getBytes("UTF-8")); SecretKeySpec key = new SecretKeySpec(keyHash, "AES"); aes256.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(initializationVector)); ฉันก็ทำเช่นเดียวกันกับที่แนะนำไว้ในคำตอบของคุณ แต่ฉันยังจบลงด้วย java.security.InvalidKeyException นี้: ขนาดของคีย์ที่ผิดกฎหมายกำลังดาวน์โหลดไฟล์นโยบาย JCE บังคับหรือไม่
Niranjan Subramanian

2
ห้ามใช้วิธีนี้ในสภาพแวดล้อมการผลิตทุกประเภท เมื่อเริ่มต้นด้วยการเข้ารหัสด้วยรหัสผ่านผู้ใช้จำนวนมากได้รับความสับสนจากการใช้รหัสและไม่เข้าใจว่าการโจมตีพจนานุกรมและการแฮ็กแบบง่าย ๆ อื่น ๆ ทำงานอย่างไร แม้ว่าการเรียนรู้จะน่าหงุดหงิด แต่ก็เป็นการลงทุนที่คุ้มค่าในการวิจัย นี่คือบทความเริ่มต้นที่ดี: adambard.com/blog/3-wrong-ways-to-store-a-password
IcedDante

1

เมื่อเพิ่มการแก้ไขของ @ Wufoo เวอร์ชันต่อไปนี้จะใช้ InputStreams แทนที่จะเป็นไฟล์เพื่อให้การทำงานกับไฟล์หลากหลายง่ายขึ้น นอกจากนี้ยังเก็บ IV และ Salt ในตอนต้นของไฟล์ทำให้ต้องมีการติดตามรหัสผ่านเท่านั้น เนื่องจาก IV และเกลือไม่จำเป็นต้องเป็นความลับสิ่งนี้ทำให้ชีวิตง่ายขึ้นเล็กน้อย

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

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

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

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

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

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

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

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

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

        return hex.toString();
    }

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

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

        return ba;
    }

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

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

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

            SecureRandom rnd = new SecureRandom();

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

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

            tmp = factory.generateSecret(spec);

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

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

            AlgorithmParameters params = mEcipher.getParameters();

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

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

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

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

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

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

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

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

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

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

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

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

            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1
วิธีนี้ดูเหมือนว่าจะใช้การจัดการบัฟเฟอร์ที่น่าอึดอัดใจและการจัดการข้อยกเว้นย่อยอย่างแน่นอนโดยทั่วไปบันทึกพวกเขาแล้วลืมเกี่ยวกับพวกเขา ถูกเตือนว่าการใช้ CBC นั้นใช้ได้สำหรับไฟล์ แต่ไม่ใช่เพื่อความปลอดภัยในการขนส่ง แน่นอนว่าการใช้ PBKDF2 และ AES นั้นสามารถป้องกันได้ในแง่นั้นอาจเป็นรากฐานที่ดีสำหรับการแก้ปัญหา
Maarten Bodewes

1

(อาจเป็นประโยชน์สำหรับผู้อื่นที่มีความต้องการคล้ายกัน)

ฉันมีข้อกำหนดที่คล้ายกันในการใช้การAES-256-CBCเข้ารหัสและถอดรหัสใน Java

เพื่อให้บรรลุ (หรือระบุ) การเข้ารหัส / ถอดรหัส 256- ไบต์Java Cryptography Extension (JCE)นโยบายควรตั้งเป็น"Unlimited"

สามารถตั้งค่าในjava.securityไฟล์ภายใต้$JAVA_HOME/jre/lib/security(สำหรับ JDK) หรือ$JAVA_HOME/lib/security(สำหรับ JRE)

crypto.policy=unlimited

หรือในรหัสเป็น

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

Java 9 และรุ่นที่ใหม่กว่าเปิดใช้งานสิ่งนี้ตามค่าเริ่มต้น


0

พิจารณาใช้Encryptor4jซึ่งฉันเป็นผู้เขียน

ก่อนอื่นตรวจสอบให้แน่ใจว่าคุณได้ติดตั้งไฟล์นโยบายความแข็งแกร่งไม่ จำกัด เขตอำนาจศาลก่อนดำเนินการต่อเพื่อให้คุณสามารถใช้คีย์ AES 256 บิต

จากนั้นทำดังต่อไปนี้:

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

ตอนนี้คุณสามารถใช้ตัวเข้ารหัสเพื่อเข้ารหัสข้อความของคุณได้แล้ว คุณสามารถทำการสตรีมเข้ารหัสได้หากต้องการ มันสร้างและเสริม IV ที่ปลอดภัยโดยอัตโนมัติเพื่อความสะดวกของคุณ

หากเป็นไฟล์ที่คุณต้องการบีบอัดลองดูที่คำตอบนี้การ เข้ารหัสไฟล์ขนาดใหญ่ด้วย AES โดยใช้ JAVAสำหรับวิธีที่ง่ายกว่า


2
สวัสดี Martin คุณควรระบุว่าคุณเป็นนักเขียนของห้องสมุดเสมอหากคุณต้องการชี้ให้เห็น มีตัวห่อ crypto มากมายที่พยายามทำให้ง่ายขึ้น เอกสารนี้มีเอกสารความปลอดภัยหรือได้รับการตรวจสอบเพื่อให้คุ้มค่าหรือไม่?
Maarten Bodewes

-1

ใช้คลาสนี้เพื่อเข้ารหัส มันได้ผล.

public class ObjectCrypter {


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

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

    }

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

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

    }
}

และนี่คือ ivBytes และคีย์สุ่ม

String key = "e8ffc7e56311679f12b6fc91aa77a5eb";

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

10
"ใช้งานได้" .... ใช่ แต่ไม่เป็นไปตามข้อกำหนดสำหรับการสร้างโซลูชันที่มีความปลอดภัยเข้ารหัส (ไม่เป็นไปตามมาตรฐานการเข้ารหัสของ Java เกี่ยวกับการจัดการข้อยกเว้นในความคิดของฉัน)
Maarten Bodewes

2
IV ถูกเตรียมใช้งานเป็นศูนย์ ค้นหาการโจมตี BEAST และ ACPA
Michele Giuseppe Fadda

ข้อยกเว้น wazoo วิธีการสร้างคีย์ "random" และ zero IV เป็นปัญหาเกี่ยวกับการใช้งานนี้ แต่ปัญหาเหล่านั้นไม่สำคัญที่จะแก้ไข +1
ฟิล
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.