RSA、Base64、AES加密解密

1、RSA

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;

public class RSAUtil {

    //密钥对
    private KeyPair keyPair = null;

    /**
     * 初始化密钥对
     */
    public RSAUtil(){
        try {
            this.keyPair = this.generateKeyPair();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成密钥对
     * @return KeyPair
     * @throws Exception
     */
    private KeyPair generateKeyPair() throws Exception {
        try {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA",new org.bouncycastle.jce.provider.BouncyCastleProvider());
            //这个值关系到块加密的大小,可以更改,但是不要太大,否则效率会低
            final int KEY_SIZE = 1024;
            keyPairGen.initialize(KEY_SIZE, new SecureRandom());
            KeyPair keyPair = keyPairGen.genKeyPair();
            return keyPair;
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }

    }

    /**
     * 生成公钥
     * @param modulus
     * @param publicExponent
     * @return RSAPublicKey
     * @throws Exception
     */
    private RSAPublicKey generateRSAPublicKey(byte[] modulus, byte[] publicExponent) throws Exception {

        KeyFactory keyFac = null;
        try {
            keyFac = KeyFactory.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
        } catch (NoSuchAlgorithmException ex) {
            throw new Exception(ex.getMessage());
        }
        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(publicExponent));
        try {
            return (RSAPublicKey) keyFac.generatePublic(pubKeySpec);
        } catch (InvalidKeySpecException ex) {
            throw new Exception(ex.getMessage());
        }

    }

    /**
     * 生成私钥
     * @param modulus
     * @param privateExponent
     * @return RSAPrivateKey
     * @throws Exception
     */
    private RSAPrivateKey generateRSAPrivateKey(byte[] modulus, byte[] privateExponent) throws Exception {
        KeyFactory keyFac = null;
        try {
            keyFac = KeyFactory.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
        } catch (NoSuchAlgorithmException ex) {
            throw new Exception(ex.getMessage());
        }
        RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent));
        try {
            return (RSAPrivateKey) keyFac.generatePrivate(priKeySpec);
        } catch (InvalidKeySpecException ex) {
            throw new Exception(ex.getMessage());
        }
    }

    /**
     * 加密
     * @param key 加密的密钥
     * @param data 待加密的明文数据
     * @return 加密后的数据
     * @throws Exception
     */
    public byte[] encrypt(Key key, byte[] data) throws Exception {
        try {
            Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
            cipher.init(Cipher.ENCRYPT_MODE, key);
            //获得加密块大小,如:加密前数据为128个byte,而key_size=1024 加密块大小为127 byte,加密后为128个byte;
            //因此共有2个加密块,第一个127 byte第二个为1个byte
            int blockSize = cipher.getBlockSize();
            int outputSize = cipher.getOutputSize(data.length);//获得加密块加密后块大小
            int leavedSize = data.length % blockSize;
            int blocksSize = leavedSize != 0 ? data.length / blockSize + 1 : data.length / blockSize;
            byte[] raw = new byte[outputSize * blocksSize];
            int i = 0;
            while (data.length - i * blockSize > 0) {
                if (data.length - i * blockSize > blockSize)
                    cipher.doFinal(data, i * blockSize, blockSize, raw, i * outputSize);
                else
                    cipher.doFinal(data, i * blockSize, data.length - i * blockSize, raw, i * outputSize);
                //这里面doUpdate方法不可用,查看源代码后发现每次doUpdate后并没有什么实际动作除了把byte[]放到ByteArrayOutputStream中
                //,而最后doFinal的时候才将所有的byte[]进行加密,可是到了此时加密块大小很可能已经超出了OutputSize所以只好用dofinal方法。
                i++;
            }
            return raw;
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }
    }

    /**
     * 解密
     * @param key 解密的密钥
     * @param raw 已经加密的数据
     * @return 解密后的明文
     * @throws Exception
     */
    public byte[] decrypt(Key key, byte[] raw) throws Exception {
        try {
            Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
            cipher.init(cipher.DECRYPT_MODE, key);
            int blockSize = cipher.getBlockSize();
            ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
            int j = 0;
            while (raw.length - j * blockSize > 0) {
                bout.write(cipher.doFinal(raw, j * blockSize, blockSize));
                j++;
            }
            return bout.toByteArray();
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }
    }

    /**
     * 返回公钥
     * @return
     * @throws Exception
     */
    public RSAPublicKey getRSAPublicKey() throws Exception{

        //获取公钥
        RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
        //获取公钥系数(字节数组形式)
        byte[] pubModBytes = pubKey.getModulus().toByteArray();
        //返回公钥公用指数(字节数组形式)
        byte[] pubPubExpBytes = pubKey.getPublicExponent().toByteArray();
        //生成公钥
        RSAPublicKey recoveryPubKey = this.generateRSAPublicKey(pubModBytes,pubPubExpBytes);
        return recoveryPubKey;
    }

    /**
     * 获取私钥
     * @return
     * @throws Exception
     */
    public RSAPrivateKey getRSAPrivateKey() throws Exception{

        //获取私钥
        RSAPrivateKey priKey = (RSAPrivateKey) keyPair.getPrivate();
        //返回私钥系数(字节数组形式)
        byte[] priModBytes = priKey.getModulus().toByteArray();
        //返回私钥专用指数(字节数组形式)
        byte[] priPriExpBytes = priKey.getPrivateExponent().toByteArray();
        //生成私钥
        RSAPrivateKey recoveryPriKey = this.generateRSAPrivateKey(priModBytes,priPriExpBytes);
        return recoveryPriKey;
    }

}

