Java AES และใช้คีย์ของฉันเอง


89

ฉันต้องการเข้ารหัสสตริงโดยใช้ AES ด้วยคีย์ของฉันเอง แต่ฉันมีปัญหากับความยาวบิตของคีย์ คุณสามารถตรวจสอบรหัสของฉันและดูสิ่งที่ฉันต้องแก้ไข / เปลี่ยนแปลง

public static void main(String[] args) throws Exception {
    String username = "bob@google.org";
    String password = "Password1";
    String secretID = "BlahBlahBlah";
    String SALT2 = "deliciously salty";

    // Get the Key
    byte[] key = (SALT2 + username + password).getBytes();
    System.out.println((SALT2 + username + password).getBytes().length);

    // Need to pad key for AES
    // TODO: Best way?

    // Generate the secret key specs.
    SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

    // Instantiate the cipher
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);

    byte[] encrypted = cipher.doFinal((secrectID).getBytes());
    System.out.println("encrypted string: " + asHex(encrypted));

    cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
    byte[] original = cipher.doFinal(encrypted);
    String originalString = new String(original);
    System.out.println("Original string: " + originalString + "\nOriginal string (Hex): " + asHex(original));
}

ตอนนี้ฉันได้รับข้อยกเว้น " ความยาวคีย์ AES ไม่ถูกต้อง: 86 ไบต์ " ต้องใส่กุญแจไหม ควรทำอย่างไร?

ฉันต้องตั้งค่าอะไรสำหรับ ECB หรือ CBC หรือไม่

ขอบคุณ


6
ฉันคิดว่าคุณขาดเกลือสุ่มรบกวน ตอนนี้จริงจัง: ในบริบทของการเข้ารหัสSALT ควรเป็นแบบสุ่ม
João Portela

16
ฮ่า ๆ ตลกดี จริงๆแล้วฉันมีเกลือแบบสุ่ม แต่ฉันทำความสะอาดโค้ดของฉันเพื่อให้คำถามของฉันชัดเจนยิ่งขึ้น นั่นเป็นเหตุผลว่าทำไมตัวแปรจึงชื่อว่า SALT2 แต่การอ้างอิงที่ดีสำหรับผู้อื่นที่พบปัญหาเดียวกันนี้และต้องการคัดลอก / วางรหัส
Bernie Perez

คำตอบ:


125

แก้ไข:

ตามที่เขียนไว้ในความคิดเห็นรหัสเก่าไม่ใช่ "แนวทางปฏิบัติที่ดีที่สุด" คุณควรใช้อัลกอริทึมการสร้างคีย์เช่น PBKDF2 ที่มีการนับซ้ำสูง นอกจากนี้คุณควรใช้เกลือที่ไม่คงที่อย่างน้อยบางส่วน (หมายถึงเกลือเฉพาะ "เอกลักษณ์" แต่ละตัว) หากเป็นไปได้ที่สร้างขึ้นแบบสุ่มและจัดเก็บไว้พร้อมกับการเข้ารหัส

    SecureRandom sr = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[16];
    sr.nextBytes(salt);

    PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 1000, 128 * 8);
    SecretKey key = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec);
    Cipher aes = Cipher.getInstance("AES");
    aes.init(Cipher.ENCRYPT_MODE, key);

===========

คำตอบเก่า

คุณควรใช้ SHA-1 เพื่อสร้างแฮชจากคีย์ของคุณและตัดแต่งผลลัพธ์เป็น 128 บิต (16 ไบต์)

นอกจากนี้อย่าสร้างอาร์เรย์ไบต์จากสตริงผ่านgetBytes () โดยใช้ Charset เริ่มต้นของแพลตฟอร์ม ดังนั้นรหัสผ่าน "blaöä" จึงทำให้เกิดอาร์เรย์ไบต์ที่แตกต่างกันบนแพลตฟอร์มต่างๆ

byte[] key = (SALT2 + username + password).getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit

SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

