密码学算法——DES密码算法 c++实现

DES密码算法

1.算法原理

DES 设计中使用了分组密码设计的两个原则:混淆(confusion)和扩散(diffusion),其目的是抗击敌手对密码系统的统计分析。
混淆是使密文的统计特性与密钥的取值之间的关系尽可能复杂化,以使密钥和明文以及密文之间的依赖性对密码分析者来说是无法利用的。
扩散的作用就是将每一位明文的影响尽可能迅速地作用到较多的输出密文位中,以便在大量的密文中消除明文的统计结构,并且使每一位密钥的影响尽可能迅速地扩展到较多的密文位中, 以防对密钥进行逐段破译。

DES 密码的加解密算法基本流程如下图所示。
在这里插入图片描述

加密算法常见的有ECB模式和CBC模式:

  • ECB模式中,将数据按照8个字节一段进行DES加密或解密得到一段段的8个字节的密文或者明文,最后一段不足8个字节(一般补0或者F),按照需求补足8个字节进行计算(并行计算),之后按照顺序将计算所得的数据连在一起即可,各段数据之间互不影响。
  • CBC模式中,实现机制使加密的各段数据之间有了联系。其实现的机理如下:
  1. 首先将数据按照8个字节一组进行分组得到D1D2…Dn(若数据不是8的整数倍,用指定的PADDING数据补位,本实验中补0)
  2. 第一组数据D1与初始化向量Ⅳ异或后的结果进行DES加密得到第一组密文C
  3. 第二组数据D2与第一组的加密结果C1异或以后的结果进行DES加密,得到第二组密文C2
  4. 之后的数据以此类推,得到Cn
  5. 按顺序连为C1C2C3…Cn即为加密结果

比较CBC与ECB模式区别,如下图所示:
在这里插入图片描述

2.结果展示

在这里插入图片描述

本次算法没有使用MFC库,而是用Easy_X库手动画了一个,所以显得比较简陋,至于为什么呢,是因为我在汉字字符转换编码时用的GBK编码,做不了utf编码转换,而且我是用CLion写的,复制到VS时有些内容不兼容,所以干脆就用了Easy_X画了个界面出来。但作为DES算法核心的内容,应该也是通用的。

3.具体过程

本文主要是关于DES算法的具体实现过程,如果想要学习DES算法原理,推荐阅读DES加密算法这篇文章。

3.1核心代码DESCipher类

DESCipher.h

