简单的AES与RSA组合加密解密测试工具类

RSA与AES工具类

1. 需求分析

公司要新增一个dmz网关,网关中已经有源码部分,可以点击查看。此处为整理方便直接查看。具体需求为外部进入数据需要加密解密,使用AES与RSA进行加密解密,具体过程如下

  1. 使用AES生成随机加密秘钥转换为base64字符串
  2. 生成网关验签的sign信息(md5[系统标识|业务时间|约定md5随机key])
  3. 使用AES随机秘钥对请求参数Body进行AES对称加密
  4. 将AES随机秘钥byte[]转换为16进制字符串
  5. 将16进制的秘钥串进行RSA非对称加密[提前自己生成需要的私钥和公钥]既signature

2. 工具类

AES工具类

package cn.git.dmz.util;

import cn.git.common.exception.ServiceException;
import cn.git.dmz.constants.DmzConstants;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

/**
 * @description: AES加解密工具类
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2024-01-05 08:52:41
 */
@Component
@Slf4j
public class AESUtil {

    @Autowired
    private LogUtil logUtil;

    /**
     * 获取随机AES密钥
     * @return 返回加密秘钥
     */
    public SecretKey getRandomKey() {
        // 使用种子生成种子随机秘钥,如果想每一次都生成不同秘钥,则去掉种子即可
        SecureRandom secureRandom = new SecureRandom(DmzConstants.DMZ_LOCK_KEY.getBytes(StandardCharsets.UTF_8));
        // 实例
        KeyGenerator kg = null;
        try {
            kg = KeyGenerator.getInstance(DmzConstants.AES_TYPE);
            // AES
            kg.init(DmzConstants.KEY_SIZE, secureRandom);
            // 生成密钥
            SecretKey secretKey = kg.generateKey();
            return secretKey;
        } catch (NoSuchAlgorithmException e) {
            String errorMsg = logUtil.getStackTraceInfo(e, DmzConstants.NUM_2000);
            log.error(errorMsg);
            throw new ServiceException("获取随机AES密钥失败");
        }
    }

    /**
     * 加密
     *
     * @param content 内容
     * @param randomPassKey     秘钥
     * @return 加密后的数据
     */
    public String aesEncrypt(String content, String randomPassKey) {
        try {
            // 新建Cipher 类
            Cipher cipher = Cipher.getInstance(DmzConstants.AES_MODE);
            // 初始化秘钥
            SecretKeySpec sks = new SecretKeySpec(Base64.getDecoder().decode(randomPassKey), DmzConstants.AES_TYPE);
            // 初始化加密类
            cipher.init(Cipher.ENCRYPT_MODE, sks);
            // 进行加密
            byte[] encrypt = cipher.doFinal(content.getBytes());
            // 进行base64编码
            encrypt = Base64.getEncoder().encode(encrypt);
            // 转成字符串返回
            return new String(encrypt, StandardCharsets.UTF_8);
        } catch (Exception e) {
            String errorMsg = logUtil.getStackTraceInfo(e, DmzConstants.NUM_2000);
            log.error(errorMsg);
            throw new ServiceException("AES加密失败");
        }
    }

    /**
     * AES进行解密数据
     *
     * @param content 解密内容
     * @param randomPassKey aes随机秘钥
     * @return 解密后数据
     */
    public String aesDecrypt(String content, String randomPassKey) {
        try {
            // base64里会携带换行符导致解码失败,替换base64里的换行
            content = content.replaceAll("[\\n\\r]", StrUtil.EMPTY);
            // base64 解码,跟上面的编码对称
            byte[] data = Base64.getDecoder().decode(content.getBytes(StandardCharsets.UTF_8));
            // 新建Cipher 类
            Cipher cipher = Cipher.getInstance(DmzConstants.AES_MODE);
            // 初始化秘钥
            SecretKeySpec keySpec = new SecretKeySpec(Base64.getDecoder().decode(randomPassKey), DmzConstants.AES_TYPE);
            // 初始化类
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            // 进行AES
            byte[] result = cipher.doFinal(data);
            // 返回解密后内容信息
            return new String(result);
        } catch (Exception e) {
            String errorMsg = logUtil.getStackTraceInfo(e, DmzConstants.NUM_2000);
            log.error(errorMsg);
            throw new ServiceException("AES解密失败");
        }
    }