2、AES

import com.sinosig.ec.security.AesEcb;
import com.sinosig.ec.security.HEX;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.logging.log4j.util.Strings;

public class AesUtil {

	private static Log logger = LogFactory.getLog(AesUtil.class);

	/**
	 * 加密数据
	 * 
	 * @param value
	 * @return
	 */
	public static String encrypt(String value, String secret_key) throws EncryptException {
		if (Strings.isEmpty(value)) {
			return value;
		}
		value = value.trim();
		if (value.length() % 16 == 0) {
			try {
				String decValue = new String(decrypt(HEX.hexString2Bytes(value), secret_key), "utf-8");
				if (!Strings.isEmpty(decValue)) {
					logger.info("EncAndDecUtil#setDataEnc 原数据可以被解密,返回原值,原值:" + value + ",解密后:" + decValue);
					return value;
				}
			} catch (Exception e2) {

			}
		}
		try {
			return HEX.bytes2HexString(encrypt(value.getBytes("utf-8"), secret_key));
		} catch (Exception e) {
			logger.info("EncAndDecUtil#setDataEnc 加密发生异常:" + e + ",输入值为:" + value);
			if (e instanceof EncryptException ) {
				throw (EncryptException)e;
			}
			throw new EncryptException("encrypt error", e);
		}
	}

	/**
	 * 解密数据
	 * 
	 * @param encValue
	 * @return
	 */
	public static String decrypt(String encValue, String secret_key) throws EncryptException {
		try {
			if (Strings.isEmpty(encValue)) {
				return encValue;
			}
			encValue = encValue.trim();
			return new String(decrypt(HEX.hexString2Bytes(encValue), secret_key), "utf-8");
		} catch (Exception e) {
			logger.error("EncAndDecUtil#setDataDec 解密发生异常:" + e.toString() + ",输入值为:" + encValue);
			if (e instanceof EncryptException ) {
				throw (EncryptException)e;
			}
			throw new EncryptException("decrypt error", e);
		}
	}

	public static byte[] encrypt(byte[] data, String secret_key) throws EncryptException {

		try {
			if (Strings.isEmpty(secret_key)) {
                throw new EncryptException("Please configure the ec.project.db.eccreat.aeskey env param.");
            }
			AesEcb aes = new AesEcb(secret_key.getBytes("utf-8"));
			return aes.encrypt(data);
		} catch (Exception e) {
			if (e instanceof EncryptException ) {
				throw (EncryptException)e;
			}
			throw new EncryptException("encrypt error", e);
		}
	}

	public static byte[] decrypt(byte[] content, String secret_key) throws EncryptException {

		try {
			if (Strings.isEmpty(secret_key)) {
                throw new EncryptException("Please configure the ec.project.db.eccreat.aeskey env param.");
            }
			AesEcb aes = new AesEcb(secret_key.getBytes("utf-8"));
			return aes.decrypt(content);
		} catch (Exception e) {
			if (e instanceof EncryptException ) {
				throw (EncryptException)e;
			}
			throw new EncryptException("decrypt error", e);
		}
	}

}

