2013年7月31日 星期三

[Java] 使用Java實做,AES encrypt and decrypt

AES (Advanced Encryption Standard,AES)是用來取代DES的新的進階加密法,又稱Rijndael加密法,是美國聯邦政府採用的一種區塊加密標準。
 
AES 有幾個概念,
  • Key : 長度分成128bit(16byte)、192bit(24byte)和256bit(byte)
  • Mode :
    • ECB:為基礎加密模式,plain text 被分割成相同長度的區塊,然後再一一的加密組成cipher text。(本文使用模式)
    • CBC:是一種循環模式,前一個分組的密文和當前分組的明文異或操作後再加密,這樣做的目的是增強破解難度。
    • CFB/OFB 實際上是一種反饋模式,目的也是增強破解的難度。
    • ECB和CBC 的加密結果是不一樣的,兩者的模式不同,而且CBC會在第一個密碼塊運算時加入一個初始化向量。 
       
  • PKCS5Padding : 當一次加密的長度不足16byte時,須做填充補足,而PKCS5Padding及填充的方式。 
Java本身就提供了完整的crypt library( javax.crypto.* ),只要有安裝完整的JDK,就不需要在額外安裝或下載jar file。

  • Java 支援的功能
    • AES/CBC/NoPadding (128)
    • AES/CBC/PKCS5Padding (128)
    • AES/ECB/NoPadding (128)
    • AES/ECB/PKCS5Padding (128)

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

class Convert {
   
    /**
     * str2byte : String to Byte
     * Eg. 00010203 --> 0x00 0x01 0x02 0x03
     */
    public static byte[] str2byte(String key)
    {
        byte[] b = new byte[key.length()/2];

        for(int i=0, bStepper=0; i<key.length()+2; i+=2)
            if(i !=0)
                b[bStepper++]=((byte)Integer.parseInt((key.charAt(i-2)+""+key.charAt(i-1)), 16));

        return b;
    }
   
    /**
     * byte2str : Byte to String
     * Eg. 0x00 0x01 0x02 0x03 --> 00010203
     */
    public static String byte2str (byte buf[]) {
        StringBuffer strbuf = new StringBuffer(buf.length * 2);
        int i;

        for (i = 0; i < buf.length; i++) {   
            if (((int) buf[i] & 0xff) < 0x10)
                strbuf.append("0");
            strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
            strbuf.append(" ");
        }
        return strbuf.toString();
    }
}

class AESEncrypt {
    private SecretKeySpec skeySpec;
    private Cipher cipher;
    private Cipher noPadding;
    private Cipher padding;
    private byte[] key;
   
    public AESEncrypt() throws NoSuchAlgorithmException, NoSuchPaddingException{
        KeyGenerator keyG = KeyGenerator.getInstance("AES");
        keyG.init(128);
        SecretKey secuK = keyG.generateKey();
        this.key = secuK.getEncoded();
        init();
    }
   
    public AESEncrypt(String key) throws NoSuchAlgorithmException, NoSuchPaddingException{
        this.key = Convert.str2byte(key);
        init();
    }
   
    private void init() throws NoSuchAlgorithmException, NoSuchPaddingException{
        this.skeySpec = new SecretKeySpec(this.key, "AES");
        this.noPadding = Cipher.getInstance("AES/ECB/NoPadding"); 
        this.padding = Cipher.getInstance("AES/ECB/PKCS5Padding");
        this.cipher = noPadding;
    }
   
    public void setNoPadding() {
        this.cipher = noPadding;
    }
   
    public void setPKCS5Padding() {
        this.cipher = padding;
    }
   
    public  byte[] encrypt( byte[] byteArray, int offset, int len ) throws Exception{
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        return cipher.doFinal(byteArray, offset, len);
    }
   
    public  byte[] encrypt( byte[] byteArray ) throws Exception{
        return encrypt( byteArray, 0, byteArray.length);
    }
   
    public  byte[] decrypt( byte[] byteArray, int offset, int len ) throws Exception{
        this.cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        return cipher.doFinal(byteArray, offset, len);
    }   
   
    public  byte[] decrypt( byte[] byteArray ) throws Exception{
        return decrypt( byteArray, 0, byteArray.length);
    }
   
    public void encryptFile(FileInputStream fim, FileOutputStream fom) throws Exception {
         byte[] buffer = new byte[16];
         int length = -1;
       
         while((length = fim.read(buffer, 0, buffer.length)) != -1) {
             if ( length < buffer.length ) setPKCS5Padding();
             else setNoPadding();
           
             byte[] decoder = encrypt(buffer, 0, length);
             fom.write(decoder, 0, decoder.length);
         }
    }
   
    public void decryptFile(FileInputStream fim, FileOutputStream fom) throws Exception {
        byte[] buffer = new byte[8192];
        int length = -1;
      
        while((length = fim.read(buffer,0,buffer.length)) != -1) {
            if ( length < buffer.length ) setPKCS5Padding();
            else setNoPadding();
           
            byte[] decoder = decrypt(buffer, 0, length);
            fom.write(decoder, 0, decoder.length);
        }
    }
}

public class SampleAES {
   
    public static void demoStringCrypt() throws Exception {
        System.out.println("### Demo String Encrypt and Decrypt");
       
        String plainText = "HelloWorld";
       
        // 128 bit
        AESEncrypt aes = new AESEncrypt("000102030405060708090A0B0C0D0E0F");
       
        // encode
        if ( (plainText.getBytes().length % 16) == 0) aes.setNoPadding();
        else aes.setPKCS5Padding();
        byte[] encoder = aes.encrypt(plainText.getBytes());
       
        System.out.println("Encoder: " + Convert.byte2str(encoder));
       
        // decode
        aes.setPKCS5Padding();
        String decoder = new String(aes.decrypt(encoder));
        System.out.println("Decoder: " + decoder);
    }
   
    public static void demoFileCrypt()  throws Exception {
        System.out.println("### Demo File Encrypt and Decrypt");
       
        String plainFile = "plainText.txt";
        String encodeFile = "encode.txt";
        String decodeFile = "decode.txt";
       
        FileInputStream plainText =
                new FileInputStream(new File(plainFile));
        
        FileOutputStream encode =
                new FileOutputStream(new File(encodeFile));
   
        AESEncrypt aes = new AESEncrypt();
       
        // encrypt file
        System.out.println(plainFile + " encrypt to " + encodeFile);
        aes.encryptFile(plainText, encode);
       
        FileInputStream cipherText =
                new FileInputStream(new File(encodeFile));
       
        FileOutputStream decode =
                new FileOutputStream(new File(decodeFile));
       
        // decrypt file
        System.out.println(encodeFile + " decrypt to " + decodeFile);
        aes.decryptFile(cipherText, decode);
       

        plainText.close();
        encode.close();
        cipherText.close();
        decode.close();   
    }
   
    public static void main(String[] args) throws Exception {
       
        demoStringCrypt();

        demoFileCrypt();
     }
}

  •  Note: 以下記錄在撰寫中發生的Error Message
    • Give final block not properly padded : 再測試加密檔案時,一直發生此錯誤,後來發現是由padding的所造成的,在進行加解密時,都必須確認要做加解密的陣列, 是否需要做padding,需要則呼叫setPKCS5Padding(),不需要則呼叫setNoPadding()。


Reference : 
[1] 分组对称加密模式:ECB/CBC/CFB/OFB
[2] Java AES Encrypt & Decrypt Example(加解密)
[3] 使用 Java 進行 AES 加密
[4] Class Cipher

沒有留言:

張貼留言