暗号化および復号化テクノロジーの基本ガイド (Base64、Hex、AES、SM4、RSA アルゴリズム)

暗号化の概要

暗号化技術は、情報をエンコードおよびデコードする技術です。エンコードは、元の読み取り可能な情報 (平文とも呼ばれます) をコード形式 (暗号文ともいいます) に変換することです。その逆のプロセスはデコード (復号化) です。暗号化技術のキーポイントは暗号化アルゴリズムであり、次の 3 つのカテゴリに分類できます。

  • AES、SM4(国家機密)などの対称暗号化
    • 基本原理: 平文を N 個のグループに分割し、キーを使用して各グループを暗号化してそれぞれの暗号文を形成し、最後にグループ化されたすべての暗号文を結合して最終的な暗号文を形成します。
    • 利点: オープンなアルゴリズム、少ない計算量、速い暗号化速度、高い暗号化効率
    • 欠点: 双方が同じキーを使用するため、セキュリティが保証されない
  • RSA、git の ssh 公開キーおよび秘密キーなどの非対称暗号化
    • 基本原理: 秘密鍵と公開鍵の 2 つの鍵が同時に生成されます。秘密鍵は秘密に保たれ、公開鍵は信頼できるクライアントに送信できます。秘密鍵は暗号化に使用され、公開鍵は復号化に使用されます。
      • 秘密キー暗号化。秘密キーまたは公開キーを保持しているだけで復号化できます。
      • 公開キー暗号化。秘密キーのみを復号化できます。
    • 長所: 安全、クラックされにくい
    • 欠点: アルゴリズムに時間がかかる
    • 非対称アルゴリズムは通常、対称暗号化アルゴリズムのキーを送信するために使用されます。
  • MD5、SHAなどの不可逆暗号化
    • 基本原理: 暗号化プロセスにキーを使用する必要はありません. 平文が入力されると、システムは暗号化アルゴリズムを通じて直接暗号文に処理します. この暗号化されたデータは復号化できず、暗号文から平文を計算することはできません.

エンコーダ

Base64

原理分析:ジャンプ URL

Base64 は、インターネット上で 8 ビット バイトコードを送信するための最も一般的なエンコード方式の 1 つで、64 個の印刷可能な文字に基づいてバイナリ データを表現する方式です。

HTTPプロトコルでバイナリデータを送信するにはBase64が一般的ですが、HTTPプロトコルはテキストプロトコルであるため、HTTPプロトコルでバイナリデータを送信するにはバイナリデータを文字データに変換する必要があります。ただし、直接変換はできません。ネットワーク送信では印刷可能な文字しか送信できないためです。

**印刷可能文字:**ASCII コードでは、0 ~ 31 と 127 の 33 文字が制御文字、32 ~ 126 の 95 文字が印刷可能文字とされており、ネットワーク送信ではこの 95 文字しか送信できず、この範囲外の文字は送信できません。では、他の文字はどのようにして伝わるのでしょうか?1 つの方法は、Base64 を使用することです。

ほとんどのエンコードは文字列をバイナリに変換するプロセスですが、Base64 エンコードはバイナリを文字列に変換します。普通とはまったく逆です。

Base64エンコードは主に伝送、保存、バイナリ表現の分野で使用されており、暗号化とは言えませんが、平文を直接見ることはできません。Base64 エンコードをシャッフルすることによって暗号化を行うこともできます。

中国語には複数のエンコーディング (例: utf-8、gb2312、gbk など) があり、異なるエンコーディングは異なる Base64 エンコーディング結果に対応します。


java.util.Base64 ユーティリティ クラス: このクラスは、エンコーダとデコーダが Base64 エンコード スキームを取得するための静的メソッドのみで構成されます。

効果:

  • Base64 内のエンコーダーを使用してデータをエンコード (暗号化) します。
  • Base64 のデコーダーを使用してデータをデコード (復号化) します。

Base64 ツールクラスの静的メソッド:

// 使用Basic型base64编码方案
static Base64.Encoder getEncoder() 		// 获取加密器(编码器)
static Base64.Decoder getDecoder() 		// 获取解密器(解码器)
    
// 使用MIME型base64编码方案
static Base64.Encoder getMineEncoder() 		// 获取加密器(编码器)
static Base64.Decoder getMineDecoder() 		// 获取解密器(解码器)
static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator)	// 指定行长度和行分隔符获取加密器
    
// 使用 URL and Filename safe 型base64编码方案
static Base64.Encoder getUrlEncoder() 		// 获取加密器(编码器)
static Base64.Decoder getUrlDecoder() 		// 获取解密器(解码器)

java.util.Base64.Encoder: データの暗号化に使用される Base64 の内部クラスです。

Encoder のメンバー メソッド:

String encodeToString(byte[] src) 		// 使用Base64编码方案将指定的字节数组编码为字符串。

java.util.Base64.Decoder: データの復号化に使用される Base64 の内部クラスです。

Decoder のメンバー メソッド:

 byte[] decode(String src) 		// 使用Base64编码方案将Base64编码的字符串解码为新分配的字节数组。

Hex (16 進数)

16進数、英語名:Hex Number System(ヘックスナンバーシステム、略称:hex)とは、16を基数とする数え方で、16ごとに1を加える進数法です。