แก้ไข: หากคุณต้องการ 256 บิตเป็นขนาดคีย์คุณต้องดาวน์โหลดลิงก์ดาวน์โหลด "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files" ให้ใช้ SHA-256 เป็นแฮชและลบบรรทัดArrays.copyOf "ECB" คือโหมดการเข้ารหัสเริ่มต้นและ "PKCS5Padding" ช่องว่างภายในเริ่มต้น คุณสามารถใช้ Cipher Modes และ Padding Modes ที่แตกต่างกันผ่านสตริงCipher.getInstanceโดยใช้รูปแบบต่อไปนี้: "Cipher / Mode / Padding"

สำหรับ AES ที่ใช้ CTS และ PKCS5Padding สตริงคือ: "AES / CTS / PKCS5Padding"


วิธีนี้จะใช้งานได้ แต่จะแฮชรหัสผ่านของฉันจากนั้นใช้เพียงไม่กี่บิตแรกเท่านั้น ไม่มีวิธีที่ดีกว่านี้จะทำอย่างไร?
Bernie Perez

4
ไม่มีวิธีใดที่ดีกว่าในการสร้างคีย์สาเหตุที่ AES ต้องการคีย์ 128/192/256 บิต หากคุณไม่ได้แฮชคีย์และตัดเฉพาะอินพุตมันจะใช้ไบต์ 16/24/32 แรกเท่านั้น ดังนั้นการสร้าง Hash จึงเป็นวิธีเดียวที่สมเหตุสมผล
mknjc

13
หมายเหตุว่าคำตอบนี้ไม่ได้ใช้ฟังก์ชั่นที่มาที่สำคัญที่ดีและดังนั้นจึงไม่เป็นที่เชื่อถือได้ตามที่มันควรจะเป็น ดูคำตอบอื่นสำหรับฟังก์ชันการหาคีย์ที่ล้าสมัยไปเล็กน้อยและน่าเสียดายที่ยังคงเป็นเกลือแบบคงที่
Maarten Bodewes

2
ฉันขอแนะนำให้ลบคำตอบนี้เนื่องจากเป็นการปฏิบัติที่ไม่ดีอย่างยิ่ง ควรใช้ฟังก์ชันการหาคีย์ที่เหมาะสม - อย่างน้อย PBKDF2
Boris the Spider

1
ใช่คำตอบนั้นแย่มากอย่างที่ Maarten กล่าวไว้เมื่อหลายปีก่อน โปรดตรวจสอบคำตอบนี้จากCryptography and Key Derivation Function
kelalaka

14

คุณควรใช้ KeyGenerator เพื่อสร้างคีย์

ความยาวคีย์ AES คือ 128, 192 และ 256 บิตขึ้นอยู่กับการเข้ารหัสที่คุณต้องการใช้

ดูบทแนะนำที่นี่

นี่คือรหัสสำหรับการเข้ารหัสด้วยรหัสผ่านซึ่งมีรหัสผ่านที่ป้อนผ่าน System.in คุณสามารถเปลี่ยนรหัสผ่านเพื่อใช้รหัสผ่านที่เก็บไว้ได้หากต้องการ

        PBEKeySpec pbeKeySpec;
        PBEParameterSpec pbeParamSpec;
        SecretKeyFactory keyFac;

        // Salt
        byte[] salt = {
            (byte)0xc7, (byte)0x73, (byte)0x21, (byte)0x8c,
            (byte)0x7e, (byte)0xc8, (byte)0xee, (byte)0x99
        };

        // Iteration count
        int count = 20;

        // Create PBE parameter set
        pbeParamSpec = new PBEParameterSpec(salt, count);

        // Prompt user for encryption password.
        // Collect user password as char array (using the
        // "readPassword" method from above), and convert
        // it into a SecretKey object, using a PBE key
        // factory.
        System.out.print("Enter encryption password:  ");
        System.out.flush();
        pbeKeySpec = new PBEKeySpec(readPassword(System.in));
        keyFac = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);

        // Create PBE Cipher
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");

        // Initialize PBE Cipher with key and parameters
        pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);

        // Our cleartext
        byte[] cleartext = "This is another example".getBytes();

        // Encrypt the cleartext
        byte[] ciphertext = pbeCipher.doFinal(cleartext);