3、Base64
3.1、Base64Encoder

import java.io.BufferedInputStream;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

public class Base64Encoder extends FilterOutputStream
{
  private static final char[] chars = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
  private int charCount;
  private int carryOver;

  public Base64Encoder(OutputStream out)
  {
    super(out);
  }

  public void write(int b)
    throws IOException
  {
    if (b < 0) {
      b += 256;
    }

    if (this.charCount % 3 == 0) {
      int lookup = b >> 2;
      this.carryOver = (b & 0x3);
      this.out.write(chars[lookup]);
    }
    else if (this.charCount % 3 == 1) {
      int lookup = (this.carryOver << 4) + (b >> 4) & 0x3F;
      this.carryOver = (b & 0xF);
      this.out.write(chars[lookup]);
    }
    else if (this.charCount % 3 == 2) {
      int lookup = (this.carryOver << 2) + (b >> 6) & 0x3F;
      this.out.write(chars[lookup]);
      lookup = b & 0x3F;
      this.out.write(chars[lookup]);
      this.carryOver = 0;
    }
    this.charCount += 1;

    if (this.charCount % 57 == 0)
      this.out.write(10);
  }

  public void write(byte[] buf, int off, int len)
    throws IOException
  {
    for (int i = 0; i < len; i++)
      write(buf[(off + i)]);
  }

  public void close()
    throws IOException
  {
    if (this.charCount % 3 == 1) {
      int lookup = this.carryOver << 4 & 0x3F;
      this.out.write(chars[lookup]);
      this.out.write(61);
      this.out.write(61);
    }
    else if (this.charCount % 3 == 2) {
      int lookup = this.carryOver << 2 & 0x3F;
      this.out.write(chars[lookup]);
      this.out.write(61);
    }
    super.close();
  }

  public static String encode(String unencoded)
  {
    byte[] bytes = null;
    try {
      bytes = unencoded.getBytes("8859_1");
    } catch (UnsupportedEncodingException ignored) {
    }
    return encode(bytes);
  }

  public static String encode(byte[] bytes)
  {
    ByteArrayOutputStream out = new ByteArrayOutputStream((int)(bytes.length * 1.37D));

    Base64Encoder encodedOut = new Base64Encoder(out);
    try
    {
      encodedOut.write(bytes);
      encodedOut.close();

      return out.toString("8859_1"); } catch (IOException ignored) {
    }
    return null;
  }

  public static void main(String[] args) throws Exception {
    if (args.length != 1) {
      System.err.println("Usage: java com.oreilly.servlet.Base64Encoder fileToEncode");

      return;
    }

    Base64Encoder encoder = null;
    BufferedInputStream in = null;
    try {
      encoder = new Base64Encoder(System.out);
      in = new BufferedInputStream(new FileInputStream(args[0]));

      byte[] buf = new byte[4096];
      int bytesRead;
      while ((bytesRead = in.read(buf)) != -1)
        encoder.write(buf, 0, bytesRead);
    }
    finally
    {
      if (in != null) in.close();
      if (encoder != null) encoder.close();
    }
  }
}

3.2、Base64Decoder

