当前位置:   article > 正文

python模块gmssl,SM国密算法_python gmssl

python gmssl
一、简介

gmssl 是一个用于处理国密算法的 Python 模块,它提供了对国密算法的支持,包括对称加密、非对称加密、散列函数和数字签名等,仅列出了其中两个较为完善的第三方库,需要注意的是,SM1 和 SM7 算法不公开,目前大多库仅实现了 SM2、SM3、SM4 三种密算法。若要使用 SM9 算法,可下载 gmssl-python 源码手动安装
国密算法是中国自主研发的密码算法标准,相比于传统的国际标准算法,如 AES、RSA 等,国密算法具有以下优点和缺点:
优点:
1.安全性:国密算法经过严格的安全性评估和密码学专家的审查,具有较高的安全性。它们采用了更长的密钥长度和更复杂的算法设计,以抵御现代密码攻击。
2.自主可控:国密算法是中国自主研发的,不依赖于国外的算法标准和技术,有助于保护国家信息安全和数据主权。
3.高效性:国密算法在硬件和软件实现上进行了优化,具有较高的加密和解密速度,适用于大规模数据和高性能计算场景。
4.适应性:国密算法涵盖了对称加密、非对称加密、散列函数和数字签名等多个密码学领域,可以满足各种安全需求。
缺点:
1.兼容性:国密算法与传统的国际标准算法存在不兼容的问题,导致在与国际系统和标准进行交互时可能存在一些困难。
2.成熟度:相对于传统的国际标准算法,国密算法的应用和生态系统相对较新,可能在实际应用中还需要进一步的验证和完善。
3.开放性:国密算法的设计和实现细节相对闭源,对于研究者和开发者来说,了解和审计算法的难度较大。
需要注意的是,选择使用国密算法还是传统的国际标准算法应根据具体的应用场景和需求来决定。在保证安全性的前提下,要考虑到兼容性、生态系统支持以及算法的成熟度和广泛应用程度等因素。

二、安装
pip install gmssl
  • 1
三、gmssl之gmssl.func通用的功能函数

gmssl.func 子模块提供了一些通用的功能函数,用于处理国密算法中常见的操作,例如编码转换、随机数生成、消息填充等。这些函数可以在使用国密算法时提供便利。

  1. gmssl.func.rotl(value, shift):将给定的整数 value 向左循环移位 shift 位,并返回结果。
  2. gmssl.func.xor(a, b):对两个字节串 a 和 b 进行按位异或操作,并返回结果。
  3. gmssl.func.random_hex(length):生成指定长度 length 的随机十六进制字符串,并返回结果。
  4. gmssl.func.zero_padding(data, block_size):将字节串 data 进行零填充,使其长度为 block_size 的倍数,并返回结果。
  5. gmssl.func.bytes_to_list(data):将字节串 data 转换为整数列表,并返回结果。
  6. gmssl.func.get_uint32_be(data, offset):从字节串 data 的指定偏移量 offset 处读取 4 个字节,并将其转换为大端序(big-endian)的无符号整数,并返回结果。
  7. gmssl.func.list_to_bytes(data):将整数列表 data 转换为字节串,并返回结果。
  8. gmssl.func.pkcs7_padding(data, block_size):对字节串 data 进行 PKCS7 填充,使其长度为 block_size 的倍数,并返回结果。
  9. gmssl.func.pkcs7_unpadding(data):对经过 PKCS7 填充的字节串 data 进行去填充操作,并返回结果。
  10. gmssl.func.put_uint32_be(value, data, offset):将大端序(big-endian)的无符号整数 value 写入到字节串 data 的指定偏移量 offset 处。
  11. gmssl.func.zero_unpadding(data):对经过零填充的字节串 data 进行去填充操作,并返回结果。
四、gmssl之gmssl.sm2实现了SM2 椭圆曲线公钥密码算法