3
ฉันจะสร้างคีย์ด้วยรหัสผ่านโดยใช้ KeyGenerator ได้อย่างไร ฉันต้องการสร้างคีย์เดียวกันตามรหัสผ่าน ดังนั้นฉันจึงสามารถถอดรหัสสตริงได้ในภายหลัง
Bernie Perez

สิ่งที่คุณพูดถึงคือการเข้ารหัสตามรหัสผ่านไม่ใช่ AES ฉันอัปเดตคำตอบด้วยโปรแกรมตัวอย่างสำหรับ PBE
Keibosh

5
ลองใช้ตัวสร้างคีย์ PBEKDF2 แทนโดยใช้สตริง "PBKDF2WithHmacSHA1" สำหรับการSecretKeyFactoryเข้ารหัสที่ทันสมัยมากขึ้น
Maarten Bodewes

12
อันที่จริงแล้วการเข้ารหัสดั้งเดิมที่ใช้ทั้งหมดในคำตอบนี้ล้าสมัย MD5 และ DES อย่างแน่นอน จงระวัง.
Maarten Bodewes

MD5 และ DES เป็นชุดการเข้ารหัสที่อ่อนแอและควรหลีกเลี่ยง
atom88

6
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.*;
import java.io.BufferedReader;
import java.io.FileReader;

public class AESFile 
{
private static String algorithm = "AES";
private static byte[] keyValue=new byte[] {'0','2','3','4','5','6','7','8','9','1','2','3','4','5','6','7'};// your key

    // Performs Encryption
    public static String encrypt(String plainText) throws Exception 
    {
            Key key = generateKey();
            Cipher chiper = Cipher.getInstance(algorithm);
            chiper.init(Cipher.ENCRYPT_MODE, key);
            byte[] encVal = chiper.doFinal(plainText.getBytes());
            String encryptedValue = new BASE64Encoder().encode(encVal);
            return encryptedValue;
    }

    // Performs decryption
    public static String decrypt(String encryptedText) throws Exception 
    {
            // generate key 
            Key key = generateKey();
            Cipher chiper = Cipher.getInstance(algorithm);
            chiper.init(Cipher.DECRYPT_MODE, key);
            byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedText);
            byte[] decValue = chiper.doFinal(decordedValue);
            String decryptedValue = new String(decValue);
            return decryptedValue;
    }

//generateKey() is used to generate a secret key for AES algorithm
    private static Key generateKey() throws Exception 
    {
            Key key = new SecretKeySpec(keyValue, algorithm);
            return key;
    }

    // performs encryption & decryption 
    public static void main(String[] args) throws Exception 
    {
        FileReader file = new FileReader("C://myprograms//plaintext.txt");
        BufferedReader reader = new BufferedReader(file);
        String text = "";
        String line = reader.readLine();
    while(line!= null)
        {
            text += line;
    line = reader.readLine();
        }
        reader.close();
    System.out.println(text);

            String plainText = text;
            String encryptedText = AESFile.encrypt(plainText);
            String decryptedText = AESFile.decrypt(encryptedText);

            System.out.println("Plain Text : " + plainText);
            System.out.println("Encrypted Text : " + encryptedText);
            System.out.println("Decrypted Text : " + decryptedText);
    }
}

5
อาจเพิ่มข้อความอธิบายเพิ่มเติม
ChrisG

คำถามอะไรคือจุดที่มีkeyValueอาร์เรย์ไบต์? ฉันเห็นว่ามันถูกใช้เพื่อสร้างคีย์เพราะเหตุใด สิ่งที่สามารถทำได้โดยใช้ like SecretKeyแทน? ถ้าเป็นเช่นนั้นอย่างไร?
Austin