public class Base64Decoder extends FilterInputStream
{
  private static final char[] chars = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };

  private static final int[] ints = new int['聙'];
  private int charCount;
  private int carryOver;

  public Base64Decoder(InputStream in)
  {
    super(in);
  }

  public int read()
    throws IOException
  {
	  int x;
    do
    {
      x = this.in.read();
      if (x == -1)
        return -1;
    }
    while (Character.isWhitespace((char)x));
    this.charCount += 1;

    if (x == 61) {
      return -1;
    }

     x = ints[x];

    int mode = (this.charCount - 1) % 4;

    if (mode == 0) {
      this.carryOver = (x & 0x3F);
      return read();
    }

    if (mode == 1) {
      int decoded = (this.carryOver << 2) + (x >> 4) & 0xFF;
      this.carryOver = (x & 0xF);
      return decoded;
    }

    if (mode == 2) {
      int decoded = (this.carryOver << 4) + (x >> 2) & 0xFF;
      this.carryOver = (x & 0x3);
      return decoded;
    }

    if (mode == 3) {
      int decoded = (this.carryOver << 6) + x & 0xFF;
      return decoded;
    }
    return -1;
  }

  public int read(byte[] buf, int off, int len)
    throws IOException
  {
	  int i;
	  
    if (buf.length < len + off - 1) {
      throw new IOException("The input buffer is too small: " + len + " bytes requested starting at offset " + off + " while the buffer " + " is only " + buf.length + " bytes long.");
    }

    for ( i = 0; i < len; i++) {
      int x = read();
      if ((x == -1) && (i == 0)) {
        return -1;
      }
      if (x == -1) {
        break;
      }
      buf[(off + i)] = (byte)x;
    }
    return i;
  }

  public static String decode(String encoded)
  {
    return new String(decodeToBytes(encoded));
  }

  public static byte[] decodeToBytes(String encoded)
  {
    byte[] bytes = null;
    try {
      bytes = encoded.getBytes("8859_1");
    }
    catch (UnsupportedEncodingException ignored) {
    }
    Base64Decoder in = new Base64Decoder(new ByteArrayInputStream(bytes));

    ByteArrayOutputStream out = new ByteArrayOutputStream((int)(bytes.length * 0.67D));
    try
    {
      byte[] buf = new byte[4096];
      int bytesRead;
      while ((bytesRead = in.read(buf)) != -1) {
        out.write(buf, 0, bytesRead);
      }
      out.close();

      return out.toByteArray(); } catch (IOException ignored) {
    }
    return null;
  }

  public static void main(String[] args) throws Exception {
    if (args.length != 1) {
      System.err.println("Usage: java Base64Decoder fileToDecode");
      return;
    }

    Base64Decoder decoder = null;
    try {
      decoder = new Base64Decoder(new BufferedInputStream(new FileInputStream(args[0])));

      byte[] buf = new byte[4096];
      int bytesRead;
      while ((bytesRead = decoder.read(buf)) != -1)
        System.out.write(buf, 0, bytesRead);
    }
    finally
    {
      if (decoder != null) decoder.close();
    }
  }

  static
  {
    for (int i = 0; i < 64; i++)
      ints[chars[i]] = i;
  }
}

3.3、Base64Tool