gmssl.sm2 子模块实现了 SM2 椭圆曲线公钥密码算法,该算法是国密算法体系中的非对称加密算法。它提供了生成密钥对、加密、解密、签名和验证等功能,用于实现 SM2 密钥交换、数据加密和数字签名等操作。

  1. sm2.default_ecc_table:默认的椭圆曲线参数表
  2. cryptSM2 = sm2.CryptSM2(private_key, public_key, ecc_table=default_ecc_table, mode=0, asn1=False):创建一个 SM2 密钥对对象
  • private_key:私钥,类型为字节串(bytes)。私钥用于解密、签名等操作。
  • public_key:公钥,类型为字节串(bytes)。公钥用于加密、验证等操作。
  • ecc_table:椭圆曲线表,类型为字典,默认值为 default_ecc_table。该参数用于指定椭圆曲线的参数,如曲线方程、基点坐标等。如果不提供该参数,默认使用 default_ecc_table 中的参数。
  • mode:模式,类型为整数,默认值为0。该参数用于指定加密和解密的模式,0 表示 C1C3C2 模式,1 表示 C1C2C3 模式。
  • asn1:ASN.1 编码标志,类型为布尔值,默认值为 False。该参数用于指定是否对密文进行 ASN.1 编码。
  1. cryptSM2.sign(data, K):使用私钥对数据进行签名
  • data:待签名的数据,类型为字节串(bytes)。需要注意的是,数据应该是原始的字节形式,而不是经过编码或加密后的数据。
  • K:用于签名的随机数 K,类型为整数。K 的选择应该是随机且不可预测的,以确保签名的安全性。
  1. cryptSM2.verify(sign, data):使用公钥验证签名
  • sign:待验证的签名数据,类型为字节串(bytes)。签名数据应该是原始的字节形式。
  • data:原始数据,类型为字节串(bytes)。需要注意的是,数据应该是原始的字节形式,而不是经过编码或加密后的数据。
  1. cryptSM2.encrypt(data):使用公钥对数据 data 进行加密。
  2. cryptSM2.decrypt(data):使用私钥对数据 data 进行解密。
  3. cryptSM2.sign_with_sm3(data, random_hex_str=None):使用 SM3 哈希算法对数据 进行哈希,并使用私钥对哈希值进行签名。
  • data:待签名的数据,类型为字节串(bytes)。需要注意的是,数据应该是原始的字节形式,而不是经过编码或加密后的数据。
  • random_hex_str:可选参数,随机数的十六进制字符串表示形式。如果提供了该参数,则使用指定的随机数进行签名;如果未提供该参数,则内部会生成一个随机数进行签名。
  1. cryptSM2.verify_with_sm3(sign, data):使用 SM3 哈希算法对数据 进行哈希,并使用公钥验证签名
  • sign:待验证的签名数据,类型为字节串(bytes)。签名数据应该是原始的字节形式。
  • data:原始数据,类型为字节串(bytes)。需要注意的是,数据应该是原始的字节形式,而不是经过编码或加密后的数据。
  1. cryptSM2.mode:加密模式。表示 SM2 加密算法的模式,默认为 0。
  2. cryptSM2.asn1:是否使用 ASN.1 编码。表示是否对加密结果进行 ASN.1 编码,默认为 False。
  3. cryptSM2.ecc_table:椭圆曲线参数表。表示使用的椭圆曲线参数表,默认为 default_ecc_table。
  4. cryptSM2.public_key:公钥。表示 SM2 密钥对的公钥。
  5. cryptSM2.ecc_a3:椭圆曲线参数 a 的值。表示 SM2 椭圆曲线参数 a 的值。
  6. cryptSM2.para_len:参数长度。表示 SM2 参数的长度。
  7. cryptSM2.private_key:私钥。表示 SM2 密钥对的私钥
五、gmssl之gmssl.sm3实现了 SM3 哈希算法

gmssl.sm3 子模块实现了 SM3 哈希算法,该算法是国密算法体系中的散列函数。它可以对输入数据进行哈希计算,生成固定长度的哈希值,用于数据完整性校验、密码存储和消息认证等场景

  1. sm3.T_j:SM3 算法中使用的常量表,共 64 个常量,用于循环中的非线性变换。
  2. sm3.IV:SM3 算法中使用的初始向量,用于初始化哈希计算的中间状态。
  3. sm3.sm3_ff_j(x, y, z, j):SM3 算法中的非线性变换函数,用于计算轮函数中的 FF 操作。
  • x、y、z:表示输入的三个 32 位无符号整数。
  • j:表示当前轮数,取值范围为 0 到 63。
  1. sm3.sm3_gg_j(x, y, z, j):SM3 算法中的非线性变换函数,用于计算轮函数中的 GG 操作。
  • x、y、z:表示输入的三个 32 位无符号整数。
  • j:表示当前轮数,取值范围为 0 到 63。
  1. sm3.sm3_cf(v_i, b_i):SM3 算法中的压缩函数,用于将消息分组进行压缩。
  2. sm3.sm3_hash(msg):计算给定消息的 SM3 哈希值。
  3. sm3.sm3_kdf(z, klen):SM3 算法中的密钥派生函数,用于从输入密钥派生指定长度的密钥。
  4. sm3.sm3_p_0(x):SM3 算法中的置换函数,用于计算 P0 操作。
  5. sm3.sm3_p_1(x):SM3 算法中的置换函数,用于计算 P1 操作。