    /**
     * 将byte数组转换成16进制String
     * @param bytes 转换的byte数组
     * @return 16进制字符串
     */
    public String parseBytesToHexStr(byte[] bytes) {
        StringBuilder stringBuilder = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(b & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            stringBuilder.append(hex.toUpperCase());
        }
        return stringBuilder.toString();
    }


    /**
     * 将16进制String转换为byte数组
     * @param hexStr 16进制字符串
     * @return 2进制byte数组
     */
    public byte[] parseHexStrToBytes(String hexStr) {
        // 空值判断
        if (hexStr.length() < 1) {
            return null;
        }
        byte[] result = new byte[hexStr.length() / 2];
        for (int i = 0; i < hexStr.length() / DmzConstants.NUM_2; 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;
    }

}

RSA工具类

package cn.git.dmz.util;

import cn.git.common.exception.ServiceException;
import cn.git.dmz.constants.DmzConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * @description: RSA加解密工具类
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2024-01-05 08:58:58
 */
@Component
@Slf4j
public class RSAUtil {

    @Autowired
    private LogUtil logUtil;

    /**
     * RSA初始化key pair,初始化公钥私钥方法
     * @return KeyPair
     */
    public KeyPair getRSAKeyPair() {
        try {
            // 生成RSA密钥对
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance(DmzConstants.RSA_TYPE);
            // 根据种子生成秘钥对
            SecureRandom secureRandom = new SecureRandom(DmzConstants.DMZ_LOCK_KEY.getBytes(StandardCharsets.UTF_8));
            keyGen.initialize(DmzConstants.NUM_1024, secureRandom);
            return keyGen.generateKeyPair();
        } catch (Exception e) {
            String errorMsg = logUtil.getStackTraceInfo(e, DmzConstants.NUM_2000);
            log.error(errorMsg);
            throw new ServiceException("RSA初始化keyPairs失败");
        }
    }

    /**
     * RSA加密
     * @param data 待加密数据
     * @param publicKeyStr 公钥
     * @return 加密后的数据
     */
    public String encryptRSA(String data, String publicKeyStr) {
        try {
            byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
            // 初始化公钥key
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(DmzConstants.RSA_TYPE);
            PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);

            // 使用公钥进行加密
            Cipher encryptCipher = Cipher.getInstance(DmzConstants.RSA_MODE);
            encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] encryptedBytes = encryptCipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encryptedBytes);
        } catch (Exception e) {
            String errorMsg = logUtil.getStackTraceInfo(e, DmzConstants.NUM_2000);
            log.error(errorMsg);
            throw new ServiceException("RSA加密失败");
        }
    }

    /**
     * 进行RSA解密
     * @param content 解密文本
     * @param privateKeyStr 私钥字符串
     * @return
     */
    public String decryptRSA(String content, String privateKeyStr) {
        try {
            byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
            // 初始化私钥
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(DmzConstants.RSA_TYPE);
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);

            // 使用私钥进行解密
            Cipher decryptCipher = Cipher.getInstance(DmzConstants.RSA_MODE);
            decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] decryptedBytes = decryptCipher.doFinal(Base64.getDecoder().decode(content));
            return new String(decryptedBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            String errorMsg = logUtil.getStackTraceInfo(e, DmzConstants.NUM_2000);
            log.error(errorMsg);
            throw new ServiceException("RSA解密失败");
        }
    }
}

3. 本人测试类

测试类则是没有封装成util的方法,进行需求进行简单开发的逻辑代码,可以直接运行,注意如果要自己使用,修改自己的秘钥