通常は0~9、A~Fで構成されており、このうちA~Fは10~15を表し、これらを16進数と呼びます。


カスタム ツール クラス: EncoderUtils

import java.util.Base64;

public class EncoderUtils {
    
    

    public static final String UTF_8 = "UTF-8";
    public static final String BASE_64 = "base64";
    public static final String HEX = "hex";

    /**
     * base64 编码
     */
    public static String encodeByteToBase64(byte[] byteArray) {
    
    
        return Base64.getEncoder().encodeToString(byteArray);
    }

    /**
     * base64 解码
     */
    public static byte[] decodeBase64ToByte(String base64EncodedString) {
    
    
        return Base64.getDecoder().decode(base64EncodedString);
    }

    /**
     * 将二进制转换成十六进制(Hex)
     */
    public static String parseByte2HexStr(byte[] buf){
    
    
        StringBuilder sb = new StringBuilder();
        for (byte b : buf) {
    
    
            String hex = Integer.toHexString(b & 0xFF);
            if (hex.length() == 1) {
    
    
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

    /**
     * 将十六进制(Hex)转换成二进制
     */
    public static byte[]  parseHexStr2Byte(String hexStr){
    
    
        if (hexStr.length() < 1){
    
    
            return null;
        } else {
    
    
            int half = hexStr.length() / 2;
            byte[] result = new byte[half];
            for (int i = 0; i < half; i++) {
    
    
                int high = Integer.parseInt(hexStr.substring(i*2, i*2 + 1), 16);
                int low = Integer.parseInt(hexStr.substring(i*2 + 1, i*2 + 2), 16);
                result[i] = (byte) (high * 16 + low);
            }
            return result;
        }
    }
}

対称暗号化

AESアルゴリズム

概要

AES 暗号化アルゴリズムの正式名は Advanced Encryption Standard (Advanced Encryption Standard) で、最も一般的な対称暗号化アルゴリズムの 1 つです。

AES 暗号化には次のものが必要です: 平文 + キー + オフセット (IV) + 暗号モード (アルゴリズム/モード/パディング)

AES 復号化には次のものが必要です: 暗号文 + キー + オフセット (IV) + 暗号モード (アルゴリズム/モード/パディング)

AES のパスワード モードは通常 AES/CBC/PKCS5Padding です。

  • AES: 暗号化および復号化アルゴリズム

  • CBC: データパケットモード

  • PKCS5Padding: データを一定のサイズでグループ化し、最後に残ったグループを分割します。長さが足りない場合は埋める必要があります。パディング モードとも呼ばれます。


実際の業務における暗号化処理

実際の作業では、クライアントとサーバー間の対話は通常文字列形式であるため、より適切な暗号化プロセスは次のとおりです。

  • 暗号化プロセス: 平文はキーを通過し (場合によってはオフセットも必要)、AES 暗号化アルゴリズムを使用し、Base64 でトランスコードし、最後に暗号化された文字列を生成します。
  • 復号化プロセス: 暗号化された文字列はキーを渡し (オフセットも必要な場合もあります)、AES 復号化アルゴリズムを使用し、Base64 でトランスコードし、最後に復号化された文字列を生成します。

データパケットモード

AES データ パケット モードは次のとおりです。

  • 電子コードブック モード (電子コードブック、ECB): 暗号化されるメッセージは、ブロック暗号のブロック サイズに従っていくつかのブロックに分割され、各ブロックが独立して暗号化されます。

  • 電卓モード (CTR)

  • 暗号フィードバック モード (CFB)

  • 出力フィードバックモード (OFB)

  • 暗号ブロック連鎖モード (CBC)

    平文全体がいくつかの小さな部分に分割され、各小さな部分が最初のブロックまたは前の部分の暗号文と XOR 演算され、キーで暗号化されます。

    CBC モードでは、パケットの暗号化および復号化に AES 暗号化および復号化方式を使用する場合、次の 2 つのパラメータを使用する必要があります。

    • 初期化ベクトル (オフセット)
    • 暗号化キーと復号化キー

    ここに画像の説明を挿入


塗りつぶしパターン

通常、データを暗号化および復号する際、データを一定のサイズ(ブロックサイズ)に合わせて複数のグループに分割すると、最後のグループがブロックサイズに足りない場合、それを補充する必要があるという問題が発生します。

たとえば、電子コードブック (ECB) と暗号文ブロック チェーン (CBC) は対称キー暗号化用に設計されており、ブロック暗号動作モードでは入力平文の長さがブロック長の整数倍である必要があるため、要件を満たすように情報を入力する必要があります。

一般的な塗りつぶしモード:

アルゴリズム/モード/パディング 暗号化データ長16バイト 暗号化された長さは 16 バイト未満
AES/CBC/パディングなし 16 サポートしません
AES/CBC/PKCS5パディング 32 16
AES/CBC/ISO10126パディング 32 16
AES/CFB/パディングなし 16 生データの長さ
AES/CFB/PKCS5パディング 32 16
AES/CFB/ISO10126パディング 32 16
AES/ECB/パディングなし 16 サポートしません
AES/ECB/PKCS5パディング 32 16
AES/ECB/ISO10126パディング 32 16
AES/OFB/パディングなし 16 サポートしません
AES/OFB/PKCS5パディング 32 16
AES/OFB/ISO10126パディング 32 16
AES/PCBC/パディングなし 16 サポートしません
AES/PCBC/PKCS5パディング 32 16
AES/PCBC/ISO10126パディング 32 16

オフセット

AES一般に、暗号化の複雑性を高め、データのセキュリティを強化することです。一般に、オフセットは AES_256 で使用されますが、AES_128 暗号化では使用されません。


キャラクターセット

AES 暗号化では、文字セットの問題に特別な注意を払う必要があります。一般的に使用される文字セットは utf-8 と gbk です。


AES 固有の暗号化プロセスの概要

ここに画像の説明を挿入

  • 平文 P: 暗号化されていないデータ

  • キー K: 平文の暗号化に使用されるパスワード対称暗号化アルゴリズムでは、暗号化キーと復号化キーは同じです

    鍵は受信側と送信側のネゴシエーションによって生成されますが、ネットワーク上に直接送信することはできず、漏洩してしまうため、通常は非対称暗号アルゴリズムで暗号化してネットワーク経由で相手に送信するか、直接対面で話し合う方法がとられます。キーは決して漏洩してはなりません。漏洩すると、攻撃者が暗号文を復元して機密データを盗んでしまいます。

  • AES暗号化関数: 暗号化関数のパラメータとして平文Pと鍵Kを入力すると、暗号化関数Eは暗号文Cを出力します。

  • 暗号文C:暗号化機能により処理されたデータ

  • AES 復号化関数: 復号化関数のパラメータとして暗号文 C とキー K を入力すると、復号化関数は平文 P を出力します。


AES暗号化方式

AES はブロック暗号化です。ブロック暗号化は平文をグループに分割し、各グループは同じ長さを持ち、平文全体が暗号化されるまでデータのグループを毎回暗号化します。

AES キーの長さ (32 ビットワード) パケット長 (32 ビットワード) 暗号化ラウンド数
AES-128 4 4 10
AES-192 6 4 12
AES-256 8 4 14

AES 標準仕様では、パケット長は 128 ビットのみです。つまり、各パケットは 16 バイト、各バイトは 8 ビットで、キーの長さは 128 ビット、192 ビット、または 258 ビットです。キーの長さが異なり、推奨される暗号化ラウンド数も異なります。たとえば、AES-128 はキーの長さが 128 ビットで、暗号化ラウンド数が 10 ラウンド、AES-192 は 12 ラウンド、AES-256 は 14 ラウンドを意味します。

AES-128 を例に挙げると、1 ラウンドの暗号化にはバイト置換、行置換、列混合、ラウンド キー追加の 4 つの操作があります。

  • バイト置換: AES 文字置換は実際には単純なテーブル検索操作であり、AES は S ボックスと逆 S ボックスを定義します。

  • 行シフト: 単純な左循環シフト操作です。

  • 列混合:行列の乗算によって実現され、シフト状態行列に固定行列を乗算して混乱状態行列を取得します。

  • ラウンド鍵加算: 128 ビットのラウンド鍵 Ki と状態行列のデータに対してビットごとの XOR 演算を実行します。


カスタム ツール クラス: AESUtils

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

/**
 * AES-128-CBC加密模式
 */
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
@Data
@Component
public class AESUtils {
    
    

    public static final String AES = "AES";
    // 填充模式,"算法/模式/补码方式"
    public static final String PADDING = "AES/CBC/PKCS5Padding";

    // 密钥,长度16位,可以用26个字母和数字组成
    private String key = "hj7x89H$yuBI0456";
    // 偏移量,长度16位。增加加密算法的强度
    private String iv = "NIfb&95GUY86Gfgh";


    /**
     * @Description AES算法加密明文
     * @param data 明文
     * @return 密文字节数组
     */
    public byte[] encryptAES(String data) throws Exception {
    
    
        // "算法/模式/补码方式"
        Cipher cipher = Cipher.getInstance(PADDING);
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES);
        // CBC模式,需要一个向量iv,可增加加密算法的强度
        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        return cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
    }

    public String encryptAESAndEncoder(String data, String strOutType) throws Exception {
    
    
        byte[] encrypted = encryptAES(data);
        if (EncoderUtils.BASE_64.equalsIgnoreCase(strOutType)){
    
    
            // BASE64做转码。同时能起到2次加密的作用。
            return EncoderUtils.encodeByteToBase64(encrypted).trim();
        } else if (EncoderUtils.HEX.equalsIgnoreCase(strOutType)){
    
    
            // 将二进制转换成十六进制
            return EncoderUtils.parseByte2HexStr(encrypted).trim();
        } else {
    
    
            log.warn("can not match strOutType, default use hex");
            // 将二进制转换成十六进制
            return EncoderUtils.parseByte2HexStr(encrypted).trim();
        }
    }

    public String encryptAESAndHexEncoder(String data) throws Exception {
    
    
        return encryptAESAndEncoder(data, EncoderUtils.HEX);
    }

    /**
     * @Description AES算法解密密文
     * @param data 密文
     * @return 明文
     */
    public String decryptAES(byte[] data) throws Exception {
    
    
        // "算法/模式/补码方式"
        Cipher cipher = Cipher.getInstance(PADDING);
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES);
        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        return new String(cipher.doFinal(data));
    }

    public String decryptAESAndDecode(String data, String strOutType) throws Exception {
    
    
        byte[] decodeData = null;
        if (EncoderUtils.BASE_64.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.decodeBase64ToByte(data);
        } else if (EncoderUtils.HEX.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.parseHexStr2Byte(data);
        } else {
    
    
            log.warn("can not match strOutType, default use hex");
            decodeData = EncoderUtils.parseHexStr2Byte(data);
        }
        return decryptAES(decodeData);
    }

    public String decryptAESAndHexDecode(String data) throws Exception {
    
    
        return decryptAESAndDecode(data, EncoderUtils.HEX);
    }

    public boolean verifyAES(String cipherText, String paramStr, String strOutType) throws Exception {
    
    
        byte[] decodeData = null;
        if (EncoderUtils.BASE_64.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.decodeBase64ToByte(cipherText);
        } else if (EncoderUtils.HEX.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.parseHexStr2Byte(cipherText);
        } else {
    
    
            log.warn("can not match strOutType, default use hex");
            decodeData = EncoderUtils.parseHexStr2Byte(cipherText);
        }
        return paramStr.equalsIgnoreCase(decryptAES(decodeData));
    }

    public boolean verifyAES(String cipherText, String paramStr) throws Exception {
    
    
        return verifyAES(cipherText, paramStr, EncoderUtils.HEX);
    }

    public static void main(String[] args) throws Exception {
    
    
        // 待加密的字串
        String data = "password123";
        AESUtils aesUtils = new AESUtils();
        String encryptedData = aesUtils.encryptAESAndHexEncoder(data);
        System.out.println("加密结果:" + encryptedData);
        String decryptedData = aesUtils.decryptAESAndHexDecode(encryptedData);
        System.out.println("解密结果:" + decryptedData);
    }
}

SM4アルゴリズム

概要

SM4 ブロック暗号化アルゴリズムは、中国の無線標準で使用されているブロック暗号化アルゴリズムです。2012 年、国家商業暗号局によって国家暗号化業界標準として認定されました。標準番号は GM/T 0002-2012 で、SM4 アルゴリズムに改名されました。SM2 楕円曲線公開キー暗号化アルゴリズムおよび SM3 暗号化ハッシュ アルゴリズムとともに、国家暗号化業界標準として機能し、我が国の暗号化業界で非常に重要な役割を果たしています。

SM4 アルゴリズムのキー長とブロック長は両方とも 128 ビットで、暗号化アルゴリズムと復号化アルゴリズムはすべて、ブロック暗号 LOKI のキー拡張アルゴリズムで初めて登場した 32 ラウンドの不平衡型 Feistel 反復構造を使用します。SM4 は、32 ラウンドの非線形反復の後に逆シーケンス変換を追加するため、復号キーは暗号化キーの逆シーケンスであることのみが必要となり、復号アルゴリズムと暗号化アルゴリズムの一貫性を保つことができます。したがって、SM4 アルゴリズムの暗号化および復号化プロセスで使用されるアルゴリズムはまったく同じです。唯一の違いは、このアルゴリズムの復号化キーがその暗号化キーの逆変換によって取得されることです。

