DES加密算法以及实现

DES 加密算法以及实现

1. 中文版介绍

此处推荐看其他博主的文章

经典的DES算法详解_yasinzhang的博客-CSDN博客_des算法

(二)DES算法_E.D.Sec的博客-CSDN博客_des算法

2. 英文版介绍

自己根据英文材料简单概括的写了一下,若有出错的地方,还请各位斧正

DES, known as Data Encryption Standard , is designed to encipher and decipher the 64-bit blocks under the control of a 64-bit key. Generally, the encryption/decrytion process can be sparated into two chuncks. Subkeys generation and encipherment of data.

1.1 Subkeys generation


When the inputted key is firstly permuted (pc1), its length is shorted to 56 bits by removing the parity check bits. Then it is split into two parts C0 and D0 with equal length, where the left shift will be performed 16 times. Finally, a list of subkeys (K1 … Kn) can be obtainted.

1.2 Data encryption

在这里插入图片描述
Similarly, an initial permtation is performed on the 64-bit inputted data, which will be also separated into two part with equal length L0 nad R0. More importantly, the next part, basically a round function, is iterated 16 times, and within each iteration an XOR cumputation is performed. This XOR takes the parameters of the left part of last iteration and result of a funtion() . This particuar function can be illustrated as below:
在这里插入图片描述
At first, this function is permutated by E table, which can extend the inputted R(i-1) to 48 bits. Then another XOR computation is implemented with another parameter K(i). Finally, every block of 6 bits B(1-8) is taken by S(1-8) to get a result of 4 bits. The S operation is actually finding the value at a specified position on a given table with the inputted 6-bit data. The first and the last bits are concatenate to show a decimal number within 0 to 3, while the middle four bits can indicate a decimal number within 0 to 15. The former is the index of row and the latter is the index of column in the table. After 16 iterations, the final result is got by R16 at left side and L16 at right side concatenatd, and performed an inverse initial permutation.

3. 算法实践

代码整体结构(文章结尾会附上全部代码)
请添加图片描述
对于DES算法的实现,本次实现考虑了基本的面向对象思想,其中一个名为DES的类负责逻辑处理,而UI类负责处理UI组件并控制I/O。此外,DES类中的数据和键大多是一个整数列表,如[1,0,…1, 0]。这样易于控制和计算。而字符串和十六进制、十六进制和二进制之间的转换,以及随机密钥生成都在主UI类中处理。算法中需要用到多个置换矩阵,这些矩阵可以从一个permutation.txt中读取,它大概长这样请添加图片描述
算法实现的基本思想为: 如果加密前和解密后两个比特序列相同,则整个加密和解密过程可以被证明是正确的
请添加图片描述
此外,如果输入的明文转成二进制不够64位的话需要填0补充到64位,如果超过64位则截成多个64位的段然后分别进行DES加密(main.py中实现)

3.1 读取置换矩阵

    def __readTables(self):
        # read contents
        with open("permutation.txt", 'r') as f:
            raw = f.read().split('--\n')
            for form in raw:
                # loop each permutation
                results = []
                lines = form.split("\n")
                pattern_name = lines[0]
                for content in lines[1:]:
                    # loop each line in each permuatation
                    lst = content.split(" ")
                    pure_lst = [int(x) for x in lst if x.strip() != '']
                    # turn into Integer
                    if len(pure_lst) != 0:
                        results.append(pure_lst)
                self.patterns[pattern_name] = results
        return None

将permutation.txt中的内容读取到一个字典中(self.patterns)

3.2 置换