package cn.git.demo.contract;

import cn.git.common.constant.CommonConstant;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * a11111密码初始化类
 * 密码初始化
 */
public class TestEncodePassword {

    /**
     * 外部系统请求网关加密密匙
     */
    public static final String DMZ_LOCK_KEY = "DMZ:XdXt:loan==,";

    /**
     * AES
     * 算法名称/加密模式/数据填充方式
     */
    public static String MODE = "AES/ECB/PKCS5Padding";

    /**
     * RSA
     * 算法名称/加密模式/数据填充方式
     */
    private final static String RSA_MODE = "RSA/ECB/PKCS1Padding";

    /**
     * 加密模式
     */
    public static String KEY_ALGORITHM = "AES";

    /**
     * 加密单位长度
     */
    private static final int KEY_SIZE = 128;

    /**
     * RSA公钥
     */
    private static final String RSA_PUB_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCMZ/Q5CIvZjBTLD4sTwxbWYNNZWd7rImD+sAgwOAf+nH1vWN+Qk63iBGQYHzYK5EhSAFqsqX04lsTiJUH42YRJ9yIXUnvd6Osp2JYOXW8WTGgMtfDJp5WzbYzJRxQ0RmJNcfqMcHPVepgpjs2HNj59aTqNPLdiouRSRlh2E0umGwIDAQAB";

    /**
     * RSA私钥
     */
    private static final String RSA_PRI_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIxn9DkIi9mMFMsPixPDFtZg01lZ3usiYP6wCDA4B/6cfW9Y35CTreIEZBgfNgrkSFIAWqypfTiWxOIlQfjZhEn3IhdSe93o6ynYlg5dbxZMaAy18MmnlbNtjMlHFDRGYk1x+oxwc9V6mCmOzYc2Pn1pOo08t2Ki5FJGWHYTS6YbAgMBAAECgYEAiQRNNXccmsDz7bGOZEumtrAor/Je8xFKnGCGrR+Q1aw7UHTnPvyO3JiyYUPcBkb+OF+2HPcNhzLCkXoQZltGlznwOwGvHl4qEheVAMwdgijuYQZpsiGQyVyr4C506ydoPjPXbWD+9GGLuakHtIlRP9FAGvwQe/5fkUYsiAJD8mkCQQDop8hxDCrsH9eBQ7PusaGjmW213zmX0O5yAntwuznX3zsQOo+AgeM00ottF4J5BXWCeyF5ZxKi6WoPjSgTw77fAkEAmn6QNwMSEbcWHbuaC3ofjcYhnOl47aQWNr+56G+Wc7vs4xs8PXdd3sVmlepOFSdJLWCxguUBO60Dg6cpnAFMRQJABZLPaHXkKVfx77TRgKxctPCeAjdgx9RHgg+xKVgy4IsGfTMJ8Qgriz5n/KsNgxywXfnZKXFgrupskgbNqPuNfQJAQlOjxnpi/4gCzrED6Xl8onk1ZRA3Ao83mjmlrsx5YyaDBN1kd18PxdwptqLo8tvy5rBkhTWb2erlX1gc3QURoQJABakHaa+GNmKyc7pasJHQ3HMR39k888TQPKEQiE461oc5//Cyqek7u91rCG0HR7O4Hk+ostH9lkxc4bG+HYgWgg==";

    public static void main(String[] args) throws Exception {
        testAES();
    }

