ホームページ  >  記事  >  Java  >  Java で AES 暗号化および復号化操作を実行する方法

Java で AES 暗号化および復号化操作を実行する方法

WBOY
WBOY転載
2023-05-08 22:34:162606ブラウズ

1. 背景知識

Java で AES 暗号化および復号化操作を実行する方法

  • 暗号化では、暗号化アルゴリズムは一方向暗号化と双方向暗号化に分類されます。

    • 対称暗号化とは、AES 暗号化、DES 暗号化などを含む、暗号化と復号化に同じキーが使用されることを意味します。

    • 非対称暗号化とは、RSA 暗号化などを含む、暗号化と復号化で異なる​​キーを使用することを意味します。

    • 一方向暗号化には、MD5 や SHA などの元に戻せないダイジェスト アルゴリズムが含まれます。

    • 双方向暗号化には、対称暗号化と非対称暗号化が含まれます。双方向暗号化は元に戻すことができ、暗号文へのキーが存在します。

2. AES の概要

AES: Advanced Encryption Standard は米国連邦政府によって採用されています A ブロック暗号化現在最も人気のある 対称暗号化アルゴリズム である標準。

  • は、DES を置き換えるために使用される新世代のブロック暗号化アルゴリズムです。

  • AES は、128 ビット、192 ビット、256 ビットの 3 つのキー長をサポートします。

3. AES 暗号化プロセス (AES 処理単位: バイト)

AES の暗号化および復号化プロセスは、ブロック暗号化を介して DES と同じです。 、グループ復号化。いわゆるブロック暗号化とは、暗号化および復号するコンテンツを 128 ビットごとにグループ化し、鍵を 128 ビット、192 ビット、256 ビットごとにグループ化し、グループ化された平文とそれに対応する暗号化および復号化を行うものです。それぞれグループ化されたキー。

暗号化: 平文と鍵がグループ化された後、グループごとに: 平文グループと鍵グループの処理 -> ラウンド鍵の追加 -> 10 ラウンドの暗号化 -> 暗号文グループ

復号化: 各グループ: 暗号文グループ -> ラウンド鍵追加 -> 10 ラウンドの復号化 -> 平文グループ

平文グループ化: 各グループの長さは同じで、両方とも 128 ビット (16 バイト);

キーのグループ化: 128 ビット、192 ビット、および 256 ビットがあります。推奨される暗号化数ラウンド数はそれぞれ 10、12、14 です。

キー グループの処理: 128 ビットのキー グループを例にとります (推奨される暗号化ラウンド数は 10 であり、演算は最初の9回は同じ、10回目は異なる) 同様に、128ビットの鍵もバイト単位の行列で表現され、鍵配列関数により44個の要素の列W[0]が形成され、 W[1]、…、W[43] (各要素は 4 バイト); このうち、W[0]、W[1]、W[2]、W[3] が元のキーで、残りは40 個の要素は 10 のグループに分割され、各グループ 4 要素 (4*4=16 バイト) がそれぞれ 10 ラウンドの暗号化に使用されます。

AES 暗号化アルゴリズムには 4 つの操作が含まれます: バイト置換 (サブバイト)、行シフト (ShiftRows)、 列混合 (MixColumns) および ラウンド キーと (AddRoundKey)。

次の図は、AES 暗号化と復号化のプロセスを示しています:

  • AddRoundKey (ラウンド キーの追加)— 行列内の各単語 各サブキーラウンドキーと XOR 演算され、各サブキーはキー生成スキームによって生成されます。

  • SubBytes (バイト置換) - 非線形置換関数により、ルックアップ テーブルを使用して各バイトが対応するバイトに置換されます。

  • ShiftRows (行シフト) — マトリックスの各列を循環的にシフトします。

  • MixColumns (列の混乱)— マトリックスの各直線行の演算を完全に混合するため。このステップでは、線形変換を使用して各列の 4 バイトをブレンドします。

Java で AES 暗号化および復号化操作を実行する方法

#平文の長さが 192 ビットで、128 ビットごとに 1 つの平文ブロックに分割される場合、2 番目の平文ブロックはは 64 ビットのみであり、128 ビットよりも小さいです。この時どうすればいいでしょうか?平文ブロックをパディング(Padding)する必要があります。

パディングには次の

3 つのパディング モードが含まれます:

  • NoPadding: パディングは行われませんが、プレーン テキストはパディングされなければなりません。 be 16 バイトの整数倍。

  • PKCS5Padding (デフォルト): 平文ブロックが 16 バイト (128 ビット) 未満の場合、平文ブロックの末尾に対応する文字数を追加します。および各単語セクションの値は、欠落している文字の数に等しくなります。