```python
    def __permutation(self, data, pattern):
        # pattern is a matrix
        new = []
        for i in range(len(pattern)):
            for j in range(len(pattern[i])):
                position = pattern[i][j]
                new.append(int(data[position - 1]))
        return new

这是至关重要的一步,循环遍历pattern(矩阵),利用pattern提供的位置转换数据。一些操作如置换选择1和2、初始置换、e-bit selection等都应采用该方法来达到目的

3.3 子密钥生成以及循环左移

    def __subKeyGeneration(self):
        # generate subKeys
        pc1 = self.patterns.get('pc1')
        pc2 = self.patterns.get('pc2')
        shift = self.patterns.get('shift')
        self.currentKey = self.__permutation(self.currentKey, pc1)
        self.subKeys = self.__leftShiftAndPermutation(self.currentKey, shift, pc2)
        return None

    def __leftShiftAndPermutation(self, data, pattern, pc2):
        # pattern, pc2 is a matrix
        new = []
        length = int(len(data) / 2)
        # split into two parts
        c = data[0:length]
        d = data[length:]
        for i in pattern:
            # transformation
            c_front = c[0:i[1]]
            c_back = c[i[1]:]
            c = c_back + c_front
            d_front = d[0:i[1]]
            d_back = d[i[1]:]
            d = d_back + d_front
            # call permutation functions
            key = self.__permutation(c + d, pc2)
            new.append(key)
        return new  

此方法比较简单,只是取pc1,根据移位矩阵进行左移位,最后再进行pc2得到结果

3.4 加密和解密

    def encrypt(self):
        # encryption
        if not self.__encrypted:
            self.__subKeyGeneration()

            # initial permutation (IP)
            ip = self.patterns.get('ip')
            new_txt = self.__permutation(self.currentTxt, ip)

            # left and right
            length = int(len(new_txt) / 2)
            l = new_txt[0:length]
            r = new_txt[length:]

            # the very important step (16 rounds)
            for key in self.subKeys:
                tempr = r
                r = self.__xorComputation(l, self.__funtionF(key, tempr))
                l = tempr
            result = r + l

            # the last ip-1 permutation
            result = self.__permutation(result, self.patterns.get('ip-1'))
            return result
        else:
            return None

    def decrypt(self):
         # decryption
        if self.__encrypted:

            # very similar to encrypt
            self.__subKeyGeneration()
            ip = self.patterns.get('ip')
            new_txt = self.__permutation(self.currentTxt, ip)
            length = int(len(new_txt) / 2)
            l = new_txt[0:length]
            r = new_txt[length:]

            # notice the loop order of subKeys should be reversed
            for key in self.subKeys[::-1]:
                tempr = r
                r = self.__xorComputation(l, self.__funtionF(key, tempr))
                l = tempr
            result = r + l
            result = self.__permutation(result, self.patterns.get('ip-1'))
            return result  
        else:
            return None

通过对上述几个函数的解释,可以清楚地了解加密步骤。它生成sukeys,进行初始置换,拆分为Left和Right,使用XOR和函数F迭代16轮,最终进行ip-1置换。加密和解密之间唯一的区别是子密钥的循环顺序是颠倒的(for key in subkeys / subkeys[::-1])。

4. 示例

请添加图片描述

  1. 首先点击Generate Key按钮来获取一个随机的Key。

  2. 在左侧输入框中输入任意长度的文本。

  3. 记录密文或直接将此密文放入右侧输入框

  4. 将解密后的文本与原始输入的文本进行比较

5. 代码

main.py

import random
from DES import DES
import tkinter as tk
from tkinter import RIDGE, DISABLED, FLAT, NORMAL

def encode(s):
    # turn string into binary
    return ''.join([bin(ord(i)).replace('0b', '') for i in s])

def randomKey():
    # generate a random key
    str_hex = ''
    str_bin = ''
    for i in range(16):
        num = random.randint(0, 15)
        str_hex += str(hex(num)).replace('0x', '')
        binary = bin(num)
        if (len(binary) - 2) == 4:
            str_bin += str(binary).replace('0b', '')
        else:
            num = 6 - len(binary)
            str_bin += str(binary).replace('0b', num * '0')
    return [str_hex, str_bin]

class UI:
    # the content of the text input
    def __init__(self):
        # window and instances
        self.currentText = None
        self.currentCipher = None
        self.currentKey = None
        self.textList = []
        self.cipherList = []
        self.results = {}
        self.root = tk.Tk()
        self.root.title('DES Demonstrator')
        self.root.geometry("900x450+350+80")

        # text and cipher
        self.entry_txt_var = tk.StringVar()
        self.entry_cipher_var = tk.StringVar()
        self.entry_txt = tk.Entry(self.root, textvariable=self.entry_txt_var)
        self.entry_cipher = tk.Entry(self.root, textvariable=self.entry_cipher_var)
        self.text_txt = tk.Text(self.root, relief=FLAT)
        self.text_cipher = tk.Text(self.root, relief=FLAT)
        self.label_txt_input = tk.Label(self.root, text="Enter text: ")
        self.label_cipher_input = tk.Label(self.root, text="Enter cipher: ")
        self.label_txt_output = tk.Label(self.root, text="Get cipher: ")
        self.label_cipher_output = tk.Label(self.root, text="Get text: ")
        self.label_text_notification = tk.Label(self.root, text="Please input yor text")
        self.label_cipher_notification = tk.Label(self.root, text="Please input yor cipher")
        self.btn_txt = tk.Button(self.root, text="Encrypt", relief=RIDGE, command=self.txt_event)
        self.btn_cipher = tk.Button(self.root, text="Decrypt", relief=RIDGE, command=self.cipher_event)

        # key
        self.label_key = tk.Label(self.root, text="Key: ")
        self.label_hex = tk.Label(self.root, text="(Hexadecimal)")
        self.label_bin = tk.Label(self.root, text="(Binary)")
        self.text_key_1 = tk.Text(self.root, relief=FLAT)
        self.text_key_2 = tk.Text(self.root, relief=FLAT)
        self.btn_key = tk.Button(self.root, text="Generate Key", relief=RIDGE, command=self.key_event)

        # layout
        self.label_key.place(x=80, y=305)
        self.btn_key.place(x=680, y=285, height=70, width=100)
        self.text_key_1.place(x=140, y=280, height=30, width=400)
        self.text_key_2.place(x=140, y=330, height=30, width=400)
        self.label_text_notification.place(x=190, y=15)
        self.label_cipher_notification.place(x=625, y=15)
        self.label_hex.place(x=390, y=280, height=30, width=400)
        self.label_bin.place(x=390, y=330, height=30, width=400)
        self.label_txt_input.place(x=80, y=55)
        self.label_cipher_input.place(x=515, y=55)
        self.label_txt_output.place(x=25, y=185)
        self.label_cipher_output.place(x=490, y=185)
        self.btn_txt.place(x=200, y=100, height=50, width=100)
        self.btn_cipher.place(x=650, y=100, height=50, width=100)
        self.entry_txt.place(x=150, y=50, height=30, width=200)
        self.entry_cipher.place(x=600, y=50, height=30, width=200)
        self.text_txt.place(x=100, y=180, height=30, width=300)
        self.text_cipher.place(x=550, y=180, height=30, width=300)
        self.root.resizable(False, False)
        self.text_txt.config(state=DISABLED)
        self.text_cipher.config(state=DISABLED)
        self.text_key_1.config(state=DISABLED)
        self.text_key_2.config(state=DISABLED)

    def txt_event(self):
        tempText = self.entry_txt_var.get()
        if len(tempText) == 0:
            # not inputted text
            self.label_text_notification.config(text="Absent content", fg="red")
        else:
            if self.currentKey != None:
                # when the encryption can be run successfully
                self.label_text_notification.config(text="Please input yor text", fg="black")
                self.currentText = tempText
                # every time the buttion is clicked, an encryption will be performed
                self.encryption()
            else:
                # no key
                self.label_text_notification.config(text="Generate key first", fg="red")

    def cipher_event(self):
        tempCipher = self.entry_cipher_var.get()
        if len(tempCipher) == 0:
             # not inputted text
            self.label_cipher_notification.config(text="Absent content", fg="red")
        else:
            if self.currentKey != None:
                # when the decryption can be run successfully
                self.label_cipher_notification.config(text="Please input yor cipher", fg="black")
                self.currentCipher = tempCipher
                # every time the buttion is clicked, an decryption will be performed
                self.decryption()
            else:
                # no key
                self.label_cipher_notification.config(text="Generate key first", fg="red")

    def key_event(self):
        # get a random key
        key_random = randomKey()
        self.currentKey = key_random[1]
        self.changeContent(self.text_key_1, key_random[0])
        self.changeContent(self.text_key_2, key_random[1])

    def encryption(self):
        # turn into binary
        binary = encode(self.currentText)
        
        # call dataPreparation
        self.textList.clear()
        self.dataPreparation(binary)
        
        # results handling 
        index_binary = ''
        entire_cipher = []
        for i in self.textList:
            # create index
            index_binary += i
            tempDes = DES(i, self.currentKey, False)
            cipher = tempDes.encrypt()
            entire_cipher += cipher 

        # string result in binary
        result = ''.join([str(x) for x in entire_cipher])
        # string result in hexadecimal
        hex_result = ''
        for i in range(len(entire_cipher)):
            if i % 4 == 0:
                str_bin = ''.join([str(j) for j in entire_cipher[i : i+4]])
                hex_result += hex(int(str_bin, 2)).replace('0x', '')

        # display it on UI
        self.changeContent(self.text_txt, hex_result)
        # add to the results
        self.results[index_binary] =self.currentText

    def decryption(self):
        # turn hexadecimal into binary
        binary = ''
        for i in self.currentCipher:
            tempBin = bin(int(i, 16))
            if (len(tempBin) - 2) == 4:
                binary += str(tempBin).replace('0b', '')
            else:
                num = 6 - len(tempBin)
                binary += str(tempBin).replace('0b', num * '0')
        
        # trun into groups
        self.cipherList.clear()
        self.cipherPreparation(binary)
        
        # results handling 
        entire_text = []
        for i in self.cipherList:
            tempDes = DES(i, self.currentKey, True)
            text = tempDes.decrypt()
            entire_text += text
        
        # string result in binary
        result = ''.join([str(x) for x in entire_text])
        
        # get the original text 
        original_text = self.results.get(result)
        
        # error handling 
        if original_text == None:
            self.label_cipher_notification.config(text="Key has been changed", fg="red")
            original_text = ''
            
        # display it on UI
        self.changeContent(self.text_cipher, original_text)
        
    def changeContent(self, widget, content):
        # change content of a specified text field
        widget.config(state=NORMAL)
        widget.delete('0.0', 'end')
        widget.insert('0.0', content)
        widget.config(state=DISABLED)
    
    def dataPreparation(self, data):
        # split into a list of 64 bits when longer than 64 bits, 
        # or add up to 64 bits with '0' when shorter than 64 bits
        if len(data) <= 64:
            # end this recursion
            remain = 64 - len(data)
            data += remain * '0'
            self.textList.append(data)
            return self.textList
        else:
            present = data[0:64]
            forward = data[64:]
            self.textList.append(present)
            return self.dataPreparation(forward)

    def cipherPreparation(self, data):
        # same as above function
        if len(data) <= 64:
            # end this recursion
            remain = 64 - len(data)
            data += remain * '0'
            self.cipherList.append(data)
            return self.cipherList
        else:
            present = data[0:64]
            forward = data[64:]
            self.cipherList.append(present)
            return self.cipherPreparation(forward)


if __name__ == '__main__':
    GUI = UI()
    GUI.root.mainloop()

DES.py

class DES:
    # txt, key are string , encrypted are boolean
    # utility matrices are put into patterns
    patterns = {}
    currentKey = []
    currentTxt = []
    subKeys = []

    def __init__(self, txt, key, encrypted):
        self.__txt = txt
        self.__key = key
        self.__encrypted = encrypted
        self.__readTables()
        self.__readIntoList()

    def getTxt(self):
        return self.__txt

    def getKey(self):
        return self.__key

    def getStatus(self):
        return self.__encrypted

    def __readTables(self):
        # read contents
        with open("permutation.txt", 'r') as f:
            raw = f.read().split('--\n')
            for form in raw:
                # loop each permutation
                results = []
                lines = form.split("\n")
                pattern_name = lines[0]
                for content in lines[1:]:
                    # loop each line in each permuatation
                    lst = content.split(" ")
                    pure_lst = [int(x) for x in lst if x.strip() != '']
                    # turn into Integer
                    if len(pure_lst) != 0:
                        results.append(pure_lst)
                self.patterns[pattern_name] = results
        return None

    def __readIntoList(self):
        # turn str into list [1, 0, 0 1 ......]
        self.currentKey.clear()
        self.currentTxt.clear()
        for i in self.__key:
            self.currentKey.append(int(i))
        for j in self.__txt:
            self.currentTxt.append(int(j))
        return None

    def encrypt(self):
        # encryption
        if not self.__encrypted:
            self.__subKeyGeneration()

            # initial permutation (IP)
            ip = self.patterns.get('ip')
            new_txt = self.__permutation(self.currentTxt, ip)
            
            # left and right
            length = int(len(new_txt) / 2)
            l = new_txt[0:length]
            r = new_txt[length:]

            # the very important step (16 rounds)
            for key in self.subKeys:
                tempr = r
                r = self.__xorComputation(l, self.__funtionF(key, tempr))
                l = tempr
            result = r + l

            # the last ip-1 permutation
            result = self.__permutation(result, self.patterns.get('ip-1'))
            return result
        else:
            return None

    def decrypt(self):
         # decryption
        if self.__encrypted:
            
            # very similar to encrypt
            self.__subKeyGeneration()
            ip = self.patterns.get('ip')
            new_txt = self.__permutation(self.currentTxt, ip)
            length = int(len(new_txt) / 2)
            l = new_txt[0:length]
            r = new_txt[length:]

            # notice the loop order of subKeys should be reversed
            for key in self.subKeys[::-1]:
                tempr = r
                r = self.__xorComputation(l, self.__funtionF(key, tempr))
                l = tempr
            result = r + l
            result = self.__permutation(result, self.patterns.get('ip-1'))
            return result  
        else:
            return None

    def __funtionF(self, key, data):
        # F function
        eTable = self.patterns.get('etable')
        # E bit-selection
        extension = self.__permutation(data, eTable)
        # XOR        
        xor = self.__xorComputation(key, extension)
        # S boxes (S1 S2 ... S8)
        num = 1
        value = [] # 32 bits
        for i in range(len(xor)):
            if i % 6 == 0:
                pos = xor[i: i + 6]
                # different S box
                s = self.patterns.get('s' + str(num))
                value += self.__extractFromSbox(pos, s)
                num += 1
        # the last p permuatation
        value = self.__permutation(value, self.patterns.get('p'))   
        return value

    def __extractFromSbox(self, pos, s):
        row = str(pos[0]) + str(pos[5])
        column = str(pos[1]) + str(pos[2]) + str(pos[3]) + str(pos[4])
        
        # from binary into decimal
        row_dec = int(row, 2)
        col_dec = int(column, 2)
        
        # get the value in the table
        value = bin(s[row_dec][col_dec])
        if (len(value) - 2) == 4:
            value = value.replace('0b', '')
        else:
            num = 6 - len(value)
            value= value.replace('0b', num * '0')
        return [int(x) for x in value]

    def __xorComputation(self, key, data):
        # XOR 
        xor = []
        for i in range(len(data)):
            if key[i] != data[i]:
                xor.append(1)
            else:
                xor.append(0)
        return xor
        
    def __subKeyGeneration(self):
        # generate subKeys
        pc1 = self.patterns.get('pc1')
        pc2 = self.patterns.get('pc2')
        shift = self.patterns.get('shift')
        self.currentKey = self.__permutation(self.currentKey, pc1)
        self.subKeys = self.__leftShiftAndPermutation(self.currentKey, shift, pc2)
        return None

    def __permutation(self, data, pattern):
        # pattern is a matrix
        new = []
        for i in range(len(pattern)):
            for j in range(len(pattern[i])):
                position = pattern[i][j]
                new.append(int(data[position - 1]))
        return new

    def __leftShiftAndPermutation(self, data, pattern, pc2):
        # pattern, pc2 is a matrix
        new = []
        length = int(len(data) / 2)
        # split into two parts
        c = data[0:length]
        d = data[length:]
        for i in pattern:
            # transformation
            c_front = c[0:i[1]]
            c_back = c[i[1]:]
            c = c_back + c_front
            d_front = d[0:i[1]]
            d_back = d[i[1]:]
            d = d_back + d_front
            # call permutation functions
            key = self.__permutation(c + d, pc2)
            new.append(key)
        return new

pc1
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
--
shift
1          1
2          1
3          2
4          2
5          2
6          2
7          2
8          2
9          1
10          2
11          2
12          2
13          2
14          2
15          2
16          1
--
pc2
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
--
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
--
etable
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
--
s1
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
--
s2
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
--
s3
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
--
s4
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
--
s5
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
--
s6
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
--
s7
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
--
s8
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
--
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  2
--
ip-1
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

推荐文章

手把手DES加密解密详解-Java,Python_Lightr-的博客-CSDN博客_des解密

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值