  • 基本操作: SM4 暗号化アルゴリズムは、基本操作としてモジュロ 2 加算と巡回シフトを使用します。
  • 基本暗号コンポーネント: SM4 暗号アルゴリズムは、S ボックス、非線形変換 τ、線形変換コンポーネント L、および合成変換 T の基本暗号コンポーネントを使用します。
  • ラウンド関数: SM4 暗号アルゴリズムは、基本的なラウンド関数を反復する構造を採用しています。ラウンド関数は、上記の基本的な暗号コンポーネントを使用して形成できます。SM4暗号アルゴリズムのラウンド関数は、ワードを処理単位とする暗号関数である。
  • 暗号化アルゴリズム: SM4 暗号アルゴリズムはブロック アルゴリズムです。データ パケットの長さは 128 ビット、キーの長さは 128 ビットです。暗号化アルゴリズムは32ラウンドの反復構造を採用しており、各ラウンドでラウンド鍵が使用されます。
  • 復号化アルゴリズム: SM4 暗号化アルゴリズムはインボリューション演算であるため、復号化アルゴリズムの構造は暗号化アルゴリズムの構造と同じですが、ラウンド キーが使用される順序が逆であり、復号化ラウンド キーは暗号化ラウンド キーの逆順である点が異なります。
  • 鍵拡張アルゴリズム: SM4 暗号化アルゴリズムは 128 ビットの暗号鍵を使用し、32 ラウンド暗号化構造を採用しており、各暗号化には 32 ビットのラウンド鍵が使用され、合計 32 のラウンド鍵が使用されます。したがって、鍵拡張アルゴリズムを使用して、暗号鍵から 32 個のラウンド鍵を生成する必要があります。
  • SM4のセキュリティ:SM4暗号アルゴリズムは我が国の専門暗号機関によって十分に分析およびテストされており、差分攻撃や線形攻撃などの既存の攻撃に耐えることができるため、安全です。

注: S-box は、非線形変換を使用して構築されたブロック暗号のコンポーネントであり、主にブロック暗号プロセスにおける混乱の特性と設計を実現するために設計されています。SM4 アルゴリズムの S-box は、当初から欧米のブロック暗号の設計基準に完全に準拠して設計されており、その方式としては差分攻撃に強いアフィン関数逆写像複合方式を採用しています。


カスタム ツール クラス: Sm4Utils

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;
import java.util.Base64.Encoder;
import java.util.Base64.Decoder;

@Slf4j
@NoArgsConstructor
@AllArgsConstructor
@Data
@Component
public class Sm4Utils {
    
    

