Cipher.getInstance("AES/ECB/PKCS5Padding");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
暗号化や復号化のプログラミングを行う際に、上記の文章に触れたことがある友人も多いはずですが、コーディングの過程でECB/CBCの意味と違い、PKCS5Paddingの意味を理解できたでしょうか?よくわからない場合は、この記事が役立つことを願っています。
ブロック暗号とは何ですか?
ブロック暗号は、固定長の文字に従って平文を暗号化し、暗号文を復号する暗号化および復号アルゴリズムです。一般的なものは DES、AES などです。具体的には下図のようになります
IV(オフセット)とは何ですか?
これは、暗号化および復号化のプロセス中に設定される変数として理解でき、タイムスタンプ、uuid、またはその他のランダムな値にすることができます。目的は、混乱を増やし、解読されるリスクを軽減することです。
ブロック暗号化モードとは何ですか?
一般的な暗号化と復号化 (または ECB) の場合、暗号化と復号化関数をブロック暗号化と呼ぶだけで終了しますが、リプレイ攻撃 (同じリクエストが複数回開始される) の場合、ハッカーは暗号文を簡単に傍受できます。グループにある場合、現時点ではシステム セキュリティに対する脅威となります。これを防ぐために、ブロック暗号を反復処理するための対策を講じることができます。上記の ECB (電子コードブック モード)/CBC (暗号文ブロック連鎖モード) は、ブロック暗号化モードに属します。また、CFB(暗号文フィードバックモード)もブロック暗号化モードに属します。
さまざまなブロック暗号化モードの意味と、それぞれの長所と短所を 1 つずつ分析してみましょう。
ECB (Electronic Codebook Mode)
プロセス/原理:
このブロック暗号化モードは、最も単純な未処理ブロック暗号化モードに属し、平文または暗号文をグループごとに直接暗号化または復号して結果を取得します。
利点:
- マルチスレッド非同期処理をサポートし、高効率
- 反復計算のグループ化が不要で、計算量が少ない
短所:
- セキュリティパフォーマンスが低く、簡単に侵害されます。
CBC (暗号文ブロック連鎖モード)
プロセス/原理:
暗号化:
第 1 ラウンド: 初期化ベクトル iv を生成し、平文ブロック XOR 演算の最初のグループと共同で暗号化します。
フォローアップ: CBC 演算の最終ラウンドの結果と現在のブロックの平文 XOR 演算を取得して暗号化します。
復号化:
第 1 ラウンド: 初期化ベクトル iv を生成し、最初に暗号文ブロックの最初のグループを復号化してから、初期化ベクトルを使用して XOR 演算を実行します。
フォローアップ: まず現在の暗号文グループを復号化してから、前のラウンドの暗号文グループと XOR 演算を実行します。
暗号化と復号化の原則:
A XOR B XOR B = A
たとえば、最初のラウンドの暗号文のグループの最初のグループ e1 = Ek暗号化(iv x または平文の最初のグループ)
、その後、最初の復号化後の平文の最初のグループ d1 = (Ek'(暗号文の最初のグループ) xor iv) = (iv xor 平文の最初のグループ) xor iv = 最初のグループ平文の
利点:
- iv を確率変数として使用すると、解読の難易度が高まり、同じ平文の各暗号化の結果に一貫性がなくなります - 暗号化では、
前のラウンドの出力が次のラウンドの入力として使用されます
デメリット:
- 計算量が増加し、計算オーバーヘッドが増加します。
CFB (暗号文フィードバック モード)
プロセス/原理:
暗号化:
第 1 ラウンド: 初期化ベクトル iv を生成し、最初に暗号化計算を実行し、次に平文グループの最初のグループと XOR 演算を実行します。
フォローアップ: CFB 操作の最後のラウンドの結果を取得し、まず暗号化してから、現在のグループの平文と XOR 操作を実行します。
復号化:
第 1 ラウンド: 初期化ベクトル iv を生成し、最初に暗号化計算を実行し、次に暗号文グループの最初のグループと XOR 演算を実行します。
フォローアップ: CFB 演算の最終ラウンドの結果を取得し、まず暗号化してから、現在のブロック暗号文と XOR 演算を実行します。
暗号化と復号化の原則:
A XOR B XOR B = A
たとえば、最初の暗号化ラウンド後の暗号文 e1 の最初のグループ = Ek(iv) xor 平文の最初のグループ、
次に暗号化の最初のラウンド後の平文の最初のグループ復号化 d1 = ( Ek(iv) xor 暗号文の最初のセット) = (Ek(iv) xor (Ek(iv) xor 平文の最初のセット) ) = 平文の最初のセット
利点:
- iv を確率変数として使用すると、解読の難易度が高まり、同じ平文の暗号化ごとに結果に一貫性がなくなります - 暗号化では、前の
ラウンドの出力が次のラウンドの入力として使用されます
- iv は、暗号化で単独で使用できます。ブロック暗号化アルゴリズムの使用
デメリット:
- 計算量が増加し、計算オーバーヘッドが増加します。
ブロック暗号化モードコードの実装
注: AES コードが必要な場合は、次の記事を参照してください。
プログラマの成長を支える暗号 - AES アルゴリズムの復号とコードの提示の詳細な説明 https://blog.csdn.net/qq_31236027/article/details/131206018
列挙型
public enum EncryptMode {
CBC("CBC"),
CFB("CFB");
private String name;
private EncryptMode(String name) {
this.setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
暗号化された部分
/**
* 分组加密(128位一组)【完整版】
* @param text 明文
* @param mode 加密模式(CFB、CBC)
* @param iv 偏移量
*/
@Override
public String encrypt(String text, String iv, EncryptMode mode) {
if(mode == null) {
return encrypt(text);
}
String result = null;
switch(mode) {
case CBC: result = encryptCBC(text,iv); break;
case CFB: result = encryptCFB(text,iv);break;
default: result = encrypt(text);break;
}
return result;
}
/**
* 分组加密(128位一组)(无iv【偏移量】版)
* @param text 明文
*/
private String encrypt(String text) {
StringBuilder sb = new StringBuilder();
int textLen = text.length();
//获取分组长度
// DIV_LEN * CHAR_LEN = 128
// 根据DIV_LEN进行分组,如CHAR_LEN=16位,那么就每8个字符一组
int divLen = textLen % AESConstant.DIV_LEN == 0 ? textLen / AESConstant.DIV_LEN : (textLen / AESConstant.DIV_LEN + 1);
//分组加密处理
for (int i = 0; i < divLen; i++) {
int startIndex = i * AESConstant.DIV_LEN;
int endIndex = (startIndex + AESConstant.DIV_LEN) >= textLen ? textLen : (startIndex + AESConstant.DIV_LEN);
String substr = text.substring(startIndex, endIndex);
//尾部填充
while(substr.length() < AESConstant.DIV_LEN) {
substr += " ";
}
sb.append(EncodeUtil.binaryToHexStr(baseEncrypt(substr)).trim());
}
return new BASE64Encoder().encode(sb.toString().trim().getBytes());
}
/**
* 分组加密(128位一组),(有iv【偏移量】CBC版,更安全)
*
* CBC特性
* 1. 每一组分组的密文都依赖于上一组的结果
* 2. 加入了iv偏移量使得每次加密执行后的结果都不一致
*
* @param text 明文
* @param iv 偏移量
*/
private String encryptCBC(String text,String iv) {
StringBuilder sb = new StringBuilder();
int textLen = text.length();
//获取分组长度
// DIV_LEN * CHAR_LEN = 128
// 根据DIV_LEN进行分组,如CHAR_LEN=16位【UNICODE】,那么就每8个字符一组
int divLen = textLen % AESConstant.DIV_LEN == 0 ? textLen / AESConstant.DIV_LEN : (textLen / AESConstant.DIV_LEN + 1);
// CFB加密初始化向量
String encryptedPart = iv;
//分组加密处理
for (int i = 0; i < divLen; i++) {
int startIndex = i * AESConstant.DIV_LEN;
int endIndex = (startIndex + AESConstant.DIV_LEN) >= textLen ? textLen : (startIndex + AESConstant.DIV_LEN);
String substr = text.substring(startIndex, endIndex);
//尾部填充
while(substr.length() < AESConstant.DIV_LEN) {
substr += " ";
}
while(encryptedPart.length() < AESConstant.DIV_LEN) {
encryptedPart += " ";
}
//CBC关键,需要拿明文与上一轮结果进行异或得到的结果共同加密作为下一轮的输入
encryptedPart = EncodeUtil.binaryToStr(
baseEncrypt(strXor(encryptedPart,substr)), 16
);
sb.append(encryptedPart);
}
//批量处理为16进制后base64运算
String result = sb.toString().trim();
result = EncodeUtil.strtoBinary(result, 16);
result = EncodeUtil.binaryToHexStr(result);
return new BASE64Encoder().encode(result.getBytes());
}
/**
* 分组加密(128位一组),(有iv【偏移量】CFB版,更安全)
*
* CFB特性
*
* @param text 明文
* @param iv 偏移量
*/
private String encryptCFB(String text,String iv) {
StringBuilder sb = new StringBuilder();
int textLen = text.length();
//获取分组长度
// DIV_LEN * CHAR_LEN = 128
// 根据DIV_LEN进行分组,如CHAR_LEN=16位【UNICODE】,那么就每8个字符一组
int divLen = textLen % AESConstant.DIV_LEN == 0 ? textLen / AESConstant.DIV_LEN : (textLen / AESConstant.DIV_LEN + 1);
// CFB加密初始化向量
String encryptedPart = iv;
//分组加密处理
for (int i = 0; i < divLen; i++) {
int startIndex = i * AESConstant.DIV_LEN;
int endIndex = (startIndex + AESConstant.DIV_LEN) >= textLen ? textLen : (startIndex + AESConstant.DIV_LEN);
String substr = text.substring(startIndex, endIndex);
//尾部填充
while(substr.length() < AESConstant.DIV_LEN) {
substr += " ";
}
while(encryptedPart.length() < AESConstant.DIV_LEN) {
encryptedPart += " ";
}
//CFB关键,需要拿明文与上一轮加密结果进行异或得到的结果作为下一轮的输入
encryptedPart = strXor(EncodeUtil.binaryToStr(
baseEncrypt(encryptedPart), 16
),substr);
sb.append(encryptedPart);
}
//批量处理为16进制后base64运算
String result = sb.toString().trim();
result = EncodeUtil.strtoBinary(result, 16);
result = EncodeUtil.binaryToHexStr(result);
return new BASE64Encoder().encode(result.getBytes());
}
復号化部分
/**
* 分组解密(128位一组)【完整版】
* @param encrytedText 密文
* @param mode 加密模式(CFB、CBC)
* @param iv 偏移量
*/
@Override
public String decrypt(String encrytedText, String iv, EncryptMode mode) {
if(mode == null) {
return decrypt(encrytedText);
}
String result = null;
switch(mode) {
case CBC: result = decryptCBC(encrytedText,iv); break;
case CFB: result = decryptCFB(encrytedText,iv);break;
default: result = decrypt(encrytedText);break;
}
return result;
}
/**
* 分组解密
* @param encrytedText 密文
*/
private String decrypt(String encrytedText) {
try {
//base64解码
byte[] bytes = new BASE64Decoder().decodeBuffer(encrytedText);
String str = new String(bytes,Charset.forName("UTF8"));
int textLen = str.length();
StringBuilder sb = new StringBuilder();
int divLen = textLen < 32 ? 1 : (int)(Math.ceil(textLen/(4*8*1.0))); //因为加密后会自动填充所以长度必为字符长度的倍数(HEX 4位)
//分组解密
for (int i = 0; i< divLen; i++) {
int startIndex = i * (4*8);
int endIndex = (startIndex + (4*8));
String temp = str.substring(startIndex, endIndex);
sb.append(baseDecrypt(temp));
}
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 分组解密(128位一组),(有iv【偏移量】CBC版)
* @param encrytedText 密文
* @param iv 偏移量
* @return 明文
*/
private String decryptCBC(String encrytedText,String iv) {
try {
//base64解码
byte[] bytes = new BASE64Decoder().decodeBuffer(encrytedText);
String str = new String(bytes,Charset.forName("UTF8"));
int textLen = str.length();
StringBuilder sb = new StringBuilder();
int divLen = textLen < 32 ? 1 : (int)(Math.ceil(textLen/(4*8*1.0))); //因为加密后会自动填充所以长度必为字符长度的倍数(HEX 4位)
//CFB解密初始化向量
String decryptedPart = iv;
//分组解密
for (int i = 0; i< divLen; i++) {
int startIndex = i * (4*8);
int endIndex = (startIndex + (4*8));
String temp = str.substring(startIndex, endIndex);
//尾部填充
while(decryptedPart.length() < AESConstant.DIV_LEN) {
decryptedPart += " ";
}
//转换成16位的字符,方便strXor运算
sb.append(strXor(baseDecrypt(temp),decryptedPart));
//位数转换
decryptedPart = EncodeUtil.binaryToStr(EncodeUtil.toBinary(temp, EncodeRadix.HEX), 16);
}
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 分组解密(128位一组),(有iv【偏移量】CFB版)
* @param encrytedText 密文
* @param iv 偏移量
* @return 明文
*/
private String decryptCFB(String encrytedText,String iv) {
try {
//base64解码
byte[] bytes = new BASE64Decoder().decodeBuffer(encrytedText);
String str = new String(bytes,Charset.forName("UTF8"));
int textLen = str.length();
StringBuilder sb = new StringBuilder();
int divLen = textLen < 32 ? 1 : (int)(Math.ceil(textLen/(4*8*1.0))); //因为加密后会自动填充所以长度必为字符长度的倍数(HEX 4位)
//CFB解密初始化向量(转为16进制方便计算
String decryptedPart = iv;
//分组解密
for (int i = 0; i< divLen; i++) {
int startIndex = i * (4*8);
int endIndex = (startIndex + (4*8));
String temp = str.substring(startIndex, endIndex);
//转换成16位的字符,方便strXor运算
temp = EncodeUtil.binaryToStr(EncodeUtil.toBinary(temp, EncodeRadix.HEX), 16);
//尾部填充
while(decryptedPart.length() < AESConstant.DIV_LEN) {
decryptedPart += " ";
}
//转换成16位的字符,方便strXor运算
sb.append(
strXor(EncodeUtil.binaryToStr(baseEncrypt(decryptedPart), 16),temp)
);
decryptedPart = temp;
}
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
コードを実行する
public static void main(String[] args) {
AesUtil util = new AesUtil();
//偏移量(8个字符,每个字符16位)
String iv = UUID.randomUUID().toString().substring(0,8);
//CFB(密文反馈模式)
String encrytedStr = util.encrypt(
"{\"code\":200,\"message\":\"成功!\",\"data\":{\"id\":\"2103813902831\",\"name\":\"章鱼哥是我啊\",\"gender\":\"男\"}}"
,iv
,EncryptMode.CFB
);
System.out.println("encrytedStr = " + encrytedStr);
System.out.println("result= " + util.decrypt(encrytedStr,iv,EncryptMode.CFB));
}
最後に、スクリーンショットを実行します
———————————————PKCS5Padding については後で説明します —————————————————————