学习目标:了解一些常见的密码学算法

Hex编码

什么是Hex编码

Hex编码是一种用16个字符表示任意二进制数据的方法。它是一种编码,而非加密算法

java代码如下:

package org.example;

import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;

//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
public class Main {
public static void main(String[] args) {
String name = "许之少年凌云志, 曾许人间第一流";
byte[] bytes = name.getBytes();
System.out.println(bytes);
String encode = HexBin.encode(bytes);
System.out.println(encode);
}
}

这里注意把编码方式改成UTF-8否则会报错,修改完后要重启idea

url编码与HEX编码大体相同,区别就是在每组字节前面加了个%

encode源码

static public String encode(byte[] binaryData) {
if (binaryData == null)
return null;
int lengthData = binaryData.length;
int lengthEncode = lengthData * 2;
char[] encodedData = new char[lengthEncode];
int temp;
for (int i = 0; i < lengthData; i++) {
temp = binaryData[i];
if (temp < 0)
temp += 256;
encodedData[i*2] = lookUpHexAlphabet[temp >> 4];
encodedData[i*2+1] = lookUpHexAlphabet[temp & 0xf];
}
return new String(encodedData);
}

在安卓当中使用

导入依赖

implementation ("com.squareup.okhttp3:okhttp:4.9.3")
Log.d("hyq", "测试代码");
ByteString byteString = ByteString.of("好有钱".getBytes());
System.out.println(byteString.hex());

Hex编码的特点

  • 用0-9 a-f 16个字符表示
  • 每个十六进制字符代表4bit,也就是2个十六进制字符代表哪一个字节
  • 在实际引用中,比如密钥初始化,一定要分清楚传进去的密钥是哪种编码写的,采用对应方式解析,才能得到正确结果

手写代码

package org.example;

public class HexEncoder {
private static final char[] lookupHexAlphabet = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9','A', 'B', 'C', 'D', 'E', 'F'
};

public static String encode(byte[] binaryData) {
if(binaryData == null) return null;
int lengthData = binaryData.length;
int lengthEncode = lengthData * 2;
char[] encodeData = new char[lengthEncode];
int temp;
for(int i=0; i<lengthData; i++) {
temp = binaryData[i];
if(temp < 0) temp += 256;
encodeData[2*i] = lookupHexAlphabet[temp>> 4];
encodeData[2*i+1] = lookupHexAlphabet[temp & 0xF];
}
return new String(encodeData);
}

public static void main(String[] args) {
String name = "好有钱";
byte[] binaryData = name.getBytes();
System.out.println(encode(binaryData));
}
}

Base64

什么是base64

Base64是一种用64个字符表示任意二进制数据的方法。它是一种编码,而非加密算法

A-Z a-z 0-9 + / =

Base64的应用

RSA密钥、加密后的密文、图片等数据中,会有一些不可见字符。直接转换成文本传输的话,会有乱码、数据错误、数据丢失等情况出现,就可以使用Base64编码

Base64编码的实现

java中的实现

public class Main {
public static void main(String[] args) {
String s = Base64.getEncoder().encodeToString("好有钱12345678".getBytes(StandardCharsets.UTF_8));
System.out.println(s);
}
}

android中的实现

第一种,使用ByteString类

需要先导入okhttp3依赖

implementation ("com.squareup.okhttp3:okhttp:4.9.3")
ByteString byteString = ByteString.of("好有钱".getBytes());
System.out.println("byteString base64: " + byteString.base64());

第二种,在android studio中使用java库

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
String s = Base64.getEncoder().encodeToString("好有钱".getBytes());
byte[] encode = Base64.getEncoder().encode("好有钱".getBytes());
System.out.println("java.util.Base64" + new String(encode));
System.out.println("java.util.Base64" + s);
}

第三种,使用安卓自带的

String s = android.util.Base64.encodeToString("好有钱".getBytes(), 0);
System.out.println("android.util.Base64" + s);

Base64码表的妙用

为了传输数据安全,通常会对Base64数据进行URL编码,或者会把+和/替换成-和_

Base64编码细节

每个Base64字符代表原数据中的6bit

Base64编码后的字符数,是4的倍数

编码的字节数是3的倍数时,不需要填充

Base64编码的特点

  • Base64编码是编码,不是压缩,编码后只会增加字节数
  • 算法可逆,解码很方便,不用于私密信息通讯
  • 标准的Base64每行为76个字符,行末添加换行符
  • 加密后的字符串只有65种字符,不可打印字符也可传输
  • 在Java层可以通过hook对应方法名来快速定位关键代码
  • 在so层可以通过输入输出的数据和码表来确定算法