    static {
    
    
        // 注册 BouncyCastle
        Security.addProvider(new BouncyCastleProvider());
    }

    public static final String SM4 = "SM4";
    // 加密算法/分组加密模式/分组填充方式
    // PKCS5Padding-以8个字节为一组进行分组加密
    // 定义分组加密模式使用:PKCS5Padding
    public static final String PADDING = "SM4/ECB/PKCS5Padding";
    // 128-32位16进制;256-64位16进制
    public static final int DEFAULT_KEY_SIZE = 128;

    // 密钥,长度16位,可以用26个字母和数字组成
    private String key = "hj7x89H$yuBI0456";

    private static Cipher generateEcbCipher(int mode, byte[] key) throws Exception {
    
    
        Cipher cipher = Cipher.getInstance(Sm4Utils.PADDING, BouncyCastleProvider.PROVIDER_NAME);
        cipher.init(mode, new SecretKeySpec(key, SM4));
        return cipher;
    }

    public byte[] encryptSM4(byte[] data) throws Exception {
    
    
        Cipher cipher = generateEcbCipher(Cipher.ENCRYPT_MODE, key.getBytes(StandardCharsets.UTF_8));
        return cipher.doFinal(data);
    }

    public String encryptSM4AndEncoder(String data, String strOutType) throws Exception {
    
    
        byte[] encrypted = encryptSM4(data.getBytes(StandardCharsets.UTF_8));
        if (EncoderUtils.BASE_64.equalsIgnoreCase(strOutType)){
    
    
            // BASE64做转码。同时能起到2次加密的作用。
            return EncoderUtils.encodeByteToBase64(encrypted).trim();
        } else if (EncoderUtils.HEX.equalsIgnoreCase(strOutType)){
    
    
            // 将二进制转换成十六进制
            return EncoderUtils.parseByte2HexStr(encrypted).trim();
        } else {
    
    
            log.warn("can not match strOutType, default use hex");
            // 将二进制转换成十六进制
            return EncoderUtils.parseByte2HexStr(encrypted).trim();
        }
    }

