ga('set', 'anonymizeIp', 1);

[JAVA] AES256 encrypt/decrypt

Share

本文介紹AES加密,並以JAVA實作AES256加解密程式。

AES

AES(Advanced Encryption Standard)高階加密標準,最常見的為對稱加密演算法,對稱加密演算法即為加密解密方均使用同一組密鑰。

加密介紹

代號 意義
明文
密鑰
AES加密函式
AES解密函式
密文

C = E(P,K)
明文(P)使用密鑰(K)透過加密函式(E)產生密文(C)。

P = D(C,K)
密文(C)使用密鑰(K)透過解密函式(D)產生明文(P)。

AES金鑰長度分類

AES 密鑰長度(bytes) 分組長度(bytes) 加密回合(rounds)
AES-128 16 16 10
AES-192 24 16 12
AES-256 32 16 14

AES加密中,明文每個分組只能是128位(16bytes),而金鑰可以是128、192、256位,依照密鑰長度不同,加密回合數也會不同。

限制

AES加密演算法中,原始資料明文(P)長度須為16bytes的倍數,所以當資料不足16bytes時,加密前必須先將資料補足為16bytes,解密後再將之移除還原為原始資料明文。

加密步驟

依照密鑰長度不同,加密回合數(round)也不同。

回合數

AES-128 -> 10 rounds
AES-192 -> 12 rounds
AES-256 -> 14 rounds

加密函數

參考資料:維基百科

  1. Add round key: 對資料(128bits or 16 bytes) ⊕ 回合金鑰。回合金鑰是由主金鑰在每回合(透過Rijndael方法產生)產生之密鑰。
  2. SubBytes:矩陣中的各位元組透過一個8位元的S-box進行轉換。
  3. ShiftRows:每一列都向左循環位移某個偏移量。
  4. MixColumns:每一行的四個位元組透過線性變換互相結合。

每round行為

假設共有N rounds。

加密ROUND behaviers

ROUND 0

  1. Add round key

ROUND 1 ~ ROUND (N-1)

  1. SubBytes()
  2. ShiftRows()
  3. MixColumns()
  4. AddRoundKey()

ROUND N

  1. SubBytes()
  2. ShiftRows()
  3. AddRoundKey()

解密ROUND behaviers

以回合數相反方向開始。

ROUND N

  1. AddRoundKey()
  2. ShiftRows()
  3. SubBytes()

ROUND (N-1) ~ ROUND 1

  1. AddRoundKey()
  2. MixColumns()
  3. ShiftRows()
  4. SubBytes()

ROUND 0

  1. AddRoundKey()

實作 (JAVA)

以下內容使用JAVA實作AES-256加解密。