六、gmssl之gmssl.sm4实现了 SM4 分组密码算法

gmssl.sm4 子模块实现了 SM4 分组密码算法,该算法是国密算法体系中的对称加密算法。它提供了对称密钥的生成、加密和解密等功能,用于保护数据的机密性和保密性。

  1. sm4.PKCS7:表示使用 PKCS7 填充模式。
  2. sm4.ZERO:表示一个字节的零值,通常在填充时使用。
  3. sm4.SM4_BOXES_TABLE:表示 SM4 S-Box 表。
  4. sm4.SM4_CK:表示 SM4 轮密钥常量。
  5. sm4.SM4_DECRYPT:表示 SM4 解密模式。
  6. sm4.SM4_ENCRYPT:表示 SM4 加密模式。
  7. sm4.SM4_FK:表示 SM4 轮函数常量。
  8. cryptSM4 = sm4.CryptSM4(mode=SM4_ENCRYPT, padding_mode=PKCS7) :创建一个 SM4 密钥对对象
  9. cryptSM4.mode:表示当前 cryptSM4 对象的工作模式,可以是 sm4.SM4_ENCRYPT(加密模式)或 sm4.SM4_DECRYPT(解密模式)。
  10. cryptSM4.sk:表示当前 cryptSM4 对象的密钥。
  11. cryptSM4.padding_mode:表示当前 cryptSM4 对象的填充模式,可以是 sm4.PKCS5 或 sm4.PKCS7。
  12. cryptSM4.crypt_cbc(iv, input_data):使用 CBC (Cipher Block Chaining)模式对输入数据进行加密或解密
  • iv:表示初始化向量(Initialization Vector),是一个长度为16字节(128位)的字节数组。在 CBC 模式下,每个数据块都会与前一个数据块的加密结果进行异或操作,而第一个数据块没有前一个数据块,因此需要使用一个初始化向量。初始化向量在加密和解密时必须是相同的。
  • input_data:表示要处理的数据,是一个字节数组。数据长度可以是任意值,但必须是16的倍数(因为 SM4 算法处理的数据块大小为16字节)。
  1. cryptSM4.crypt_ecb(input_data):使用 ECB (Electronic Codebook)模式对输入数据进行加密或解密。
  • input_data:表示要处理的数据,是一个字节数组。数据长度可以是任意值,但必须是16的倍数(因为 SM4 算法处理的数据块大小为16字节)。
  1. cryptSM4.one_round(key, mode):对指定的密钥和模式进行一轮 SM4 运算。可以是 sm4.SM4_ENCRYPT 或 sm4.SM4_DECRYPT。返回运算结果。
  • key:表示轮密钥(Round Key),是一个长度为16字节(128位)的字节数组。轮密钥是 SM4 算法中用于加密或解密的子密钥。
  • mode:表示工作模式,可以是 sm4.SM4_ENCRYPT(加密模式)或 sm4.SM4_DECRYPT(解密模式)。
  1. cryptSM4.set_key(sk, in_put):设置密钥。sk 是密钥,in_put 是一个标志,用于指示是设置加密密钥还是解密密钥。
  • sk:表示密钥(Secret Key),是一个长度为16字节(128位)的字节数组。密钥用于加密或解密数据。
  • in_put:表示输入标志(Input Flag),是一个标志位,用于指示是设置加密密钥还是解密密钥。可以是 sm4.SM4_ENCRYPT(加密密钥)或 sm4.SM4_DECRYPT(解密密钥)。
七、SM2算法示例

创建私钥、密钥生成文件,gmsslCreateKey.py,因为每次生成的key都不一样,可以把生成的key保存到文件中

from random import SystemRandom


class CurveFp:
    def __init__(self, A, B, P, N, Gx, Gy, name):
        self.A = A
        self.B = B
        self.P = P
        self.N = N
        self.Gx = Gx
        self.Gy = Gy
        self.name = name