    public String encryptSM4AndHexEncoder(String data) throws Exception {
    
    
        return encryptSM4AndEncoder(data, EncoderUtils.HEX);
    }

    public byte[] decryptSM4(byte[] cipherText) throws Exception {
    
    
        Cipher cipher = generateEcbCipher(Cipher.DECRYPT_MODE, key.getBytes(StandardCharsets.UTF_8));
        return cipher.doFinal(cipherText);
    }

    public String decryptSM4AndDecode(String data, String strOutType) throws Exception {
    
    
        byte[] decodeData = null;
        if (EncoderUtils.BASE_64.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.decodeBase64ToByte(data);
        } else if (EncoderUtils.HEX.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.parseHexStr2Byte(data);
        } else {
    
    
            log.warn("can not match strOutType, default use hex");
            decodeData = EncoderUtils.parseHexStr2Byte(data);
        }
        return new String(decryptSM4(decodeData));
    }

    public String decryptSM4AndHexDecode(String data) throws Exception {
    
    
        return decryptSM4AndDecode(data, EncoderUtils.HEX);
    }

    public boolean verifySM4(String cipherText, String paramStr, String strOutType) throws Exception {
    
    
        byte[] decodeData = null;
        if (EncoderUtils.BASE_64.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.decodeBase64ToByte(cipherText);
        } else if (EncoderUtils.HEX.equalsIgnoreCase(strOutType)){
    
    
            decodeData = EncoderUtils.parseHexStr2Byte(cipherText);
        } else {
    
    
            log.warn("can not match strOutType, default use hex");
            decodeData = EncoderUtils.parseHexStr2Byte(cipherText);
        }
        return Arrays.equals(decryptSM4(decodeData), paramStr.getBytes(StandardCharsets.UTF_8));
    }

    public boolean verifySM4(String cipherText, String paramStr) throws Exception {
    
    
        return verifySM4(cipherText, paramStr, EncoderUtils.HEX);
    }

    public static void main(String[] args) throws Exception {
    
    
        // 待加密的字串
        String data = "张三";
        Sm4Utils sm4Utils = new Sm4Utils();
        String encryptedData = sm4Utils.encryptSM4AndHexEncoder(data);
        System.out.println("加密结果:" + encryptedData);
        String decryptedData = sm4Utils.decryptSM4AndHexDecode(encryptedData);
        System.out.println("解密结果:" + decryptedData);
    }
}

非対称暗号化

RSAアルゴリズム

概要