import java.io.UnsupportedEncodingException;
import java.security.Key;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class AES256 {
    public static final String KEY_ALGORITHM="AES";
    //public static final String CIPHER_ALGORITHM="AES/ECB/PKCS5Padding";
    public static final String CIPHER_ALGORITHM="AES/ECB/NoPadding";

    // Random generate key - not used
    public static byte[] initkey() throws Exception{
        //例項化金鑰生成器
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        KeyGenerator kg=KeyGenerator.getInstance(KEY_ALGORITHM, "BC");
        kg.init(256);
        kg.init(128);
        SecretKey secretKey=kg.generateKey();
        return secretKey.getEncoded();
    }

    // private key table
    public static byte[] initRootKey() throws Exception {
        /*return new byte[] { 0x08, 0x08, 0x04, 0x0b, 0x02, 0x0f, 0x0b, 0x0c,
        0x01, 0x03, 0x09, 0x07, 0x0c, 0x03, 0x07, 0x0a, 0x04, 0x0f,
        0x06, 0x0f, 0x0e, 0x09, 0x05, 0x01, 0x0a, 0x0a, 0x01, 0x09,
        0x06, 0x07, 0x09, 0x0d };*/

        return new byte[] { (byte) 0x9D, (byte) 0xD2, 0x00, 0x24, (byte) 0x84, 0x6A, 0x2E, (byte) 0xDA,
                0x0C, (byte) 0xDD, 0x72, 0x7B, 0x05, (byte) 0xC5, 0x6B, 0x01,
                (byte) 0xFF, 0x17, (byte) 0xCD, (byte) 0x9F, (byte) 0x8C, 0x1E, 0x3E, 0x09,
                (byte) 0xCF, 0x2F, 0x0C, 0x77, (byte) 0x87, (byte) 0xEF, (byte) 0x8A, (byte) 0xEC };
    }

    // generate key from private key
    public static Key toKey(byte[] key) throws Exception {
        SecretKey secretKey=new SecretKeySpec(key,KEY_ALGORITHM);
        return secretKey;
    }

    // encrypt 
    public byte[] encrypt(byte[] data) throws Exception {
        Key k = toKey(initRootKey());
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, "BC");
        cipher.init(Cipher.ENCRYPT_MODE, k);
        return cipher.doFinal(data);
    }

    // decrypt 
    public byte[] decrypt(byte[] data) throws Exception {
        Key k =toKey(initRootKey());
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        Cipher cipher=Cipher.getInstance(CIPHER_ALGORITHM, "BC");
        cipher.init(Cipher.DECRYPT_MODE, k);
        return cipher.doFinal(data);
    }

    // hex to byte convert 
    public static byte[] hexToBytes(String hexString) {

        char[] hex = hexString.toCharArray();
        //轉rawData長度減半
        int length = hex.length / 2;
        byte[] rawData = new byte[length];
        for (int i = 0; i  10000000 (8=>128)
          //然後與第二個值的二進位值作聯集ex: 10000000 | 00001100 => 10001100 (137)
          int value = (high  127)
            value -= 256;
          //最後轉回byte就OK
          rawData [i] = (byte) value;
        }
        return rawData ;
    }

    // byte to hex convert 
    public static String byte2Hex(byte[] bytes){
        StringBuffer stringBuffer = new StringBuffer();
        String temp = null;
        for (int i=0;i getStrListEnter(String inputString, int length) {
        int size = inputString.length() / length;
        if (inputString.length() % length != 0) {
            size += 1;
        }
        return getStrList(inputString, length, size);
    }
    public static List getStrList(String inputString, int length,
            int size) {
        List list = new ArrayList();
        for (int index = 0; index  str.length())
            return null;
        if (t > str.length()) {
            return str.substring(f, str.length());
        } else {
            return str.substring(f, t);
        }
    }
}

程式碼解析

首先我們使用的是AES加密算法


public static final String KEY_ALGORITHM="AES";

接著是以參數定義transformation,


public static final String CIPHER_ALGORITHM="AES/ECB/NoPadding";

參數意義如下表:

參數 意義
algorithm AES 必填,使用之加密算法(EX: RSA, AES, SHA-256等)
mode ECB 非必填,分組密碼運作模式。詳細請參考下方"Mode表"。
padding NoPadding 非必填,指定每次處理的bit數量。詳細請參考下方"Transform表"。

Mode表

Mode 名稱 描述
ECB Electronic CodeBook 使用相同密鑰對明文分組加密
CBC Cipher Block Chaining 加密算法之input為上個密文組合下個明文組之XOR。(加密算法=前一密文組⊕下一明文組)
CFB Cipher FeedBack 與CBC相似,前一密文作為加密算法之input,產生之密文MSB (X bit)與明文之X bit XOR後產生密文之X bit。
OFB Output FeedBack 與CFB相似,只是加密算法之input為前次加密算法之output。
CTR Counter 每組明文都與一經過加密之counter XOR,之後counter遞增。

Transform表

Transform 16byte加密後長度 不滿16byte加密後長度
AES/CBC/NoPadding 16 不支援
AES/CBC/PKCS5Padding 32 16
AES/CBC/ISO10126Padding 32 16
AES/CFB/NoPadding 16 原始數據長度
AES/CFB/PKCS5Padding 32 16
AES/CFB/ISO10126Padding 32 16
AES/ECB/NoPadding 16 不支援
AES/ECB/PKCS5Padding 32 16
AES/ECB/ISO10126Padding 32 16
AES/OFB/NoPadding 16 原始數據長度
AES/OFB/PKCS5Padding 32 16
AES/OFB/ISO10126Padding 32 16
AES/PCBC/NoPadding 16 不支援
AES/PCBC/PKCS5Padding 32 16
AES/PCBC/ISO10126Padding 32 16

金鑰 (私鑰)

我們可以初始化一固定金鑰作為密鑰,也可以自動生成。

固定金鑰:
Key之byte[] 長度為16bytes。


public static byte[] initRootKey() throws Exception {return new byte[] { (byte) 0x9D, (byte) 0xD2, 0x00, 0x24, (byte) 0x84, 0x6A, 0x2E, (byte) 0xDA,
                0x0C, (byte) 0xDD, 0x72, 0x7B, 0x05, (byte) 0xC5, 0x6B, 0x01,
                (byte) 0xFF, 0x17, (byte) 0xCD, (byte) 0x9F, (byte) 0x8C, 0x1E, 0x3E, 0x09,
                (byte) 0xCF, 0x2F, 0x0C, 0x77, (byte) 0x87, (byte) 0xEF, (byte) 0x8A, (byte) 0xEC };}
                

自動生成金鑰


public static byte[] initkey() throws Exception{//例項化金鑰生成器
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        KeyGenerator kg=KeyGenerator.getInstance(KEY_ALGORITHM, "BC");
        kg.init(256);
        kg.init(128);
        SecretKey secretKey=kg.generateKey();
        return secretKey.getEncoded();}
        

加解密Cipher屬性設定

Cipher之7屬性:

Modifier and Type Field and Description
static int DECRYPT_MODE用於解密模式下的 Cipher 初始化。
static int ENCRYPT_MODE用於加密模式下的 Cipher 初始化。
static int PRIVATE_KEY用於說明解包模式下密鑰是是私鑰。
static int PUBLIC_KEY用於說明解包模式下密鑰是公鑰。
static int SECRET_KEY用於說明解包模式下密鑰是密鑰。
static int UNWRAP_MODE用於解包密鑰模式下的 Cipher 初始化。
static int WRAP_MODE用於包裝密鑰模式下的 Cipher 初始化。
cipher.init(Cipher.ENCRYPT_MODE, k);

JAVA – 加密預設金鑰長度限制

如出現以下錯誤訊息:
java.security.InvalidKeyException: Illegal key size or default parameters

請先至Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 下載policy檔案,後將local_policy.jar及US_export_policy.jar 取代jre/security底下的兩個檔案即可解除JAVA金鑰長度問題。

可能需用到之Functions

使用不同的Padding方式會影響當分組明文不足16bytes之加密過程,明文的編碼不同也需要先轉換編碼再進行加密。下方列出進行加密前可能所需的額外明文處理funtions。

HEX String to Byte[]


// hex to byte convert 
    public static byte[] hexToBytes(String hexString) {

        char[] hex = hexString.toCharArray();
        //轉rawData長度減半
        int length = hex.length / 2;
        byte[] rawData = new byte[length];
        for (int i = 0; i  10000000 (8=>128)
          //然後與第二個值的二進位值作聯集ex: 10000000 | 00001100 => 10001100 (137)
          int value = (high  127)
            value -= 256;
          //最後轉回byte就OK
          rawData [i] = (byte) value;
        }
        return rawData ;
    }
    

Byte[] to HEX String


// byte to hex convert 
    public static String byte2Hex(byte[] bytes){
        StringBuffer stringBuffer = new StringBuffer();
        String temp = null;
        for (int i=0;i

String Split According to Input Length


 // string split s
    public static List getStrListEnter(String inputString, int length) {
        int size = inputString.length() / length;
        if (inputString.length() % length != 0) {
            size += 1;
        }
        return getStrList(inputString, length, size);
    }
    public static List getStrList(String inputString, int length,
            int size) {
        List list = new ArrayList();
        for (int index = 0; index  str.length())
            return null;
        if (t > str.length()) {
            return str.substring(f, str.length());
        } else {
            return str.substring(f, t);
        }
    }
    

以上為JAVA實作AES256加解密之方法。

Jys

Published by
Jys

Recent Posts

[python] Flask Create RESTful API

This article gi... Read More

3 年 前發表

[Javascript] 新增/刪除JSON中key值

在web訊息交換常會需要對JS... Read More

3 年 前發表

[JAVA] SQL Server Connection

本文介紹JAVA連線SQL s... Read More

3 年 前發表