序文の知識:
1.AES (Advanced Encryption Standard)ブロック暗号としての高度な暗号化標準(平文をグループに分割し、各グループは同じ長さで、平文全体が暗号化されるまで一度に 1 つのデータ グループを暗号化します)。
2. AES 標準仕様では、パケット長は 128 ビットのみです。つまり、各パケットは 16 バイトの 8 の倍数 (1 バイトあたり 8 ビット) です。キーの長さは 128 ビット、192 ビット、または 256 ビットで、それぞれ に対応しますAES128
。キーの長さがAES192
異なると、推奨される暗号化ラウンドもAES256,
異なります。セキュリティの観点から見ると、セキュリティは最高です。性能面で言えば最高の性能です。本質的な理由は、暗号化処理ラウンドが異なることです。AES256
AES128
3. AES は、暗号化と復号化に同じ秘密キーが使用されるため、対称暗号化アルゴリズムです。
4.AES
3 つの長さのキーをサポート: 128 ビット、192 ビット、256 ビット 通常、ECB、CBC、CFB、OFB などの少なくとも 4 つのモードがあります。
5.AES
このアルゴリズムが平文を暗号化する場合、平文全体を暗号文全体に暗号化するのではなく、平文を平文ブロックと同じ長さの独立した平文ブロックに分割します128bit
。これらの平文ブロックは
AES
暗号化装置によって複雑に処理されて独立した暗号文ブロックが生成され、これらの暗号文ブロックが結合されて最終的なAES
暗号化結果が形成されます。しかし、ここで問題があり、平文の長さが の場合
196bit
、128bit
それを平文ブロックごとに分割すると、2 番目の平文ブロックだけでは64bit
不十分になってしまいます128bit
。この時どうすればいいでしょうか?平文ブロックをパディング(Padding)する必要があります。
6. いくつかの典型的な充填方法:
NoPadding
: パディングは行われませんが、プレーンテキストは 16 バイトの整数倍である必要があります。PKCS5Padding
(Java のデフォルト): 平文ブロックが 16 バイト ( ) 未満の場合128bit
、対応する数の文字が平文ブロックの末尾に追加され、各バイトの値は欠落している文字の数と等しくなります。たとえば、プレーン テキスト: {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 }ISO10126Padding
: 平文ブロックが 16 バイト未満の場合 (128bit
)、対応するバイト数が平文ブロックの末尾に追加され、最後に文字値は欠落している文字番号と等しく、他の文字は乱数で埋められます。たとえば、プレーン テキスト: {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} 原理は次の原理PKCS7Padding
と似ていPKCS5Padding
ます。PKCS5Padding
blocksize
PKCS7Padding
blocksize
AES
暗号化中に特定の充填方法が使用される場合、復号化時にも同じ充填方法を使用する必要があることに注意してくださいZeroPadding
(C++ のデフォルト、Java にはサブ充填方法がありません): Java 操作で平文が 16 ビット未満の場合手動操作が必要です。ゼロをパッドしてから NoPadding を呼び出します
AES の 4 つの動作モード原則:
1. ECB モード:
ECB (Electronic Code Book) モードは最も単純なブロック暗号暗号化モードで、暗号化前にデータ ブロック サイズに応じていくつかのブロックに分割されます(たとえば、AES は 128 ビットで、各グループのサイズはデータ ブロックと同じです)暗号化キーの長さ) その後、各ブロックは同じキーを使用し、キーはブロック暗号化装置を個別に通過します。この暗号化モードの利点は、シンプルで初期化ベクトル (IV) が必要ないこと、各データ ブロックが独立して暗号化/復号化されるため、並列計算に役立ち、暗号化/復号化の効率が非常に高いことです。ただし、このモードでは、すべてのデータが同じ鍵で暗号化/復号され、論理演算が行われないため、同じ平文から同じ暗号文が得られるため、「選択平文攻撃」が発生する可能性があります。(最も早くて最も単純なモード)
利点: 1. シンプル、2. 並列計算が容易、3. 誤差が拡散しない。
欠点: 1. 平文を隠蔽できないモード; 2. 平文に対する積極的な攻撃の可能性; したがって、このモードは小さなメッセージの暗号化に適しています。
2. CBCモード:参考
CBC ( Cipher Block Chaining ) モードでは、まず平文をいくつかの小さなブロックに分割し、次に各小さなブロックに対して最初のブロックまたは前の暗号文セグメントとの論理 XOR 演算を実行し、キーを使用して暗号化します。最初の平文ブロックは、初期化ベクトルと呼ばれるデータ ブロックと論理的に XOR 演算されます。これにより、2 つの平文ブロックが同じであっても、暗号化後に得られる暗号文ブロックは異なるという、ECB モードによって明らかになった問題が効果的に解決されます。しかし、暗号化処理が複雑で効率が低いなどの欠点も明らかです。
利点: 積極的な攻撃を受けにくく、ECB よりもセキュリティが高く、長いメッセージの送信に適しており、SSL および IPSec の標準です。
欠点: 1. 並列計算には適さない; 2. エラーが伝播する; 3. ベクトル IV を初期化する必要がある
3. CFB モード:
ブロック データのみを暗号化できる ECB モードや CBC モードとは異なり、CFB モードは暗号文をストリーム暗号文に変換できます。この暗号化モードでは、暗号化処理および復号化処理においてブロック暗号器によって暗号化されたデータは前のブロックの暗号文であるため、このブロックの平文データの長さが整数倍でなくてもパディングする必要はありません。これにより、暗号化の前後でデータ長が同じになることが保証されます。
利点: 1. 隠蔽平文モード; 2. ブロック暗号をストリーム モードに変換; 3. グループ サイズより小さいデータは暗号化してタイムリーに送信できます。
欠点: 1. 並列計算には適さない; 2. エラー送信: 1 つの平文ユニットの損傷は複数のユニットに影響する; 3. 固有の IV;
4.OFBモード:
平文ブロックを直接暗号化する代わりに、暗号化プロセスでは、最初にブロック暗号化装置を使用してキー ストリームを生成し、次にキー ストリームと平文ストリームに対して論理 XOR 演算を実行して暗号文ストリームを取得します。
利点: 1. 平文モードが隠蔽される; 2. ブロック暗号がストリームモードに変換される; 3. ブロックサイズより小さいデータをタイムリーに暗号化して送信できる; 3. ブロック暗号がストリームモードに変換される
欠点: 1. 並列コンピューティングには適さない; 2. 平文に対する積極的な攻撃の可能性がある; 3. エラー送信: 1 つの平文ユニットへの損傷は複数のユニットに影響する。
AESアルゴリズムの原理
暗号化アルゴリズムの一般的な設計ガイドライン
混乱は 暗号文、平文、鍵の間の関係の複雑さを最大化し、通常は最大の混乱を達成するために非線形変換アルゴリズムを使用します。
拡散: 平文または鍵の 1 ビットが変更されるたびに、暗号文のビット数が最大化されます。拡散を最大化するには、通常、線形変換アルゴリズムが使用されます。
Rijndael アルゴリズムの場合、復号化プロセスは暗号化プロセスの逆のプロセスです。以下の図は、AES 暗号化と復号化のプロセスを示しています。図からわかるように: 1) 復号化アルゴリズムの各ステップは、暗号化アルゴリズムの逆演算に対応します。2) すべての暗号化と復号化の操作の順序は、正確に次のとおりです。反対。これらの点 (および暗号化アルゴリズムと復号化アルゴリズムの各ステップの相互作用) があるからこそ、アルゴリズムの正確性が保証されます。具体的な参考資料 1 参考資料 2
テキスト (C の AES_cbc_encrypt 暗号化は Java の復号化に対応します)
//C中的加密方式
AES_cbc_encrypt((const unsigned char*)in.data(),
(unsigned char*)out.data(),
len,
&aes,
(unsigned char*)ivec.data(),
AES_ENCRYPT);
AES_cbc_encrypt: これは、C++ の AES の CBC 暗号化方式です。任意の長さの入力データを暗号化できます。入力データが 16 バイトの整数倍でない場合、関数は ZeroPadding を使用して入力データを自動的に埋めてから、暗号化します。 。次に、 Java には ZeroPadding が存在しないことがわかりました。ZeroPadding は手動で実装され、その後 NoPadding が呼び出されます。私の具体的な実装は次のとおりです。
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
/**
* @ClassName AESCBCUtil
* @Description TODO
* @Author wanghaha
* @Date 2023/3/1
**/
public class AESCBCUtil {
/*
* @Description: 满足pc的加密方式 生成128位
* @Author: wanghaha
* @Date: 2023/3/1
* @param: data
* @param: iv
* @return: java.lang.String
**/
public static String encrypt128(String data, String iv) {
byte[] key = new byte[16];
for (int i = 0; i < 16; i++) {
key[i] = (byte) (29 + i * 3);
}
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
byte[] dataBytes = data.getBytes();
byte[] plaintext = new byte[128];
//填充
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
//设置偏移量参数
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encryped = cipher.doFinal(plaintext);
return DatatypeConverter.printBase64Binary(encryped);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/* 加密 正常长度
* @Description:
* @Author: wanghaha
* @Date: 2023/3/1
* @param: null
* @return: null
**/
public static String encrypt(String data, String iv) {
byte[] key = new byte[16];
for (int i = 0; i < 16; i++) {
key[i] = (byte) (29 + i * 3);
}
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
byte[] dataBytes = data.getBytes();
int blockSize = cipher.getBlockSize();
int length = dataBytes.length;
//计算需填充长度
if (length % blockSize != 0) {
length = length + (blockSize - (length % blockSize));
}
byte[] plaintext = new byte[length];
//填充
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
//设置偏移量参数
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encryped = cipher.doFinal(plaintext);
return DatatypeConverter.printBase64Binary(encryped);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//解密
public static String desEncrypt(String data, String iv) {
byte[] key = new byte[16];
for (int i = 0; i < 16; i++) {
key[i] = (byte) (29 + i * 3);
}
try {
byte[] encryp = DatatypeConverter.parseBase64Binary(data);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] original = cipher.doFinal(encryp);
//因为pc端使用加密的是zeroPadding: 后补0的Nopadding方式 且 都是字符unsigned限制的 char正数 可以通过判断第一个0的位置判断字符长度
int end=0;
while(end < original.length && original[end] != 0){
end++;
}
return new String(original,0, end);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//测试
public static void main(String[] args) {
String data = "232342342342342";
String iv = "2334022720230227";
String encrypt= encrypt(data ,iv);
String desencrypt = desEncrypt(encrypt,iv);
System.out.println("长加密后:"+encrypt);
System.out.println("长解密后:"+desencrypt);
}
}
発生したバグの解決策:
bug1 : AES 復号化を行うときに、「指定された最終ブロックが適切に埋め込まれていません。復号化中に不正なキーが使用されると、このような問題が発生する可能性があります。」というエラーが発生しました。これは、暗号化と暗号化に対応するキーが異なることを意味します。
バグ 2 : AES 暗号化に Java のCipherクラスを使用すると、次のエラーが報告されます: IllegalBlockSizeException: 入力長が 16 バイトの倍数ではありません
エラーコードCipher cipher = Cipher.getInstance("AES/ CBC /NoPadding")
パディング方式が Nopadding の場合、暗号化された平文は 8 の倍数であるか、キーが 16 ビットである必要があります。
暗号化されるコンテンツの長さが 16 の倍数でない場合は、16 の倍数である必要があります。
概要: Java は AES の CBC モードをサポートしていますが、充填メソッドは ZeroPadding をサポートしていません。
原則: ZeroPadding は、「\0」を使用して暗号化されたコンテンツを BlockSize (CBC は通常 16) の整数倍まで埋めてから、それを NoPadding で埋めます。