  • RSA は、大きな数の因数分解問題に基づいています。現在、主流のすべてのコンピューター言語は RSA アルゴリズムの実装をサポートしています。
  • Java6 以降のバージョンは RSA アルゴリズムをサポートします
  • データ暗号化とデジタル署名に RSA アルゴリズムを使用可能
  • RSA アルゴリズムは、DES/AES などの対称暗号化アルゴリズムよりもはるかに低速です
  • 一般原則: 公開鍵暗号化、秘密鍵復号化 / 秘密鍵暗号化、公開鍵復号化

使い方

RSA アルゴリズムは鍵ペアの構築が簡単で、ここでは双方が送信したデータがモデルとして使用されます。

  1. 当事者 A は鍵ペア (公開鍵 + 秘密鍵) をローカルで構築し、公開鍵を当事者 B に公開します。

  2. 当事者 A は秘密キーを使用してデータを暗号化し、当事者 B に送信します

  3. 当事者 B は、当事者 A から提供された公開キーを使用してデータを復号化します。

当事者 B が当事者 A にデータを送信する場合:

  1. 当事者 B は公開キーを使用してデータを暗号化し、当事者 A に送信します。

  2. 当事者 A が秘密鍵を使用してデータを復号化する


カスタム ツール クラス: RSAUtils

import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * 非对称加密算法RSA算法组件
 */
public class RSAUtils {
    
    
    //非对称密钥算法
    public static final String KEY_ALGORITHM = "RSA";


    /**
     * 密钥长度,DH算法的默认密钥长度是1024
     * 密钥长度必须是64的倍数,在512到65536位之间
     */
    private static final int KEY_SIZE = 512;
    //公钥
    private static final String PUBLIC_KEY = "RSAPublicKey";

    //私钥
    private static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * 初始化密钥对
     *
     * @return Map 甲方密钥的Map
     */
    public static Map<String, Object> initKey() throws Exception {
    
    
        //实例化密钥生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        //初始化密钥生成器
        keyPairGenerator.initialize(KEY_SIZE);
        //生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //甲方公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        //甲方私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        //将密钥存储在map中
        Map<String, Object> keyMap = new HashMap<String, Object>();
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;

    }


    /**
     * 私钥加密
     *
     * @param data 待加密数据
     * @param key       密钥
     * @return byte[] 加密数据
     */
    public static byte[] encryptByPrivateKey(byte[] data, byte[] key) throws Exception {
    
    

        //取得私钥
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //生成私钥
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        //数据加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥加密
     *
     * @param data 待加密数据
     * @param key       密钥
     * @return byte[] 加密数据
     */
    public static byte[] encryptByPublicKey(byte[] data, byte[] key) throws Exception {
    
    

        //实例化密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //初始化公钥
        //密钥材料转换
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
        //产生公钥
        PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);

        //数据加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        return cipher.doFinal(data);
    }

    /**
     * 私钥解密
     *
     * @param data 待解密数据
     * @param key  密钥
     * @return byte[] 解密数据
     */
    public static byte[] decryptByPrivateKey(byte[] data, byte[] key) throws Exception {
    
    
        //取得私钥
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //生成私钥
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        //数据解密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥解密
     *
     * @param data 待解密数据
     * @param key  密钥
     * @return byte[] 解密数据
     */
    public static byte[] decryptByPublicKey(byte[] data, byte[] key) throws Exception {
    
    

        //实例化密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //初始化公钥
        //密钥材料转换
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
        //产生公钥
        PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
        //数据解密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, pubKey);
        return cipher.doFinal(data);
    }

    /**
     * 取得私钥
     *
     * @param keyMap 密钥map
     * @return byte[] 私钥
     */
    public static byte[] getPrivateKey(Map<String, Object> keyMap) {
    
    
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return key.getEncoded();
    }

    /**
     * 取得公钥
     *
     * @param keyMap 密钥map
     * @return byte[] 公钥
     */
    public static byte[] getPublicKey(Map<String, Object> keyMap) throws Exception {
    
    
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return key.getEncoded();
    }

    /**
     * 测试
     */
    public static void main(String[] args) throws Exception {
    
    
        //初始化密钥
        //生成密钥对
        Map<String, Object> keyMap = RSACoder.initKey();
        //公钥
        byte[] publicKey = RSACoder.getPublicKey(keyMap);
        //私钥
        byte[] privateKey = RSACoder.getPrivateKey(keyMap);
        System.out.println("公钥:" + Base64.encodeBase64String(publicKey));
        System.out.println("私钥:" + Base64.encodeBase64String(privateKey));

        System.out.println("==========密钥对构造完毕,甲方将公钥公布给乙方,开始进行加密数据的传输==========");
        String str = "RSA密码交换算法";
        System.out.println("===========甲方向乙方发送加密数据==============");
        System.out.println("原文:" + str);
        //甲方进行数据的加密
        byte[] code1 = RSACoder.encryptByPrivateKey(str.getBytes(), privateKey);
        System.out.println("加密后的数据:" + Base64.encodeBase64String(code1));
        System.out.println("===========乙方使用甲方提供的公钥对数据进行解密==============");
        //乙方进行数据的解密
        byte[] decode1 = RSACoder.decryptByPublicKey(code1, publicKey);
        System.out.println("乙方解密后的数据:" + new String(decode1));

        System.out.println("===========反向进行操作,乙方向甲方发送数据==============");

        str = "乙方向甲方发送数据RSA算法";
        System.out.println("原文:" + str);
        //乙方使用公钥对数据进行加密
        byte[] code2 = RSACoder.encryptByPublicKey(str.getBytes(), publicKey);
        System.out.println("===========乙方使用公钥对数据进行加密==============");
        System.out.println("加密后的数据:" + Base64.encodeBase64String(code2));

        System.out.println("=============乙方将数据传送给甲方======================");
        System.out.println("===========甲方使用私钥对数据进行解密==============");
        //甲方使用私钥对数据进行解密
        byte[] decode2 = RSACoder.decryptByPrivateKey(code2, privateKey);
        System.out.println("甲方解密后的数据:" + new String(decode2));
    }
}

拡大

javax.crypto.Cipherクラス

javax.crypto.Cipher クラスは jdk1.4 から導入されており、主に暗号化、復号化、パスワード機能に使用される jdk 拡張パッケージ Java Cryptographic Extension (JCE) フレームワークに属しています。

現在サポートされているアルゴリズム

合計で、次の暗号化アルゴリズムがサポートされています: AES / DES / DESede / RSA

型の値 パスワードの長さ 説明する
AES/CBC/パディングなし 128 AES アルゴリズムの CBC モード実装
AES/CBC/PKCS5パディング 128 AES アルゴリズムの CBC モード実装、および PKCS5Padding ルールで埋められます
AES/ECB/パディングなし 128 AESアルゴリズムのECBモードの実装
AES/ECB/PKCS5パディング 128 AES アルゴリズムの ECB モード実装、および PKCS5Padding ルールで埋められます
DES/CBC/パディングなし 56
DES/CBC/PKCS5パディング 56
DESede/CBC/NoPadding 168
DESede/CBC/PKCS5パディング 168
DESede/ECB/NoPadding 168
DESede/ECB/PKCS5パディング 168
RSA/ECB/PKCS1パディング (1024、2048) パスワードの長さはオプションです
RSA/ECB/OAEPwithSHA-1AndMGF1パディング (1024、2048) パスワードの長さはオプションです
RSA/ECB/OAEPwithSHA-256AndMGF1パディング (1024、2048) パスワードの長さはオプションです

一般的な暗号化モードは次のとおりです。