たとえば、プレーン テキスト: {1,2,3,4,5,a,b,c,d,e}、6 バイトが欠落している場合、補完は { 1、2、3、4、5、a、b、c、d、e、6、6、6、6、6、6}

  • ISO10126パディング##注※: 平文ブロックが 16 バイト(128 ビット)未満の場合、平文ブロックの末尾に該当するバイト数が追加され、最後の文字値は欠落文字数に等しく、その他の文字は埋められます。乱数。

  • 例: プレーン テキスト: {1,2,3,4,5,a,b,c,d,e}、6 バイトが欠けていても完了する可能性があります{1 ,2,3,4,5,a,b,c,d,e,5,c,3,G,$,6}

4.Java 実装

として

注: AES 暗号化には Base64 暗号化が含まれます

##暗号化:
AES 暗号化 -> Base64 暗号化 -> 暗号文

復号化:

Base64 復号化 -> AES 復号化 -> プレーン テキスト

テスト アドレス: ここをクリック

4.1 キーとオフセットの生成

大文字と小文字と数字の 16 個の乱数を生成します:

private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 数字和26个字母组成
private static final Random RANDOM = new SecureRandom();
/**
 * 获取长度为 6 的随机字母+数字
 * @return 随机数字
 */
public static String getRandomNumber() {
    char[] nonceChars = new char[16];  //指定长度为6位/自己可以要求设置
    for (int index = 0; index < nonceChars.length; ++index) {
        nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
    }
    return new String(nonceChars);
}

4.2 AESUtil.java ソース コード

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
 * AES加密工具类
 *
 * @author ACGkaka
 * @since 2021-06-18 19:11:03
 */