#include<iostream>
#include<sstream>
#include<cstring>
#include<iomanip>
#include<cmath>
#include<ctime>
#include<list>
#include<windows.h>
using namespace std;
class DESCipher{
private:
    string keys;//八字节密钥
    string keywords;//64位密钥
    string plains;//明文
    string plain_origin;//明文二进制
    string cipher;//密文
    string cipher_origin;//密文二进制
    string hex=("0123456789ABCDEF");
    string IV=("suixiang");
    string IV_origin;//初始化向量二进制
    char ckey[16][48];
    int IP[64] = {        //IP置换表
            58, 50, 42, 34, 26, 18, 10, 2,
            60, 52, 44, 36, 28, 20, 12, 4,
            62, 54, 46, 38, 30, 22, 14, 6,
            64, 56, 48, 40, 32, 24, 16, 8,
            57, 49, 41, 33, 25, 17, 9, 1,
            59, 51, 43, 35, 27, 19, 11, 3,
            61, 53, 45, 37, 29, 21, 13, 5,
            63, 55, 47, 39, 31, 23, 15, 7
    };
    int NIP[64] = {     //IP逆置换表
            40,  8, 48, 16, 56, 24, 64, 32,
            39,  7, 47, 15, 55, 23, 63, 31,
            38,  6, 46, 14, 54, 22, 62, 30,
            37,  5, 45, 13, 53, 21, 61, 29,
            36,  4, 44, 12, 52, 20, 60, 28,
            35,  3, 43, 11, 51, 19, 59, 27,
            34,  2, 42, 10, 50, 18, 58, 26,
            33,  1, 41,  9, 49, 17, 57, 25
    };
    int E[48] = {            //扩展换位表
            32, 1, 2, 3, 4, 5,
            4, 5, 6, 7, 8, 9,
            8, 9, 10, 11, 12, 13,
            12, 13, 14, 15, 16, 17,
            16, 17, 18, 19, 20, 21,
            20, 21, 22, 23, 24, 25,
            24, 25, 26, 27, 28, 29,
            28, 29, 30, 31, 32, 1
    };
    int PC1[56] = {           //PC1换位表(64—>56)
            57, 49, 41, 33, 25, 17, 9,
            1, 58, 50, 42, 34, 26, 18,
            10, 2, 59, 51, 43, 35, 27,
            19, 11, 3, 60, 52, 44, 36,
            63, 55, 47, 39, 31, 23, 15,
            7, 62, 54, 46, 38, 30, 22,
            14, 6, 61, 53, 45, 37, 29,
            21, 13, 5, 28, 20, 12, 4
    };
    int move[16] = {          //循环移位表
            1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1
    };
    int PC2[48] = {           //PC2换位表(56—>48)
            14, 17, 11, 24, 1, 5,
            3, 28, 15, 6, 21, 10,
            23, 19, 12, 4, 26, 8,
            16, 7, 27, 20, 13, 2,
            41, 52, 31, 37, 47, 55,
            30, 40, 51, 45, 33, 48,
            44, 49, 39, 56, 34, 53,
            46, 42, 50, 36, 29, 32
    };
    int S[8][4][16] = {
            {		{14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7},
                    {0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8},
                    {4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0},
                    {15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13}
            },//S1
            {		{15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10},
                    {3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5},
                    {0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15},
                    {13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9}
            },//S2
            {		{10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8},
                    {13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1},
                    {13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7},
                    {1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12}
            },//S3
            {		{7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15},
                    {13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9},
                    {10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4},
                    {3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14}
            },//S4
            {		{2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9},
                    {14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6},
                    {4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14},
                    {11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3}
            },//S5
            {		{12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11},
                    {10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8},
                    {9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6},
                    {4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13}
            },//S6
            {		{4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1},
                    {13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6},
                    {1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2},
                    {6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12}
            },//S7
            {       {13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7},
                    {1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2},
                    {7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8},
                    {2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11}
            } //S8
    };
    int P[32] = {                    //P换位表
            16,7,20,21,29,12,28,17,1, 15,23,26,5, 18,31,10,
            2,8,24,14,32,27,3, 9, 19,13,30,6, 22,11,4,25
    };
    char atobin[16][4]={
            {'0','0','0','0'},
            {'0','0','0','1'},
            {'0','0','1','0'},
            {'0','0','1','1'},
            {'0','1','0','0'},
            {'0','1','0','1'},
            {'0','1','1','0'},
            {'0','1','1','1'},
            {'1','0','0','0'},
            {'1','0','0','1'},
            {'1','0','1','0'},
            {'1','0','1','1'},
            {'1','1','0','0'},
            {'1','1','0','1'},
            {'1','1','1','0'},
            {'1','1','1','1'},
    };
public:
    void set_keywords(string s);//设置密钥
    void set_plain(string s);//设置明文
    void set_cipher(string s);//设置密文
    void set_four();//生成初始化向量Ⅳ的二进制
    void child_key();//生成子密钥
    string changetobin(string s);//将字符串转换为二进制
    string IP_trans(string s);//初始置换IP
    string NIP_trans(string s);//初始逆置换IP-1
    string r0_to_r1(int cnt,string string1,string string2);//生成下一轮的R
    string CBC(string s);//cbc异或操作
    void Encryption_ECB();//ECB模式加密
    void Decryption_ECB();//ECB模式解密
    void Encryption_CBC();//CBC模式加密
    void Decryption_CBC();//CBC模式解密
    string Encryption(string s,string t,int order);//加密
    string Decryption(string s,string t,int order);//解密
};

3.1.1 set_keywords

void DESCipher::set_keywords(std::string s) {
    keys=s;
    keywords = changetobin(s);//在加密计算中,使用的是二进制,所以把string转为二进制编码
}

3.1.2 set_plain

void DESCipher::set_plain(std::string s) {
    plains=s;
    plain_origin = changetobin(s);
}

3.1.3 set_cipher

void DESCipher::set_cipher(std::string s) {
    cipher=s;
    cipher_origin="";
    for(char i:s){
        if(i=='0') cipher_origin.append("0000");
        else if(i=='1') cipher_origin.append("0001");
        else if(i=='2') cipher_origin.append("0010");
        else if(i=='3') cipher_origin.append("0011");
        else if(i=='4') cipher_origin.append("0100");
        else if(i=='5') cipher_origin.append("0101");
        else if(i=='6') cipher_origin.append("0110");
        else if(i=='7') cipher_origin.append("0111");
        else if(i=='8') cipher_origin.append("1000");
        else if(i=='9') cipher_origin.append("1001");
        else if(i=='A') cipher_origin.append("1010");
        else if(i=='B') cipher_origin.append("1011");
        else if(i=='C') cipher_origin.append("1100");
        else if(i=='D') cipher_origin.append("1101");
        else if(i=='E') cipher_origin.append("1110");
        else if(i=='F') cipher_origin.append("1111");
    }
}