  • ECB (Electronic Codebook Book、電子コードブック モード): 平文をいくつかの小さなセグメントに分割し、各小さなセグメントを暗号化します。
  • CBC (Cipher Block Chaining、暗号ブロック連鎖モード): まず、平文がいくつかの小さなセグメントに分割され、次に各小さなセグメントが最初のブロックまたは前のセグメントの暗号文セグメントと XOR 演算され、キーで暗号化されます。

一般的な入力ルールは次のとおりです。

  • ほとんどの場合、平文は正確には N ビットの倍数ではありません。最後のパケットの長さが N ビット未満の場合は、N ビットまでデータを詰める必要があります

Cipher 类里面常用方法:

// 获取 Cipher 实现指定转换的对象
public static final Cipher getInstance(String transformation, String provider)
    
// 使用密钥和一组算法参数初始化此密码
public final void init(int opmod, Key key);
public final void init(int opmod, Certificate certificate);
public final void init(int opmod, Key key, SecureRandom random);
public final void init(int opmod, Certificate certificate, SecureRandom random);
public final void init(int opmod, Key key, AlgorithmParameterSpec params);
public final void init(int opmod, Key key, AlgorithmParameterSpec params, SecureRandom random);
public final void init(int opmod, Key key, AlgorithmParameters params);
public final void init(int opmod, Key key, AlgorithmParameters params,SecureRandom random);
    
// 完成多部分加密或解密操作,具体取决于此密码的初始化方式
public final byte[] doFinal(byte[] input);
public final byte[] doFinal(byte[] input, int inputOffset, int inputLen);
public final int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output);
public final int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output,int outputOffset);

// 在多步加密或解密数据时,首先需要一次或多次调用update方法,用以提供加密或解密的所有数据
public byte[] update(byte[] input);
public byte[] update(byte[] input, int inputOffset, int inputLen);
public int update(byte[]input, int inputOffset, int inputLen, byte[] output);
public int update(byte[]input, int inputOffset, int inputLen, byte[] output, int outputOffset);
// 如果还有输入数据,多步操作可以使用前面提到的doFinal方法之一结束。如果没有数据,多步操作可以使用下面的doFinal方法之一结束
public final byte[] doFinal();
public final int doFinal(byte[] output, int outputOffset);