public class AESUtil {
    /**
     * 日志相关
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(AESUtil.class);
    /**
     * 编码
     */
    private static final String ENCODING = "UTF-8";
    /**
     * 算法定义
     */
    private static final String AES_ALGORITHM = "AES";
    /**
     * 指定填充方式
     */
    private static final String CIPHER_PADDING = "AES/ECB/PKCS5Padding";
    private static final String CIPHER_CBC_PADDING = "AES/CBC/PKCS5Padding";
    /**
     * 偏移量(CBC中使用,增强加密算法强度)
     */
    private static final String IV_SEED = "1234567812345678";
    /**
     * AES加密
     * @param content 待加密内容
     * @param aesKey  密码
     * @return
     */
    public static String encrypt(String content, String aesKey){
        if(StringUtils.isBlank(content)){
            LOGGER.info("AES encrypt: the content is null!");
            return null;
        }
        //判断秘钥是否为16位
        if(StringUtils.isNotBlank(aesKey) && aesKey.length() == 16){
            try {
                //对密码进行编码
                byte[] bytes = aesKey.getBytes(ENCODING);
                //设置加密算法,生成秘钥
                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
                // "算法/模式/补码方式"
                Cipher cipher = Cipher.getInstance(CIPHER_PADDING);
                //选择加密
                cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
                //根据待加密内容生成字节数组
                byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));
                //返回base64字符串
                return Base64Utils.encodeToString(encrypted);
            } catch (Exception e) {
                LOGGER.info("AES encrypt exception:" + e.getMessage());
                throw new RuntimeException(e);
            }
        }else {
            LOGGER.info("AES encrypt: the aesKey is null or error!");
            return null;
        }
    }
    /**
     * 解密
     * 
     * @param content 待解密内容
     * @param aesKey  密码
     * @return
     */
    public static String decrypt(String content, String aesKey){
        if(StringUtils.isBlank(content)){
            LOGGER.info("AES decrypt: the content is null!");
            return null;
        }
        //判断秘钥是否为16位
        if(StringUtils.isNotBlank(aesKey) && aesKey.length() == 16){
            try {
                //对密码进行编码
                byte[] bytes = aesKey.getBytes(ENCODING);
                //设置解密算法,生成秘钥
                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
                // "算法/模式/补码方式"
                Cipher cipher = Cipher.getInstance(CIPHER_PADDING);
                //选择解密
                cipher.init(Cipher.DECRYPT_MODE, skeySpec);
                //先进行Base64解码
                byte[] decodeBase64 = Base64Utils.decodeFromString(content);
                //根据待解密内容进行解密
                byte[] decrypted = cipher.doFinal(decodeBase64);
                //将字节数组转成字符串
                return new String(decrypted, ENCODING);
            } catch (Exception e) {
                LOGGER.info("AES decrypt exception:" + e.getMessage());
                throw new RuntimeException(e);
            }
        }else {
            LOGGER.info("AES decrypt: the aesKey is null or error!");
            return null;
        }
    }
    /**
     * AES_CBC加密
     * 
     * @param content 待加密内容
     * @param aesKey  密码
     * @return
     */
    public static String encryptCBC(String content, String aesKey){
        if(StringUtils.isBlank(content)){
            LOGGER.info("AES_CBC encrypt: the content is null!");
            return null;
        }
        //判断秘钥是否为16位
        if(StringUtils.isNotBlank(aesKey) && aesKey.length() == 16){
            try {
                //对密码进行编码
                byte[] bytes = aesKey.getBytes(ENCODING);
                //设置加密算法,生成秘钥
                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
                // "算法/模式/补码方式"
                Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);
                //偏移
                IvParameterSpec iv = new IvParameterSpec(IV_SEED.getBytes(ENCODING));
                //选择加密
                cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
                //根据待加密内容生成字节数组
                byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));
                //返回base64字符串
                return Base64Utils.encodeToString(encrypted);
            } catch (Exception e) {
                LOGGER.info("AES_CBC encrypt exception:" + e.getMessage());
                throw new RuntimeException(e);
            }
        }else {
            LOGGER.info("AES_CBC encrypt: the aesKey is null or error!");
            return null;
        }
    }
    /**
     * AES_CBC解密
     * 
     * @param content 待解密内容
     * @param aesKey  密码
     * @return
     */
    public static String decryptCBC(String content, String aesKey){
        if(StringUtils.isBlank(content)){
            LOGGER.info("AES_CBC decrypt: the content is null!");
            return null;
        }
        //判断秘钥是否为16位
        if(StringUtils.isNotBlank(aesKey) && aesKey.length() == 16){
            try {
                //对密码进行编码
                byte[] bytes = aesKey.getBytes(ENCODING);
                //设置解密算法,生成秘钥
                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
                //偏移
                IvParameterSpec iv = new IvParameterSpec(IV_SEED.getBytes(ENCODING));
                // "算法/模式/补码方式"
                Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);
                //选择解密
                cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
                //先进行Base64解码
                byte[] decodeBase64 = Base64Utils.decodeFromString(content);
                //根据待解密内容进行解密
                byte[] decrypted = cipher.doFinal(decodeBase64);
                //将字节数组转成字符串
                return new String(decrypted, ENCODING);
            } catch (Exception e) {
                LOGGER.info("AES_CBC decrypt exception:" + e.getMessage());
                throw new RuntimeException(e);
            }
        }else {
            LOGGER.info("AES_CBC decrypt: the aesKey is null or error!");
            return null;
        }
    }
    public static void main(String[] args) {
        // AES支持三种长度的密钥:128位、192位、256位。
        // 代码中这种就是128位的加密密钥,16字节 * 8位/字节 = 128位。
        String random = RandomStringUtils.random(16, "abcdefghijklmnopqrstuvwxyz1234567890");
        System.out.println("随机key:" + random);
        System.out.println();
        System.out.println("---------加密---------");
        String aesResult = encrypt("测试AES加密12", random);
        System.out.println("aes加密结果:" + aesResult);
        System.out.println();
        System.out.println("---------解密---------");
        String decrypt = decrypt(aesResult, random);
        System.out.println("aes解密结果:" + decrypt);
        System.out.println();
        System.out.println("--------AES_CBC加密解密---------");
        String cbcResult = encryptCBC("测试AES加密12456", random);
        System.out.println("aes_cbc加密结果:" + cbcResult);
        System.out.println();
        System.out.println("---------解密CBC---------");
        String cbcDecrypt = decryptCBC(cbcResult, random);
        System.out.println("aes解密结果:" + cbcDecrypt);
        System.out.println();
    }
}

4.3実行結果

随机key:golrtt58318fx7ol
---------加密---------
aes加密结果:Xy8W9lCeVue9Ao36z+duM7D7WeS5tdBihIMb1q9KpNg=
---------解密---------
aes解密结果:测试AES加密12
--------AES_CBC加密解密---------
aes_cbc加密结果:xs3ypQXyd62P9jB0+RvOqxFnHIHBIlVdqoZLuqYNBLw=
---------解密CBC---------
aes解密结果:测试AES加密12456

4.4 オンライン検証

Java で AES 暗号化および復号化操作を実行する方法

以上がJava で AES 暗号化および復号化操作を実行する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。