sm2p256v1 = CurveFp(
    name="sm2p256v1",
    A=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC,
    B=0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93,
    P=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF,
    N=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123,
    Gx=0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7,
    Gy=0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0
)


def multiply(a, n, N, A, P):
    return fromJacobian(jacobianMultiply(toJacobian(a), n, N, A, P), P)


def add(a, b, A, P):
    return fromJacobian(jacobianAdd(toJacobian(a), toJacobian(b), A, P), P)


def inv(a, n):
    if a == 0:
        return 0
    lm, hm = 1, 0
    low, high = a % n, n
    while low > 1:
        r = high // low
        nm, new = hm - lm * r, high - low * r
        lm, low, hm, high = nm, new, lm, low
    return lm % n


def toJacobian(Xp_Yp):
    Xp, Yp = Xp_Yp
    return (Xp, Yp, 1)


def fromJacobian(Xp_Yp_Zp, P):
    Xp, Yp, Zp = Xp_Yp_Zp
    z = inv(Zp, P)
    return ((Xp * z ** 2) % P, (Yp * z ** 3) % P)


def jacobianDouble(Xp_Yp_Zp, A, P):
    Xp, Yp, Zp = Xp_Yp_Zp
    if not Yp:
        return (0, 0, 0)
    ysq = (Yp ** 2) % P
    S = (4 * Xp * ysq) % P
    M = (3 * Xp ** 2 + A * Zp ** 4) % P
    nx = (M ** 2 - 2 * S) % P
    ny = (M * (S - nx) - 8 * ysq ** 2) % P
    nz = (2 * Yp * Zp) % P
    return (nx, ny, nz)


def jacobianAdd(Xp_Yp_Zp, Xq_Yq_Zq, A, P):
    Xp, Yp, Zp = Xp_Yp_Zp
    Xq, Yq, Zq = Xq_Yq_Zq
    if not Yp:
        return (Xq, Yq, Zq)
    if not Yq:
        return (Xp, Yp, Zp)
    U1 = (Xp * Zq ** 2) % P
    U2 = (Xq * Zp ** 2) % P
    S1 = (Yp * Zq ** 3) % P
    S2 = (Yq * Zp ** 3) % P
    if U1 == U2:
        if S1 != S2:
            return (0, 0, 1)
        return jacobianDouble((Xp, Yp, Zp), A, P)
    H = U2 - U1
    R = S2 - S1
    H2 = (H * H) % P
    H3 = (H * H2) % P
    U1H2 = (U1 * H2) % P
    nx = (R ** 2 - H3 - 2 * U1H2) % P
    ny = (R * (U1H2 - nx) - S1 * H3) % P
    nz = (H * Zp * Zq) % P
    return (nx, ny, nz)