3.1.4 set_four()

void DESCipher::set_four() {
    IV_origin= changetobin(IV);//用于CBC模式加密的偏移量
}

3.1.5 child_key()

子密钥生成过程
子密钥生成图

void DESCipher::child_key() {
    string temp;
    int cnt=0;
    for(int i:PC1){
        temp.push_back(keywords[i-1]);
    }//置换选择1
    while(cnt<16){
        string l0(temp,move[cnt],28-move[cnt]);
        l0.append(temp.substr(0,move[cnt]));//前28位密钥循环左移
        string r0(temp,28+move[cnt],28-move[cnt]);
        r0.append(temp.substr(28,move[cnt]));//后28位密钥循环左移
        temp.replace(0,28,l0);
        temp.replace(28,28,r0);//把循环后的密钥作为新的密钥
        for(int i=0;i<48;i++){
            ckey[cnt][i]=temp[PC2[i]-1];//置换选择2
        }
        cnt++;
    }
}

对于子密钥生成的过程来说,就是一个模拟的过程,按着子密钥生成的方法,一步一步变化得到最后的子密钥矩阵就行。

3.1.6 changetobin

string DESCipher::changetobin(std::string s) {
    string temp;
    for(char i:s){
        int a=(i>>4)&0xf;//高四位
        int b=i&0xf;//第四位
        //这里是GBK编码的转换规则,具体关于编码的问题可以去学习一下,我也不是很懂
        temp.push_back(atobin[a][0]);
        temp.push_back(atobin[a][1]);
        temp.push_back(atobin[a][2]);
        temp.push_back(atobin[a][3]);
        temp.push_back(atobin[b][0]);
        temp.push_back(atobin[b][1]);
        temp.push_back(atobin[b][2]);
        temp.push_back(atobin[b][3]);
    }
    return temp;
}

3.1.7 IP_trans

string DESCipher::IP_trans(std::string s) {
    string temp;
    for(int i:IP){
        temp.push_back(s[i-1]);
    }
    return temp;
}

3.1.8 NIP_trans

string DESCipher::NIP_trans(std::string s) {
    string temp;
    for(int i:NIP){
        temp.push_back(s[i-1]);
    }
    return temp;
}

3.1.9 r0_to_r1 轮加密

在这里插入图片描述

string DESCipher::r0_to_r1(int cnt,std::string string1, std::string string2) {
    string temp;
    for(int i=0;i<48;i++){
        temp.push_back(string2[E[i]-1]);//扩充、置换,E为扩充置换表
        temp.replace(i,1, to_string((int(temp[i])-48)^(int(ckey[cnt][i])-48)));//与对应轮子密钥异或
    }
    for(int i=0;i<8;i++){
        int line=(temp[i*6]-'0')*2+(temp[i*6+5]-'0');
        int list=(temp[i*6+1]-'0')*8+(temp[i*6+2]-'0')*4+(temp[i*6+3]-'0')*2+(temp[i*6+4]-'0');
        int re=S[i][line][list];//S盒操作
        temp.push_back(atobin[re][0]);
        temp.push_back(atobin[re][1]);
        temp.push_back(atobin[re][2]);
        temp.push_back(atobin[re][3]);
    }
    temp.erase(0,48);//前48位为未经S盒的二进制串,后32位为代换、选择后的二进制串,删去前48位
    for(int i:P){
        temp.push_back(temp[i-1]);//置换操作
    }
    temp.erase(0,32);//前32位为未经置换的二进制串,后32位为置换后结果,删去前32位
    for(int i=0;i<temp.length();i++){
        temp.replace(i,1, to_string((int(temp[i])-48)^(int(string1[i])-48)));//R0与L0异或
    }
    return temp;//返回的temp则为R1
}

本函数的作用是由上一轮的L0与R0经过变换生成下一轮的R1。

3.1.10 CBC

string DESCipher::CBC(std::string s) {
    string temp;
    for(int i=0;i<s.length();i++){
        temp.append(to_string((int(s[i])-48)^(int(IV_origin[i])-48)));
    }
    return temp;
}

本函数为图中的XOR过程,其中s为当前明文分组,IV_origin为前一个密文分组,temp为异或后结果。