    /**
     * AES加解密,RES加密
     * 1.生成需要发送的交易请求明文报文JSON格式
     * 2.生成sign签名:MD5(sysid|transtime|md5key),并将生成的sign签名放入明文报文的JSON报文中
     * 3.生成AES随机密钥:Byte[] = getRandomKey(随机加密串)
     * 4.将随机密钥由二进制转为16进制:byteToHexString(AES随机密钥)
     * 5.使用AES随机密钥对报文明文进行加密:message=aesEncrypt(报文明文)
     * 6.使用RSA公钥对随机密钥进行加密,signature=BASE64.encode(RSAPublicEnc(16进制AES密钥)) 此时,将计算得出的message字段以及signature字段以post的方式发送至数字信贷平台。
     */
    private static void testAES() throws Exception {
        // 生成测试数据
        String username = "331326";
        String passowrd = "a11111";
        List<String> userList = new ArrayList<>();
        userList.add("赵六6.使用RSA公钥对随机密钥进行加密,signature=BASE64.encode(RSAPublicEnc(16进制AES密钥)) 此时,将计算得出的message字段以及signature字段以post的方式发送至数字信贷平台。");
        userList.add("赵76.使用RSA公钥对随机密钥进行加密,signature=BASE64.encode(RSAPublicEnc(16进制AES密钥)) 此时,将计算得出的message字段以及signature字段以post的方式发送至数字信贷平台。");
        userList.add("赵86.使用RSA公钥对随机密钥进行加密,signature=BASE64.encode(RSAPublicEnc(16进制AES密钥)) 此时,将计算得出的message字段以及signature字段以post的方式发送至数字信贷平台。");

        // 测试数据生成需要发送的交易请求明文报文JSON格式
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("username", username);
        jsonObject.put("password", passowrd);
        jsonObject.put("userList", userList);
        String jsonMessage = jsonObject.toJSONString();

        // 获取随机加密私钥key
        Key privateRandomAESKey = getRandomKey();
        // 随机加密串
        String privateRandomAESKeyStr = Base64.getEncoder().encodeToString(privateRandomAESKey.getEncoded());
        System.out.println("AES随机密钥 -> : " + privateRandomAESKeyStr);

        // 将随机密钥由二进制转为16进制:byteToHexString(AES随机密钥)
        String hexPassKey = parseByte2HexStr(privateRandomAESKey.getEncoded());
        System.out.println("AES十六进制hexPassKey -> : " + hexPassKey);

        // 获取RSA公钥和私钥
        KeyPair keyPair = generatorKey();
        String aesPublicKey = RSA_PUB_KEY;
        String aesPrivateKey = RSA_PRI_KEY;
        if (StrUtil.isBlank(aesPublicKey)) {
            aesPublicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
        }
        if (StrUtil.isBlank(aesPrivateKey)) {
            aesPrivateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
        }
        System.out.println("RSA公钥为 -> : " + aesPublicKey);
        System.out.println("RSA私钥为 -> : " + aesPrivateKey);

        // RSA对16进制key进行加密
        String signature = encryptRSA(hexPassKey, aesPublicKey);
        System.out.println("RSA加密signature为 -> : " + signature);

        // 使用随机秘钥AES加密message信息
        String encodeMessage = encrypt(jsonMessage, privateRandomAESKeyStr);
        System.out.println("AES加密message为 -> : " + encodeMessage);

        // 解密signature生成16进制字符串AES秘钥
        String decodeHexPassKey = decryptRSA(signature, aesPrivateKey);
        System.out.println("RSA解密signature为 -> : " + decodeHexPassKey);

        // 解密16进制字符串AES秘钥
        byte[] aesRandomKeyBytes = parseHexStr2Byte(decodeHexPassKey);
        System.out.println("AES获取秘钥为 -> : " + Base64.getEncoder().encodeToString(aesRandomKeyBytes));

        // 使用 signature 解密message数据
        String decodeMessage = decrypt(encodeMessage, Base64.getEncoder().encodeToString(aesRandomKeyBytes));
        System.out.println("AES解密message为 -> : " + decodeMessage);
    }

    /**
     * 获取密钥
     *
     * @return
     * @throws Exception
     */
    private static SecretKey getRandomKey() throws Exception {
        // 随机数
        SecureRandom secureRandom = new SecureRandom(DMZ_LOCK_KEY.getBytes(StandardCharsets.UTF_8));
        // 实例
        KeyGenerator kg = KeyGenerator.getInstance("AES");
        // AES
        kg.init(KEY_SIZE, secureRandom);
        // 生成密钥
        SecretKey secretKey = kg.generateKey();
        return secretKey;
    }