def jacobianMultiply(Xp_Yp_Zp, n, N, A, P):
    Xp, Yp, Zp = Xp_Yp_Zp
    if Yp == 0 or n == 0:
        return (0, 0, 1)
    if n == 1:
        return (Xp, Yp, Zp)
    if n < 0 or n >= N:
        return jacobianMultiply((Xp, Yp, Zp), n % N, N, A, P)
    if (n % 2) == 0:
        return jacobianDouble(jacobianMultiply((Xp, Yp, Zp), n // 2, N, A, P), A, P)
    if (n % 2) == 1:
        return jacobianAdd(jacobianDouble(jacobianMultiply((Xp, Yp, Zp), n // 2, N, A, P), A, P), (Xp, Yp, Zp), A, P)


class CreatePrivateKey:
    def __init__(self, curve=sm2p256v1, secret=None):
        self.curve = curve
        self.secret = secret or SystemRandom().randrange(1, curve.N)

    def publicKey(self):
        curve = self.curve
        xPublicKey, yPublicKey = multiply((curve.Gx, curve.Gy), self.secret, A=curve.A, P=curve.P, N=curve.N)
        return CreatePublicKey(xPublicKey, yPublicKey, curve)

    def toString(self):
        return "{}".format(str(hex(self.secret))[2:].zfill(64))


class CreatePublicKey:
    def __init__(self, x, y, curve):
        self.x = x
        self.y = y
        self.curve = curve

    def toString(self, compressed=True):
        return {
            True: str(hex(self.x))[2:],
            False: "{}{}".format(str(hex(self.x))[2:].zfill(64), str(hex(self.y))[2:].zfill(64))
        }.get(compressed)


def create_key():
    priKey = CreatePrivateKey()
    pubKey = priKey.publicKey()
    return priKey.toString(), pubKey.toString(compressed=False)


if __name__ == "__main__":
    priKey = CreatePrivateKey()
    pubKey = priKey.publicKey()
    print(priKey.toString())
    print(pubKey.toString(compressed=False))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  1. 加解密算法
from gmssl import sm2
from gmsslCreateKey import create_key

private_key,public_key = create_key()

data = b'Hello, SM2!'
cipher = sm2.CryptSM2(public_key=public_key,private_key=private_key)  # 创建sm2对象
encrypted_data = cipher.encrypt(data)# 加密
decrypted_data = cipher.decrypt(encrypted_data)# 解密

print('原数据:', data)
print('加密后的数据:', encrypted_data)
print('解密后的数据:', decrypted_data)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  1. 签名和验签算法
import gmssl.func as gmssl_func
from gmssl import sm2
from gmsslCreateKey import create_key

private_key,public_key = create_key()

data = b'Hello, SM2!'

signer = sm2.CryptSM2(private_key=private_key, public_key=public_key)  # 签名
random_hex = gmssl_func.random_hex(signer.para_len)
signature = signer.sign(data,random_hex)

verifier = sm2.CryptSM2(private_key=private_key, public_key=public_key)  # 验证签名
is_valid = verifier.verify(signature, data)
print("签名数据:", data)
print("签名:", signature)
print("签名验证结果:", is_valid)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  1. 调用SM3算法的签名和验签算法
from gmssl import sm2
from gmsslCreateKey import create_key

private_key,public_key = create_key()

data = b'Hello, SM2!'

signer = sm2.CryptSM2(private_key=private_key, public_key=public_key)  # 签名

sign = signer.sign_with_sm3(data) #  16进制
is_valid = signer.verify_with_sm3(sign, data) #  16进制

print("签名数据:", data)
print("签名:", sign)
print("签名验证结果:", is_valid)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
八、SM3使用案例
import gmssl.func as gmssl_func
from gmssl import sm3

data = b"Hello, SM!" # bytes类型
sm3_hash = sm3.sm3_hash(gmssl_func.bytes_to_list(data))
print(sm3_hash)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
九、SM4使用案例
  1. ECB 模式加密和解密(无需初始向量)
import gmssl.func as gmssl_func
from gmssl import sm4

key = bytes([0] * 16)
data = b"Hello, SM4!" # 要加密的数据

sm4_crypt = sm4.CryptSM4() # 创建SM4加密对象
sm4_crypt.set_key(key, sm4.SM4_ENCRYPT) # 设置密钥
ciphertext = sm4_crypt.crypt_ecb(gmssl_func.bytes_to_list(data))# 加密数据
encrypted_data = bytes(gmssl_func.list_to_bytes(ciphertext))# 将加密后的数据转换为字节串

# 解密数据(如果需要)
sm4_crypt.set_key(key, sm4.SM4_DECRYPT)
decrypted_data = sm4_crypt.crypt_ecb(ciphertext)
decrypted_data = bytes(gmssl_func.list_to_bytes(decrypted_data))

print("原始数据:", data.decode("utf-8"))
print("加密后的数据:", encrypted_data.hex())
print("解密后的数据:", decrypted_data.decode("utf-8"))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  1. CBC 模式加密和解密(需要初始向量)
import gmssl.func as gmssl_func
from gmssl import sm4

key = bytes([0] * 16)
data = b"Hello, SM4!" # 要加密的数据
iv = bytes([0] * 16) #  bytes类型

sm4_crypt = sm4.CryptSM4() # 创建SM4加密对象
sm4_crypt.set_key(key, sm4.SM4_ENCRYPT) # 设置密钥
ciphertext = sm4_crypt.crypt_cbc(iv,gmssl_func.bytes_to_list(data))# 加密数据
encrypted_data = bytes(gmssl_func.list_to_bytes(ciphertext))# 将加密后的数据转换为字节串

# 解密数据(如果需要)
sm4_crypt.set_key(key, sm4.SM4_DECRYPT)
decrypted_data = sm4_crypt.crypt_ecb(ciphertext)
decrypted_data = bytes(gmssl_func.list_to_bytes(decrypted_data))

print("原始数据:", data.decode("utf-8"))
print("加密后的数据:", encrypted_data.hex())
print("解密后的数据:", decrypted_data.decode("utf-8"))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/448889
推荐阅读
相关标签
  

闽ICP备14008679号