3.1.11 Encryption_ECB()

void DESCipher::Encryption_ECB(){
    string temp;
    temp=plain_origin;
    int num=64-int(temp.length()%64);
    string str1(num,'0');
    if(num!=64)   temp.append(str1);//最后一组不足64位,以0补足
    for(int i=0;i<temp.length()/64;i++){
        temp.replace(i*64,64, IP_trans(temp.substr(i*64,64)));//IP置换
        string l0(temp.substr(i*64,32)),r0(temp.substr(i*64+32,32));
        for(int j=0;j<16;j++){//对应轮密钥
            string mid;
            mid=r0;
            r0=r0_to_r1(j,l0,r0);//生成R1
            l0=mid;//交换,本轮的R0为下一轮L1
        }//16轮加密
        temp.replace(i*64,32,r0);//最后一轮不交换,即R0为前32位
        temp.replace(i*64+32,32,l0);//L0位后32位
        temp.replace(i*64,64, NIP_trans((temp.substr(i*64,64))));//IP逆置换
    	//一组64位加密完成
}
    cipher_origin=temp;//密文二进制
    string re;
    for(int i=0;i<temp.length()/4;i++){
       re.push_back(hex[((temp[i*4]-'0')*8)+((temp[i*4+1]-'0')*4)+((temp[i*4+2]-'0')*2)+((temp[i*4+3]-'0'))]);
    }//将二进制密文转为16进制字符串
    cipher=re;
}

3.1.12 Decryption_ECB()

void DESCipher::Decryption_ECB() {
    string temp;
    temp=cipher_origin;
    for(int i=0;i<temp.length()/64;i++){
        temp.replace(i*64,64, IP_trans(temp.substr(i*64,64)));//IP置换
        string l0(temp.substr(i*64,32)),r0(temp.substr(i*64+32,32));
        for(int j=15;j>=0;j--){//对应轮密钥
            string mid;
            mid=r0;
            r0=r0_to_r1(j,l0,r0);
            l0=mid;
        }
        temp.replace(i*64,32,r0);
        temp.replace(i*64+32,32,l0);//解密
        temp.replace(i*64,64, NIP_trans(temp.substr(i*64,64)));//IP-1置换
    }
    plain_origin=temp;
    string re;
    for(int i=0;i<temp.length()/8;i++){
        int a=((((temp[i*8]-'0')*8+(temp[i*8+1]-'0')*4+(temp[i*8+2]-'0')*2+(temp[i*8+3]-'0')))&0xf);
        int b=((((temp[i*8+4]-'0')*8+(temp[i*8+5]-'0')*4+(temp[i*8+6]-'0')*2+(temp[i*8+7]-'0')))&0xf);
        re.push_back((a<<4)+b);
    }//将二进制串转为字符串
    plains=re;
}

解密的过程就是加密的逆过程

3.3.13 Encryption_CBC()

void DESCipher::Encryption_CBC(){
    string temp;
    temp=plain_origin;
    set_four();//设置初始化向量Ⅳ二进制值
    int num=64-int(temp.length()%64);
    string str1(num,'0');
    if(num!=64)   temp.append(str1);//最后一组不足64位,以0补足
    for(int i=0;i<temp.length()/64;i++){
        string string1(temp.substr(i*64,64));
        string1 = CBC(string1);//CBC异或操作,当i=0时,IV_origin为IV的二进制值,当i!=0时,IV_origin为上一组加密结果
        temp.replace(i*64,64,string1);
        temp.replace(i*64,64, IP_trans(temp.substr(i*64,64)));//IP置换
        string l0(temp.substr(i*64,32)),r0(temp.substr(i*64+32,32));
        for(int j=0;j<16;j++){//对应轮密钥
            string mid;
            mid=r0;
            r0=r0_to_r1(j,l0,r0);//生成R1
            l0=mid;//交换,本轮的R0为下一轮L1
        }
        temp.replace(i*64,32,r0);//最后一轮不交换,即R0为前32位
        temp.replace(i*64+32,32,l0);//L0位后32位

        temp.replace(i*64,64, NIP_trans((temp.substr(i*64,64))));//IP逆置换
        IV_origin.replace(0,64,temp.substr(i*64,64));//更改IV_origin值
        //一组64位加密完成
    }
    cipher_origin=temp;//密文二进制
    string re;
    for(int i=0;i<temp.length()/4;i++){
        re.push_back(hex[((temp[i*4]-'0')*8)+((temp[i*4+1]-'0')*4)+((temp[i*4+2]-'0')*2)+((temp[i*4+3]-'0'))]);
    }//将二进制密文转为16进制字符串
    cipher=re;
}