MD5(消息摘要算法)

摘要长度为128bit

MD5的java实现

MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update("hyq".getBytes());
md5.digest()

MD5的一些性质

加密后的字节数组可以编码成Hex、Base64

没有任何输入,也能计算hash值

碰到加salt的MD5,可以直接输入空的值,得到结果去CMD5查询一下,有可能就得到salt(但是salt不能太长)

SHA(消息摘要算法)

SHA是一系列算法,以SHA-1、SHA-256、、、命名

除了SHA-1的摘要长度是160bit,别的摘要长度都是后面的数字

SHA的Java实现

MessageDisgest sha1 = MessageDigest.getInstance("SHA-1");
sha1.update("hyq".getBytes());
sha1.digest();

SHA的一些性质

加密后的字节数组可以编码成Hex、Base64

没有任何输入,也能计算hash值

MAC系列算法(消息认证码,对称)

算法 摘要长度(bit)
HmacMD5 128
HmacSHA1 160
HmacSHA256 256
HmacSHA384 384
HmacSHA512 512
HmacMD2 128
HmacMD4 128
HmacSHA224 224

MAC算法与MD和SHA的区别是多了一个密钥,密钥可以随便给

MAC的java实现

SecretKeySpec secretKeySpec = new SecretKeySpec("a12345678".getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance(secretKeySpec.getAlgorithm());
mac.init(secretKeySpec);
mac.update("hyq".getBytes());
mac.doFinla();

MAC的一些性质

加密后的字节数组可以编码成Hex、Base64

没有任何输入,也能计算hash值

DES(对称)

DES的java实现

//第一种密钥产生方式	不常用,了解下即可
DESKeySpec desKeySpec = new DESKeySpce("12345678".getBytes());
SecretKeyFactory key = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = key.generateSecret(desKeySpec);

//第二种密钥产生方式
SecretKeySpec secretKeySpec = new SecretKeySpec("12345678".getBytes(), "DES");
Cipher des = Cipher.getInstance("DES");
des.init(Ciphter.ENCRYPT_MODE, secretKeySpec);
des.doFinal("hyq".getBytes());

ECB模式和CBC模式的区别

  • 没有指明加密模式和填充方式,表示使用默认的DES/ECB/PKCS5Padding

  • 加密后的字节数组可以编码成Hex、Base64

  • DES算法明文按64位进行分组加密

  • 要复现一个对称加密算法,需要得到以下几个东西

    明文、key、iv、mode、padding

  • 明文、key、iv需要注意解析方式,而且不一定是字符串形式

  • 如果加密模式是ECB,则不需要iv

  • 如果明文中有两个分组的内容相同,ECB会得到完全一样的密文,CBC不会

  • 加密算法的结果通常与明文等长或者更长,如果变短了,那可能是gzip、protubuf

DESede(对称)

用第一个密钥加密,然后用第二个密钥解密,最后再用第三个密钥加密

DESede的java实现

// 第一种产生密钥的方式
DESedeKeySpec desedeKey = new DESedeKeySpec("1234567812345678").getBytes();
SecretKeyFactory key = SecretKeyFactory.getInstance("DESede");
SecretKeySpec secretKey = key.generateSecret(desKeySpec);
//第二种密钥产生方式
SecretKeySpec secretKeySpec = new SecretKeySpec("1234567812345678".getBytes(), "DESede");
Ciphter ciphter_desede = Cipher.getInstance("DESede");
ciphter_desede.init(Ciphter.ENCRYPT_MODE, secretKeySpec);
ciphter_desede.doFinal("hyq".getBytes());

AES(对称)

根据密钥长度的不同,可以分为AES128、AES192、AES256

AES的java实现

SecretKeySpec key = new SecretKeySpec("1234567890abcdef".getBytes(), "AES");
IvParamterSpec iv = new IvParamterSpec("1234567890abcdef".getBytes());
Ciphter aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(1, key, iv);
aes.doFinal("a12345678".getBytes())

ECB模式和CBC模式的区别

在对称加密算法里,如果使用NOPadding,加密的明文必须刚好等于分组长度倍数,否则会报错

如果使用PKCS5Padding,会对加密的明文填充1字节-1个分组的长度

RSA_Base64(非对称)

私钥的格式

pkcs1格式通常开头是 ——BEGIN RSA PRIVATE KEY——

pkcs8格式通常开头是 ——BEGIN PRIVATE KEY——

Java中的私钥必须是pkcs8格式

RSA密钥的解析

byte[] keyBytes = Base64Decoder.decodeBuffer(key);
X509EncodeKeySpec keySpec = new X509EncodedKeySPec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);