其中:

  • 通过调用 Cipher 类中的 getInstance() 静态工厂方法得到 Cipher 对象。

    参数 transformation :密码学算法名称,比如 DES,也可以在后面包含模式和填充方式

    两种写法:

    • “算法/模式/填充”,示例:“DES/CBC/PKCS5Padding”
    • “算法”,示例:“DES”

    如果没有指定模式或填充方式,就使用特定提供者指定的默认模式或默认填充方式。

    例如,SunJCE 提供者使用 ECB 作为 DES、DES-EDE 和 Blowfish 等 Cipher 的默认模式,并使用 PKCS5Padding 作为它们默认的填充方案。这意味着在 SunJCE 提供者中,下列形式的声明是等价的:

    • Cipherc1=Cipher.getInstance(“DES/ECB/PKCS5Padding”);
    • Cipher c1=Cipher.getInstance(“DES”);
  • getInstance() 工厂方法返回的对象没有进行初始化,因此在使用前必须进行初始化。

    通过 getInstance() 得到的 Cipher 对象必须使用下列四个模式之一进行初始化,这四个模式在 Cipher 类中被定义为 finalinteger常数,可以使用符号名来引用这些模式:

    • ENCRYPT_MODE :加密数据
    • DECRYPT_MODE :解密数据
    • WRAP_MODE :将一个 Key 封装成字节,可以用来进行安全传输
    • UNWRAP_MODE :将前述已封装的密钥解开成 java.security.Key 对象

    每个 Cipher 初始化方法使用一个模式参数 opmod,并用此模式初始化 Cipher 对象。此外还有其他参数,包括密钥 key、包含密钥的证书 certificate、算法参数 params 和随机源 random。

    必须指出的是,加密和解密必须使用相同的参数。当 Cipher 对象被初始化时,它将失去以前得到的所有状态。

  • 如果在 transformation 参数部分指定了 padding 或 unpadding 方式,则所有的 doFinal 方法都要注意所用的 padding 或unpadding 方式。

    调用 doFinal 方法将会重置 Cipher 对象到使用 init 进行初始化时的状态,就是说,Cipher 对象被重置,使得可以进行更多数据的加密或解密,至于这两种模式,可以在调用 init 时进行指定。

  • 包裹 wrap 密钥必须先使用 WRAP_MODE 初始化 Cipher 对象,然后调用以下方法:

    public final byte[] wrap(Key key);
    

    如果将调用 wrap 方法的结果(wrap 后的密钥字节)提供给解包裹 unwrap 的人使用,必须给接收者发送以下额外信息:

    1. 密钥算法名称

      密钥算法名称可以调用 Key 接口提供的 getAlgorithm 方法得到:

      public String getAlgorithm();
      
    2. 被包裹密钥的类型(Cipher.SECRET_KEY,Cipher.PRIVATE_KEY,Cipher.PUBLIC_KEY)

    为了对调用 wrap 方法返回的字节进行解包,必须先使用 UNWRAP_MODE 模式初始化 Ciphe r对象,然后调用以下方法:

    public final Keyunwrap(byte[] wrappedKey,String wrappedKeyAlgorithm,int wrappedKeyType));
    

    其中:

    • 参数 wrappedKey 是调用wrap方法返回的字节

    • 参数 wrappedKeyAlgorithm 是用来包裹密钥的算法

    • 参数 wrappedKeyType 是被包裹密钥的类型,该类型必须是

      Cipher.SECRET_KEY, Cipher.PRIVATE_KEY, Cipher.PUBLIC_KEY 三者之一


Bouncy Castle 密码包

概述

BouncyCastle(轻量级密码术包)是一种用于 Java 平台的开放源码的轻量级密码术包;Bouncycstle 包含了大量的密码算法,其支持椭圆曲线密码算法,并提供 JCE 1.2.1的实现。

Java 標準ライブラリは、一般的に使用される一連のハッシュ アルゴリズムを提供します。ただし、使用する特定のアルゴリズムが Java 標準ライブラリで提供されていない場合は、自分で記述するか、既製のサードパーティ ライブラリを直接使用する必要があります。BouncyCastle は、多くのハッシュ アルゴリズムと暗号化アルゴリズムを提供するサードパーティ ライブラリです。Java 標準ライブラリにはないいくつかのアルゴリズム (RipeMD160 ハッシュ アルゴリズムなど) が提供されます。

Bouncy Castle は軽量になるように設計されているため、J2SE 1.4 から J2ME (MIDP を含む) のプラットフォームで実行できます。これは、MIDP 上で実行される唯一の完全な暗号化パッケージです。


使用

  • bouncycastle の jar パッケージの依存関係を pom.xml ファイルに導入し、BouncyCastle によって提供される jar パッケージをクラスパスに配置します。この jar パッケージは bccrov-jdk15on-xxx.jar で、公式 Web サイトからダウンロードできます

    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-jdk15on</artifactId>
        <version>1.59</version>
    </dependency>
    
  • Java 標準ライブラリの java.security パッケージは、サードパーティ プロバイダによるシームレスな統合を可能にする標準メカニズムを提供します。BouncyCastle が提供する RipeMD160 アルゴリズムを使用するには、まず BouncyCastle を登録する必要があります。

    @Test
    public void case1(){
          
          
        static {
          
          
            // 注册BouncyCastle:
        	Security.addProvider(new BouncyCastleProvider());
        }
        
        // 按名称正常调用:
        try {
          
          
            md = MessageDigest.getInstance("RipeMD160");
            md.update("HelloWorld".getBytes("UTF-8"));
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
          
          
            e.printStackTrace();
        }
        byte[] result = md.digest();
        System.out.println(new BigInteger(1, result).toString(16));
    }
    

    注: BouncyCastle の登録は、次のステートメントによって実行されます。登録は起動時に一度だけ行う必要があり、その後は BouncyCastle が提供するすべてのハッシュ アルゴリズムと暗号化アルゴリズムを使用できます。

    Security.addProvider(new BouncyCastleProvider());
    

おすすめ

転載: blog.csdn.net/footless_bird/article/details/124029210