3.1.14 Decryption_CBC()

void DESCipher::Decryption_CBC() {
    string temp;
    set_four();//设置初始化向量Ⅳ二进制值
    temp=cipher_origin;
    for(int i=0;i<temp.length()/64;i++){
        string keep(temp.substr(i*64,64));
        temp.replace(i*64,64, IP_trans(temp.substr(i*64,64)));
        string l0(temp.substr(i*64,32)),r0(temp.substr(i*64+32,32));
        for(int j=15;j>=0;j--){
            string mid;
            mid=r0;
            r0=r0_to_r1(j,l0,r0);
            l0=mid;
        }
        temp.replace(i*64,32,r0);
        temp.replace(i*64+32,32,l0);//解密
        temp.replace(i*64,64, NIP_trans(temp.substr(i*64,64)));
        string string1(temp.substr(i*64,64));
        string1 = CBC(string1);
        temp.replace(i*64,64,string1);
        IV_origin.replace(0,64,keep);
    }
    plain_origin=temp;
    string re;
    for(int i=0;i<temp.length()/8;i++){
        int a=((((temp[i*8]-'0')*8+(temp[i*8+1]-'0')*4+(temp[i*8+2]-'0')*2+(temp[i*8+3]-'0')))&0xf);
        int b=((((temp[i*8+4]-'0')*8+(temp[i*8+5]-'0')*4+(temp[i*8+6]-'0')*2+(temp[i*8+7]-'0')))&0xf);
        re.push_back((a<<4)+b);
    }
    plains=re;
}

3.1.15 加密函数

string DESCipher::Encryption(std::string s, std::string t, int order) {
    if(order==0){//ECB模式加密
        set_keywords(s);
        set_plain(t);
        child_key();
        Encryption_ECB();
    }
else if(order==1) {//CBC模式加密
        set_keywords(s);
        set_plain(t);
        child_key();
        Encryption_CBC();
    }
    else if(order==2){//3DES模式加密(基于ECB)
        set_plain(t);
        set_keywords(s.substr(0,8));
        child_key();
        Encryption_ECB();
        set_keywords(s.substr(8,8));
        child_key();
        Decryption_ECB();
        set_keywords(s.substr(16,8));
        child_key();
        Encryption_ECB();
    }
    return cipher;
}

3.1.16 解密函数

string DESCipher::Decryption(std::string s, std::string t, int order) {
    if(order==0){//ECB模式解密
        set_keywords(s);
        set_cipher(t);
        child_key();
        Decryption_ECB();
    }
    else if(order==1){//CBC模式解密
        set_keywords(s);
        set_cipher(t);
        child_key();
        Decryption_CBC();
    }
    else if(order==2){//3DES模式解密(基于ECB)
        set_cipher(t);
        set_keywords(s.substr(0,8));
        child_key();
        Decryption_ECB();
        set_keywords(s.substr(8,8));
        child_key();
        Encryption_ECB();
        set_keywords(s.substr(16,8));
        child_key();
        Decryption_ECB();
    }
    return plains;
}

3.2 其他函数

其他大部分函数都是关于图形界面的绘制过程,在此就不过多赘述了。只是简要介绍一下通过系统API打开文件的方式。

void get_from_choose(int x){
    OPENFILENAME ofn;      // 公共对话框结构。
    TCHAR szFile[MAX_PATH]; // 保存获取文件名称的缓冲区。// 初始化选择文件对话框。
    ZeroMemory(&ofn, sizeof(OPENFILENAME));
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = NULL;
    ofn.lpstrFile = szFile;
    ofn.lpstrFile[0] = '\0';
    ofn.nMaxFile = sizeof(szFile);
    ofn.lpstrFilter = (LPCSTR)"All(*.*)\0*.*\0Text(*.txt)\0*.TXT\0\0";
    ofn.nFilterIndex = 1;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
    string file_name,name_file;
    if ( GetOpenFileName(&ofn) ){
        cout<<szFile<<endl;
        for(char i:szFile){
            if(i!=0) {
                file_name.push_back(i);
                if(i=='\\') file_name.push_back('\\');
            }
            else break;
        }
    }
}

file_name中就是选择文件的绝对路径,得到路径后就能进行文件处理,需加上#include <windows.h>头文件

总结

DES算法作为比较经典的对称密钥算法,还是有深入学习的必要,而且学会DES后,对于国密算法SM4的学习也是有帮助的。

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值