byte[] keyBytes = Base64Decoder.decodeBuffer(key);
PKCS8EncodeKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generetePrivate(keySpec);

RSA加解密

Cipher cipher = Cipher.getInstance("RSA/None/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bt_encrypted = cipher.doFinal(bt_plaintext);

Cipher cipher = Cipher.getInstance("RSA/None/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bt_orinial = cipher.doFinal(bt_encrypted);

RSA模式和填充细节

  • None模式和ECB模式是一致的

  • NoPadding

    明文最多字节数位密钥字节数

    密文与密钥等长

    填充字节0,加密后的密文不变

  • pkcs1padding

    明文最大字节数为密钥字节数-11

    密文与密钥等长

    每一次的填充不一样,使得加密后的密文会变

  • 把pkcs1padding加密后的密文,用NoPadding去解密,会得到填充的字节

  • 没有指明加密模式和填充方式,表示使用默认的RSA/ECB/NOPadding

  • 加密后的字节数组可以编码成Hex、Base64

RSA_Hex

RSA密钥的解析

BigInteger N = new BigInteger(stringN, 16);
BigInteger E = new BigInteger(stringE, 16);
RSAPublicKeySpec spec = new RSAPublicKeySpec("RSA");
PublicKey publicKey = keyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(spec);

多种加密算法的常见结合套路

随机生成AES密钥A

A密钥用于AES加密数据,得到数据密文B

使用RSA对A密钥加密,得到密钥密文C

提交密钥密文C和数据密文B给服务器

MainActivity.java

package com.example.demo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.example.demo.databinding.ActivityMainBinding;

import java.util.Base64;
import java.util.Random;

import okio.ByteString;

public class MainActivity extends AppCompatActivity {

// Used to load the 'demo' library on application startup.
static {
System.loadLibrary("demo");
}

private ActivityMainBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
try{
//客户端加密部分 cipherText和cipherKey 需要同时提交给服务器
String AESKey = generateAESKey();
String cipherText = AES.encryptAES("hyq", AESKey);
String cipherKey = RSA_Base64.encryptByPublicKey(AESKey);
//服务器解密部分
String plainKey = RSA_Base64.decryptByPrivateKey(cipherKey);
String plainText = AES.decryptAES(ByteString.decodeBase64(cipherText).toByteArray(), plainKey);

}catch (Exception e){
e.printStackTrace();
}
}

/**
* A native method that is implemented by the 'demo' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();

public static String generateAESKey(){
StringBuffer stringBuffer = new StringBuffer();
Random random = new Random();
for(int i = 0; i < 16; i++){
int random_i = random.nextInt(100);
String temp = "0" + Integer.toHexString(random_i);
temp = temp.substring(temp.length() - 2);
stringBuffer.append(temp);
}
String resultKey = stringBuffer.toString();
return resultKey;
}
}

AES.java

package com.example.demo;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import okio.ByteString;

public class AES {

public static String encryptAES(String plainText, String AESKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
SecretKeySpec aesKey = new SecretKeySpec(ByteString.decodeHex(AESKey).toByteArray(), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec("0123456789abcdef".getBytes());
Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(Cipher.ENCRYPT_MODE, aesKey, ivParameterSpec);
byte[] bytes = aes.doFinal(plainText.getBytes());
ByteString of = ByteString.of(bytes);
return of.base64();
}

public static String decryptAES(String plainText, String AESKey) throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException {
SecretKeySpec aesKey = new SecretKeySpec(ByteString.decodeHex(AESKey).toByteArray(), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec("0123456789abcdef".getBytes());
Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
byte[] bytes = aes.doFinal(plainText.getBytes());
return new String(bytes);
}
}

RSA_Base64.java

package com.example.demo;

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;

import okio.ByteString;

public class RSA_Base64 {
public static String publicKeyBase64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDxRQHxL/8xZ1EaNmQBGZnpMiCY" +
"7gRzog6nDjfBJacytEiVJnJRuq1V/D+JKaXDwetsCnSUaz65LCFHU09OSEYee5oC" +
"iI0ql21EA306c91oT/fQpPngQGZHLUtDOUdJVlAKnicCvmR24NqyNKFuY8L0cnB1" +
"zcax73Rf+Ctf/lxAOwIDAQAB";

public static String privateKeyBase64 = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAPFFAfEv/zFnURo2\n" +
"ZAEZmekyIJjuBHOiDqcON8ElpzK0SJUmclG6rVX8P4kppcPB62wKdJRrPrksIUdT\n" +
"T05IRh57mgKIjSqXbUQDfTpz3WhP99Ck+eBAZkctS0M5R0lWUAqeJwK+ZHbg2rI0\n" +
"oW5jwvRycHXNxrHvdF/4K1/+XEA7AgMBAAECgYEAsGkDrYWps0bW7zKb1o4Qkojb\n" +
"etZ2HNJ+ojlsHObaJOHbPGs7JXU4bmmdTz5LfSIacAoJCciMuTqCLrPEhfmkghPq\n" +
"U2MjyjfqYdXALoP7l/vt6QmjY/g1IAsaZN9nFhyjJ2WzgOx1f7gZj4NBSvTdSj7H\n" +
"m5E24zkm+p7Qw1z6/mkCQQD7WSXAXcv2v3Vo6qi1FUlkzQgCQLFYqXNSOSPpno3y\n" +
"oohUFIkMj0bYGbVE1LzV30Rb6Z8e8yQAByw6l8RuGb2PAkEA9bwb2euyOe6CcqpE\n" +
"PNFc+7UlOJAy5epVFKHbu0aNivVpU0hsphqjIGXJGHYTspyEOLqtzILqKPZr6pru\n" +
"WvJUlQJBAJoImQUZtlyCGs7wN/G5mN/ocscGpGikd+Lk16hdHbqbdpaoexCyYYUf\n" +
"xCHpicw75mW5d2V9Ngu6WZWS2rNqnOsCQCoMK//X8sEy7KNOOyrk8DIpxtqs4eix\n" +
"dil3oK+k3OdgIsubYuvxNuR+RjCnU6uGWKGUX9TUudiUgda89/gb6xkCQFm8gD6n\n" +
"AyN+PPPKRq2M84+cAbnvjdIAY3OFHfkaoWCtEj5DR0UDuVv7jN7+re2D7id/GkAe\n" +
"FAmhvYQwwLnifrw=";

public static PublicKey generatePublicKey() throws Exception{
byte[] publicKeyBase64Bytes = ByteString.decodeBase64(publicKeyBase64).toByteArray();
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBase64Bytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(x509EncodedKeySpec);
}

public static PrivateKey generatePrivateKey() throws Exception{
byte[] privateKeyBase64Bytes = ByteString.decodeBase64(privateKeyBase64).toByteArray();
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBase64Bytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
}

public static String encryptByPublicKey(String plainText) throws Exception{
PublicKey publicKey = generatePublicKey();
Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");
instance.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = instance.doFinal(plainText.getBytes());
ByteString of = ByteString.of(bytes);
return of.base64();
}

public static String decryptByPrivateKey(String cipherText) throws Exception{
PrivateKey privateKey = generatePrivateKey();
Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");
instance.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes = instance.doFinal(cipherText.getBytes());
return new String(bytes);
}
}

数字签名算法

签名

PrivateKey priK = getPrivateKey(str_priK);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(priK);
sig.update(data);
sig.sign();

验证

PublicKey pubK = getPublicKey(str_pubK);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(pubK);
sig.update(data);
sig.verify(sign);
package com.example.fctf;

import android.os.Bundle;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.example.fctf.databinding.ActivityMainBinding;

import org.w3c.dom.Text;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.spec.KeySpec;
import java.util.Objects;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;

import okio.ByteString;

public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;

private int num = 1;
private static String key0 = "82305002";
private static String iv0 = "82505002";
public final int getNum(){
return this.num;
}
public void setNum(int i){
this.num = i;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
final TextView textView = binding.textView;
Button button = binding.button;
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
MainActivity.a(MainActivity.this, textView, button);
} catch (IllegalBlockSizeException e) {
throw new RuntimeException(e);
} catch (BadPaddingException e) {
throw new RuntimeException(e);
}
}
});
try {
System.out.println(decrypt("whyysqwmstoryhzcontinues"));
} catch (IllegalBlockSizeException e) {
throw new RuntimeException(e);
} catch (BadPaddingException e) {
throw new RuntimeException(e);
}
}
public static final void a(MainActivity this$0, TextView tv, Button button) throws IllegalBlockSizeException, BadPaddingException {
MainActivity mainActivity = this$0;
tv.setText(String.valueOf(this$0.num));
if(this$0.check() == 20220422){
Toast.makeText(mainActivity, "Congratuations!!!", Toast.LENGTH_SHORT).show();
tv.setText("flag{" + this$0.decrypt("whyysqwmstoryhzcontinues") + "}");
}
}
public final int check(){
int i = this.num + 1;
this.num = i;
return i;
}
public final String decrypt(String str) throws IllegalBlockSizeException, BadPaddingException {
Cipher des = null;
byte[] bytes =null;
try {
Class<?> a = Class.forName(d("amF2YS5zZWN1cml0eS5NZXNzYWdlRGlnZXN0"));
Method b = a.getMethod(d("Z2V0SW5zdGFuY2U="), String.class);
Object c = b.invoke(null, d("TUQ1"));
Method e = a.getMethod(d("dXBkYXRl"), byte[].class);
e.invoke(c, key0.getBytes());
Method f = a.getMethod(d("ZGlnZXN0"));
byte[] k = (byte[]) f.invoke(c);


//反射des
// //DESKeySpec dsk = new DESKeySpec(k);
// Class<?> DESKeySpec = Class.forName("java.security.spec.KeySpec");
// Constructor<?> dESKeySpecConstructor = DESKeySpec.getConstructor(byte[].class);
// Object dsk = dESKeySpecConstructor.newInstance(k);
// //SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
// Class<?> SecretKeyFactory = Class.forName("javax.crypto.SecretKeyFactory");
// Method getInstance = SecretKeyFactory.getMethod("getInstance", String.class);
// Object keyFactory = getInstance.invoke(null, "DES");
// SecretKey key = keyFactory.generateSecret(dsk);
// Method generateSecret = SecretKeyFactory.getMethod();
// IvParameterSpec iv = new IvParameterSpec(iv0.getBytes());
// des = Cipher.getInstance("DES/CBC/PKCS5Padding");
// des.init(Cipher.ENCRYPT_MODE, key, iv);
// bytes = des.doFinal(str.getBytes());
Class<?> desKeySpecClass = Class.forName("javax.crypto.spec.DESKeySpec");
Constructor<?> desKeySpecConstructor = desKeySpecClass.getConstructor(byte[].class);
KeySpec dsk = (KeySpec) desKeySpecConstructor.newInstance(k);

// 获取 SecretKeyFactory 实例
Class<?> secretKeyFactoryClass = Class.forName("javax.crypto.SecretKeyFactory");
Method getInstanceMethod = secretKeyFactoryClass.getMethod("getInstance", String.class);
SecretKeyFactory keyFactory = (SecretKeyFactory) getInstanceMethod.invoke(null, "DES");

// 生成 SecretKey
Method generateSecretMethod = secretKeyFactoryClass.getMethod("generateSecret", KeySpec.class);
SecretKey key = (SecretKey) generateSecretMethod.invoke(keyFactory, dsk);

// 创建 IvParameterSpec 实例
Class<?> ivParameterSpecClass = Class.forName("javax.crypto.spec.IvParameterSpec");
Constructor<?> ivParameterSpecConstructor = ivParameterSpecClass.getConstructor(byte[].class);
IvParameterSpec iv = (IvParameterSpec) ivParameterSpecConstructor.newInstance(iv0.getBytes());

// 获取 Cipher 实例
Class<?> cipherClass = Class.forName("javax.crypto.Cipher");
Method getInstanceCipherMethod = cipherClass.getMethod("getInstance", String.class);
des = (Cipher) getInstanceCipherMethod.invoke(null, "DES/CBC/PKCS5Padding");

// 初始化 Cipher
Method initMethod = cipherClass.getMethod("init", int.class, java.security.Key.class, java.security.spec.AlgorithmParameterSpec.class);
initMethod.invoke(des, Cipher.ENCRYPT_MODE, key, iv);

// 执行加密操作
Method doFinalMethod = cipherClass.getMethod("doFinal", byte[].class);
bytes = (byte[]) doFinalMethod.invoke(des, str.getBytes());
// MessageDigest md5 = MessageDigest.getInstance("MD5");
// md5.update("a12345678".getBytes());
// byte[] bytes=md5.digest();
// ByteString byteString = ByteString.of(bytes);
// String key = byteString.hex();
// System.out.println(key);
} catch (Exception e) {
e.printStackTrace();
}
ByteString byteString = ByteString.of(bytes);
return byteString.hex();
}

private String d(String str){
return new String(Base64.decode(str, 0));
}

}