    /**
     * 将byte数组转换成16进制String
     * @param buf
     * @return
     */
    public static String parseByte2HexStr(byte[] buf) {
        StringBuffer sb = new StringBuffer();
        for (byte b : buf) {
            String hex = Integer.toHexString(b & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

    /**
     * 将16进制String转换为byte数组
     * @param hexStr
     * @return
     */
    public static byte[] parseHexStr2Byte(String hexStr) {
        if (hexStr.length() < 1)
            return null;
        byte[] result = new byte[hexStr.length() / 2];
        for (int i = 0; i < hexStr.length() / 2; 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;
    }

    /**
     * 加密
     *
     * @param content 内容
     * @param randomPassKey     秘钥
     * @return 加密后的数据
     */
    public static String encrypt(String content, String randomPassKey) throws Exception {
        // 新建Cipher 类
        Cipher cipher = Cipher.getInstance(MODE);
        // 初始化秘钥
        SecretKeySpec sks = new SecretKeySpec(Base64.getDecoder().decode(randomPassKey), KEY_ALGORITHM);
        // 初始化加密类
        cipher.init(Cipher.ENCRYPT_MODE, sks);
        // 进行加密
        byte[] encrypt = cipher.doFinal(content.getBytes());
        // 这一步非必须,是因为二进制数组不方便传输,所以加密的时候才进行base64编码
        encrypt = Base64.getEncoder().encode(encrypt);
        // 转成字符串返回
        return new String(encrypt, StandardCharsets.UTF_8);
    }

    /**
     * 解密数据
     *
     * @param content 内容
     * @param randomPassKey 秘钥
     * @return 数据
     */
    public static String decrypt(String content, String randomPassKey) throws Exception{
        // 替换base64里的换行,这一步也非必须,只是有些情况base64里会携带换行符导致解码失败
        content = content.replaceAll("[\\n\\r]", "");
        // base64 解码,跟上面的编码对称
        byte[] data = Base64.getDecoder().decode(content.getBytes(StandardCharsets.UTF_8));
        // 新建Cipher 类
        Cipher cipher = Cipher.getInstance(MODE);
        // 初始化秘钥
        SecretKeySpec keySpec = new SecretKeySpec(Base64.getDecoder().decode(randomPassKey), KEY_ALGORITHM);
        // 初始化类
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        // 解密
        byte[] result = cipher.doFinal(data);
        // 返回解密后的内容
        return new String(result);
    }


    /**
     * RSA初始化key pair
     * @return KeyPair
     */
    private static KeyPair generatorKey() throws NoSuchAlgorithmException {
        // 生成RSA密钥对
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(DMZ_LOCK_KEY.getBytes(StandardCharsets.UTF_8));
        keyGen.initialize(1024, secureRandom);
        KeyPair pair = keyGen.generateKeyPair();
        return pair;
    }

    /**
     * RSA加密
     * @param data 待加密数据
     * @param publicKeyStr 公钥
     * @return 加密后的数据
     */
    private static String encryptRSA(String data, String publicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException {
        byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
        // 初始化公钥key
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);

        // 使用公钥进行加密
        Cipher encryptCipher = Cipher.getInstance(RSA_MODE);
        encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encryptedBytes = encryptCipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    /**
     * RSA解密
     * @param data
     * @param privateKeyStr
     * @return
     */
    private static String decryptRSA(String data, String privateKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException {
        byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
        // 初始化私钥
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);

        // 使用私钥进行解密
        Cipher decryptCipher = Cipher.getInstance(RSA_MODE);
        decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] decryptedBytes = decryptCipher.doFinal(Base64.getDecoder().decode(data));
        return new String(decryptedBytes, "UTF-8");
    }
}

测试结果展示如下
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值