public class Base64Tool {
	public static String encode(String strSrc,String encode){
		String strOut="";
		try {
			strOut=new String(Base64Encoder.encode(strSrc.getBytes(encode)));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return strOut;
	}
	public static String encode(byte[] bytes){
		String strOut="";
		try {
			strOut=new String(Base64Encoder.encode(bytes));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return strOut;
	}
	public static String decode(String strSrc,String encode){
		String strOut="";
		try {
			 strOut = new String(Base64Decoder.decodeToBytes(strSrc),encode);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return strOut;
	}
	public static byte[] decode(String strSrc){
		byte[] bytes;
		bytes = Base64Decoder.decodeToBytes(strSrc);
		return bytes;
	}

}

测试代码

public static void main(String[] args) {
        //RSA测试
        try {
            RSAUtil rsa = new RSAUtil();
            String str = "yanzi12256";
            RSAPublicKey pubKey = rsa.getRSAPublicKey();
            RSAPrivateKey priKey = rsa.getRSAPrivateKey();
            byte[] enRsaBytes = rsa.encrypt(pubKey,str.getBytes());
            String enRsaStr = new String(enRsaBytes, "UTF-8");
            System.out.println("加密后==" + enRsaStr);
            System.out.println("解密后==" + new String(rsa.decrypt(priKey, rsa.encrypt(pubKey,str.getBytes()))));
        } catch (Exception e) {
            e.printStackTrace();
        }

        //Base64测试
        Map<String, String> param = new HashMap<>();
        param.put("grpContno", "123");
        param.put("grpName", "个意");
        String jsonStr = JSONObject.toJSONString(param);
        String str = Base64Tool.encode(jsonStr, "UTF-8");//加密
        String ret = Base64Tool.decode(str, "UTF-8");//解密
        System.out.println(str);
        System.out.println(ret);

        //AES测试,密钥:test123
        System.out.println("--------------------------------------");
        String str1 = AesUtil.encrypt(jsonStr, "test123");//加密
        String str2 = AesUtil.decrypt(str1, "test123");//解密
        System.out.println(str1);
        System.out.println(str2);

    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
双向 RSA + AES 加密是一种常见的加密方式,其中使用 RSA 算法加密 AES 密钥,然后使用 AES 算法加密数据。在 C# 中,可以使用 `RSACryptoServiceProvider` 类和 `AesCryptoServiceProvider` 类来实现此加密方式。以下是一个简单的示例: ```csharp using System; using System.IO; using System.Security.Cryptography; using System.Text; class Program { static void Main(string[] args) { string plainText = "Hello, world!"; byte[] encryptedData = Encrypt(plainText); string decryptedText = Decrypt(encryptedData); Console.WriteLine("Original text: {0}", plainText); Console.WriteLine("Encrypted data: {0}", Convert.ToBase64String(encryptedData)); Console.WriteLine("Decrypted text: {0}", decryptedText); } static byte[] Encrypt(string plainText) { byte[] aesKey = GenerateAesKey(); using (var rsa = new RSACryptoServiceProvider()) { rsa.PersistKeyInCsp = false; byte[] encryptedAesKey = rsa.Encrypt(aesKey, true); // 使用 RSA 加密 AES 密钥 using (var aes = new AesCryptoServiceProvider()) { aes.Key = aesKey; aes.GenerateIV(); using (var memoryStream = new MemoryStream()) { memoryStream.Write(aes.IV, 0, aes.IV.Length); using (var cryptoStream = new CryptoStream(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write)) { byte[] plainData = Encoding.UTF8.GetBytes(plainText); cryptoStream.Write(plainData, 0, plainData.Length); cryptoStream.FlushFinalBlock(); } byte[] encryptedData = memoryStream.ToArray(); byte[] result = new byte[encryptedAesKey.Length + encryptedData.Length]; Buffer.BlockCopy(encryptedAesKey, 0, result, 0, encryptedAesKey.Length); Buffer.BlockCopy(encryptedData, 0, result, encryptedAesKey.Length, encryptedData.Length); return result; } } } } static string Decrypt(byte[] encryptedData) { byte[] encryptedAesKey = new byte[128]; // RSA 加密 AES 密钥得到的密文长度为 128 字节 byte[] encryptedDataOnly = new byte[encryptedData.Length - encryptedAesKey.Length]; Buffer.BlockCopy(encryptedData, 0, encryptedAesKey, 0, encryptedAesKey.Length); Buffer.BlockCopy(encryptedData, encryptedAesKey.Length, encryptedDataOnly, 0, encryptedDataOnly.Length); using (var rsa = new RSACryptoServiceProvider()) { rsa.PersistKeyInCsp = false; byte[] aesKey = rsa.Decrypt(encryptedAesKey, true); // 使用 RSA 解密 AES 密钥 using (var aes = new AesCryptoServiceProvider()) { aes.Key = aesKey; aes.IV = encryptedDataOnly.Take(aes.IV.Length).ToArray(); using (var memoryStream = new MemoryStream()) { using (var cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write)) { cryptoStream.Write(encryptedDataOnly, aes.IV.Length, encryptedDataOnly.Length - aes.IV.Length); cryptoStream.FlushFinalBlock(); } byte[] decryptedData = memoryStream.ToArray(); return Encoding.UTF8.GetString(decryptedData); } } } } static byte[] GenerateAesKey() { using (var aes = new AesCryptoServiceProvider()) { aes.GenerateKey(); return aes.Key; } } } ``` 上面的代码中,首先调用 `GenerateAesKey` 方法生成 AES 密钥,然后使用 RSA 算法加密 AES 密钥。加密时,先将 AES 密钥使用 RSA 加密,然后使用 AES 算法加密数据。具体来说,将 AES 密钥和 IV 都写入 `MemoryStream` 对象中,然后使用 `CryptoStream` 对象将数据写入 `MemoryStream` 对象中。最后将密文和 RSA 加密AES 密钥一起返回。 解密时,先从密文中取出 RSA 加密AES 密钥,然后使用 RSA 算法解密 AES 密钥。解密时,先从密文中取出 AES 的 IV 值,然后使用 `CryptoStream` 对象将数据解密。最后将解密后的文本返回。 注意,上面的示例仅用于演示 RSA + AES 加密的基本原理,实际使用中还需要考虑安全性等因素。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值