@Mandrek เนื้อหาของไฟล์ "plaintext.txt" จะถูกเข้ารหัส ตรรกะข้างต้นเข้ารหัสข้อมูล / ข้อความในไฟล์ซึ่งอ่านเป็นอาร์กิวเมนต์ในตัวสร้าง FileReader
Shankar Murthy

2

จะใช้งานได้

public class CryptoUtils {

    private  final String TRANSFORMATION = "AES";
    private  final String encodekey = "1234543444555666";
    public  String encrypt(String inputFile)
            throws CryptoException {
        return doEncrypt(encodekey, inputFile);
    }


    public  String decrypt(String input)
            throws CryptoException {
    // return  doCrypto(Cipher.DECRYPT_MODE, key, inputFile);
    return doDecrypt(encodekey,input);
    }

    private  String doEncrypt(String encodekey, String inputStr)   throws CryptoException {
        try {

            Cipher cipher = Cipher.getInstance(TRANSFORMATION);

            byte[] key = encodekey.getBytes("UTF-8");
            MessageDigest sha = MessageDigest.getInstance("SHA-1");
            key = sha.digest(key);
            key = Arrays.copyOf(key, 16); // use only first 128 bit

            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);

            byte[] inputBytes = inputStr.getBytes();     
            byte[] outputBytes = cipher.doFinal(inputBytes);

            return Base64Utils.encodeToString(outputBytes);

        } catch (NoSuchPaddingException | NoSuchAlgorithmException
                | InvalidKeyException | BadPaddingException
                | IllegalBlockSizeException | IOException ex) {
            throw new CryptoException("Error encrypting/decrypting file", ex);
       }
     }


    public  String doDecrypt(String encodekey,String encrptedStr) { 
          try {     

              Cipher dcipher = Cipher.getInstance(TRANSFORMATION);
              dcipher = Cipher.getInstance("AES");
              byte[] key = encodekey.getBytes("UTF-8");
              MessageDigest sha = MessageDigest.getInstance("SHA-1");
              key = sha.digest(key);
              key = Arrays.copyOf(key, 16); // use only first 128 bit

              SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

              dcipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
            // decode with base64 to get bytes

              byte[] dec = Base64Utils.decode(encrptedStr.getBytes());  
              byte[] utf8 = dcipher.doFinal(dec);

              // create new string based on the specified charset
              return new String(utf8, "UTF8");

          } catch (Exception e) {

            e.printStackTrace();

          }
      return null;
      }
 }

2

MD5, AES ไม่มีช่องว่างภายใน

import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static org.apache.commons.io.Charsets.UTF_8;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class PasswordUtils {

    private PasswordUtils() {}

    public static String encrypt(String text, String pass) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            Key key = new SecretKeySpec(messageDigest.digest(pass.getBytes(UTF_8)), "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(ENCRYPT_MODE, key);

            byte[] encrypted = cipher.doFinal(text.getBytes(UTF_8));
            byte[] encoded = Base64.getEncoder().encode(encrypted);
            return new String(encoded, UTF_8);

        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
            throw new RuntimeException("Cannot encrypt", e);
        }
    }

    public static String decrypt(String text, String pass) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            Key key = new SecretKeySpec(messageDigest.digest(pass.getBytes(UTF_8)), "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(DECRYPT_MODE, key);

            byte[] decoded = Base64.getDecoder().decode(text.getBytes(UTF_8));
            byte[] decrypted = cipher.doFinal(decoded);
            return new String(decrypted, UTF_8);

        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
            throw new RuntimeException("Cannot decrypt", e);
        }
    }
}

วิธีสร้างคีย์ที่ปลอดภัยเช่น SecretKeySpec ในเชิงมุม (ไอออนิก 4);
Nitin Karale

0
    byte[] seed = (SALT2 + username + password).getBytes();
    SecureRandom random = new SecureRandom(seed);
    KeyGenerator generator;
    generator = KeyGenerator.getInstance("AES");
    generator.init(random);
    generator.init(256);
    Key keyObj = generator.generateKey();
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.