c语言aes加密函数 c语言 aes加密-成都快上网建站

c语言aes加密函数 c语言 aes加密

C语言如何获取一个函数的代码和数据?

为了提高代码的可读性,推荐都是多以函数实现功能。函数本身需要传入返回数据,那么aes加密函数就会把传入的数据加密,然后通过返回值返回到变量里面。我们假设aes函数名字叫aes,那么我们就这样调用:需要加密的数据是a。加密结果是result

创新互联长期为超过千家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为临沧企业提供专业的成都网站设计、成都网站制作,临沧网站改版等技术服务。拥有十载丰富建站经验和众多成功案例,为您定制开发。

int a = 000;

String result = aes(a);

result便是加密后的a

实际代码会很复杂但是结构是这样的。

求AES加密中MixColumn函数的C语言写法

1 AES加密、解密算法原理和AVR实现

AES是分组密钥,算法输入128位数据,密钥长度也是128位。用Nr表示对一个数据分组加密的轮数(加密轮数与密钥长度的关系如表1所列)。每一轮都需要一个与输入分组具有相同长度的扩展密钥Expandedkey(i)的参与。由于外部输入的加密密钥K长度有限,所以在算法中要用一个密钥扩展程序(Keyexpansion)把外部密钥K扩展成更长的比特串,以生成各轮的加密和解密密钥。

1.1圈变化

AES每一个圈变换由以下三个层组成:

非线性层——进行Subbyte变换;

线行混合层——进行ShiftRow和MixColumn运算;

密钥加层——进行AddRoundKey运算。

① Subbyte变换是作用在状态中每个字节上的一种非线性字节转换,可以通过计算出来的S盒进行映射。

Schange:

ldi zh,$01;将指针指向S盒的首地址

mov zl,r2;将要查找的数据作为指针低地址

ldtemp,z+;取出这个对应的数据

mov r2,temp;交换数据完成查表

.

.

.

ret

② ShiftRow是一个字节换位。它将状态中的行按照不同的偏移量进行循环移位,而这个偏移量也是根据Nb的不同而选择的[3]。

shiftrow:;这是一个字节换位的子程序

mov temp,r3;因为是4×4

mov r3,r7; r2 r6 r10 r14 r2 r6 r10 r14

mov r7,r11; r3 r7 r11 r15---r7 r11 r15 r3

mov r11,r15; r4 r8 r12 r17 r12 r17 r4 r8

mov r15,temp; r5 r9 r13 r18 r18 r5 r9 r13

mov temp,r4

mov temp1,r8

mov r4,r12

mov r8,r17

mov r12,temp

mov r17,temp1

mov temp,r18

mov r18,r13

mov r13,r9

mov r9,r5

mov r5,temp

ret

③ 在MixColumn变换中,把状态中的每一列看作GF(28)上的多项式a(x)与固定多项式c(x)相乘的结果。b(x)=c(x)*a(x)的系数这样计算:*运算不是普通的乘法运算,而是特殊的运算,即

b(x)=c(x)·a(x)(mod x4+1)

对于这个运算

b0=02。a0+03。a1+a2+a3

令xtime(a0)=02。a0

其中,符号“。”表示模一个八次不可约多项式的同余乘法[3]。

mov temp,a0;这是一个mixcolimn子程序

rcall xtime;调用xtime程序

mov a0,temp

mov temp,a1

rcall xtime

eor a0,a1

eor a0,temp

eor a0,a2

eor a0,a3;完成b(x)的计算

.

.

.

xtime:;这是一个子程序

ldi temp1,$1b

lsl temp

brcs next1;如果最高位是1,则转移

next: ret;否则什么也不变化

next1:eor temp,temp1

rjmp next

对于逆变化,其矩阵C要改变成相应的D,即b(x)=d(x)*a(x)。

④ 密钥加层运算(addround)是将圈密钥状态中的对应字节按位“异或”。

⑤ 根据线性变化的性质[1],解密运算是加密变化的逆变化。这里不再详细叙述。

1.2轮变化

对不同的分组长度,其对应的轮变化次数是不同的,如表1所列。

1.3密钥扩展

AES算法利用外部输入密钥K(密钥串的字数为Nk),通过密钥的扩展程序得到共计4(Nr+1)字的扩展密钥。它涉及如下三个模块:

① 位置变换(rotword)——把一个4字节的序列[A,B,C,D]变化成[B,C,D,A];

② S盒变换(subword)——对一个4字节进行S盒代替;

③ 变换Rcon[i]——Rcon[i]表示32位比特字[xi-1,00,00,00]。这里的x是(02),如

Rcon[1]=[01000000];Rcon[2]=[02000000];Rcon[3]=[04000000]……

扩展密钥的生成:扩展密钥的前Nk个字就是外部密钥K;以后的字W[[i]]等于它前一个字W[[i-1]]与前第Nk个字W[[i-Nk]]的“异或”,即W[[i]]=W[[i-1]]�W[[i- Nk]]。但是若i为Nk的倍数,则W[i]=W[i-Nk]�Subword(Rotword(W[[i-1]]))�Rcon[i/Nk]。

程序执行的时候,主要调用以上几个子程序,具体实现如下:

Keyexpansion:

rcall rotwoed

rcall subword

rcall Rcon

.

.

.

2 AES加密、解密算法的优化

由以上算法的流程中可以清楚地看到,整个算法中程序耗时最多的就是圈变化部分,因此对于算法的优化也就在此;而圈变化部分可以优化的也就是列变化。因为列变化是一个模乘同余规则。由于AES加密和解密是不对称的,如果不对其进行优化,会使算法的解密速度远远大于加密的速度[1]。

① 加密运算。对列变换(Mixcolumn)可以通过调用xtime子程序进行优化。

另一种有效的优化方法就是离线构造一个表格,即列变化表格。这样只要通过查表的方式就可以提高加密速度。

② 解密算法的优化。由于解密的列变换的系数分别是09、0E、0B和0D。在AVR单片机上实现以上的乘法显然是需要很多的时间,从而导致了解密的性能降低。

优化方法一:对列变化进行分解使倍乘次数降低。

仔细研究解密矩阵的系数,不难发现解密矩阵和加密矩阵有着一定的联系,即解密矩阵等于加密矩阵和一个矩阵的相乘。通过这样的联系,就可以对算法进行优化:

这样一来,只用几个简单的“异或”就可以实现列变化,使倍乘的次数降低,提高解密的速度。

优化方法二:构造表格。

同加密构造方法一样,可以构造四个表格T[ea]=e×a; T[9a]=9×a;T[9a]=9×a;T[ba]=b×a。这样一来,也只需要进行查表和简单的异或就可以完成解密的任务。虽然这种方法将增加额外的开销,但是它却是一种有效的方法。

3 AES加密与解密的实验仿真

根据以上实验步骤和优化方法得出表2、3所列实验结果。

设主密钥为:000102030405060708090a0b0c0d0e0f(128bit)。

加密明文:00112233445566778899AABBCCDDEEFF。

密文:69C4E0D86A7B0430D8CDB78070B4C55A。

解密密文:69C4E0D86A7B0430D8CDB78070B4C55A。

明文:00112233445566778899AABBCCDDEEFF。

总之,AES密码是一个非对称密码体制,它的解密要比加密复杂和费时。解密优化算法没有增加存储空间的基础上,以列变化为基础进行处理,程序比原始的要小,而且节约了时间。解密优化方法速度最快,效率最高,但要增加系统的存储空间,因此它的程序也是最大的一个

流程图省略 朋友参考吧

求AES算法加密C语言完整程序

恰好我有。能运行的,C语言的。

#include string.h

#include "aes.h"

#include "commonage.h"

#define byte unsigned char

#define BPOLY 0x1b //! Lower 8 bits of (x^8+x^4+x^3+x+1), ie. (x^4+x^3+x+1).

#define BLOCKSIZE 16 //! Block size in number of bytes.

#define KEYBITS 128 //! Use AES128.

#define ROUNDS 10 //! Number of rounds.

#define KEYLENGTH 16 //! Key length in number of bytes.

byte xdata block1[ 256 ]; //! Workspace 1.

byte xdata block2[ 256 ]; //! Worksapce 2.

byte xdata * powTbl; //! Final location of exponentiation lookup table.

byte xdata * logTbl; //! Final location of logarithm lookup table.

byte xdata * sBox; //! Final location of s-box.

byte xdata * sBoxInv; //! Final location of inverse s-box.

byte xdata * expandedKey; //! Final location of expanded key.

void CalcPowLog( byte * powTbl, byte * logTbl )

{

byte xdata i = 0;

byte xdata t = 1;

do {

// Use 0x03 as root for exponentiation and logarithms.

powTbl[i] = t;

logTbl[t] = i;

i++;

// Muliply t by 3 in GF(2^8).

t ^= (t 1) ^ (t 0x80 ? BPOLY : 0);

} while( t != 1 ); // Cyclic properties ensure that i 255.

powTbl[255] = powTbl[0]; // 255 = '-0', 254 = -1, etc.

}

void CalcSBox( byte * sBox )

{

byte xdata i, rot;

byte xdata temp;

byte xdata result;

// Fill all entries of sBox[].

i = 0;

do {

// Inverse in GF(2^8).

if( i 0 ) {

temp = powTbl[ 255 - logTbl[i] ];

} else {

temp = 0;

}

// Affine transformation in GF(2).

result = temp ^ 0x63; // Start with adding a vector in GF(2).

for( rot = 0; rot 4; rot++ ) {

// Rotate left.

temp = (temp1) | (temp7);

// Add rotated byte in GF(2).

result ^= temp;

}

// Put result in table.

sBox[i] = result;

} while( ++i != 0 );

}

void CalcSBoxInv( byte * sBox, byte * sBoxInv )

{

byte xdata i = 0;

byte xdata j = 0;

// Iterate through all elements in sBoxInv using i.

do {

// Search through sBox using j.

cleardog();

do {

// Check if current j is the inverse of current i.

if( sBox[ j ] == i ) {

// If so, set sBoxInc and indicate search finished.

sBoxInv[ i ] = j;

j = 255;

}

} while( ++j != 0 );

} while( ++i != 0 );

}

void CycleLeft( byte * row )

{

// Cycle 4 bytes in an array left once.

byte xdata temp = row[0];

row[0] = row[1];

row[1] = row[2];

row[2] = row[3];

row[3] = temp;

}

void InvMixColumn( byte * column )

{

byte xdata r0, r1, r2, r3;

r0 = column[1] ^ column[2] ^ column[3];

r1 = column[0] ^ column[2] ^ column[3];

r2 = column[0] ^ column[1] ^ column[3];

r3 = column[0] ^ column[1] ^ column[2];

column[0] = (column[0] 1) ^ (column[0] 0x80 ? BPOLY : 0);

column[1] = (column[1] 1) ^ (column[1] 0x80 ? BPOLY : 0);

column[2] = (column[2] 1) ^ (column[2] 0x80 ? BPOLY : 0);

column[3] = (column[3] 1) ^ (column[3] 0x80 ? BPOLY : 0);

r0 ^= column[0] ^ column[1];

r1 ^= column[1] ^ column[2];

r2 ^= column[2] ^ column[3];

r3 ^= column[0] ^ column[3];

column[0] = (column[0] 1) ^ (column[0] 0x80 ? BPOLY : 0);

column[1] = (column[1] 1) ^ (column[1] 0x80 ? BPOLY : 0);

column[2] = (column[2] 1) ^ (column[2] 0x80 ? BPOLY : 0);

column[3] = (column[3] 1) ^ (column[3] 0x80 ? BPOLY : 0);

r0 ^= column[0] ^ column[2];

r1 ^= column[1] ^ column[3];

r2 ^= column[0] ^ column[2];

r3 ^= column[1] ^ column[3];

column[0] = (column[0] 1) ^ (column[0] 0x80 ? BPOLY : 0);

column[1] = (column[1] 1) ^ (column[1] 0x80 ? BPOLY : 0);

column[2] = (column[2] 1) ^ (column[2] 0x80 ? BPOLY : 0);

column[3] = (column[3] 1) ^ (column[3] 0x80 ? BPOLY : 0);

column[0] ^= column[1] ^ column[2] ^ column[3];

r0 ^= column[0];

r1 ^= column[0];

r2 ^= column[0];

r3 ^= column[0];

column[0] = r0;

column[1] = r1;

column[2] = r2;

column[3] = r3;

}

byte Multiply( unsigned char num, unsigned char factor )

{

byte mask = 1;

byte result = 0;

while( mask != 0 ) {

// Check bit of factor given by mask.

if( mask factor ) {

// Add current multiple of num in GF(2).

result ^= num;

}

// Shift mask to indicate next bit.

mask = 1;

// Double num.

num = (num 1) ^ (num 0x80 ? BPOLY : 0);

}

return result;

}

byte DotProduct( unsigned char * vector1, unsigned char * vector2 )

{

byte result = 0;

result ^= Multiply( *vector1++, *vector2++ );

result ^= Multiply( *vector1++, *vector2++ );

result ^= Multiply( *vector1++, *vector2++ );

result ^= Multiply( *vector1 , *vector2 );

return result;

}

void MixColumn( byte * column )

{

byte xdata row[8] = {

0x02, 0x03, 0x01, 0x01,

0x02, 0x03, 0x01, 0x01

}; // Prepare first row of matrix twice, to eliminate need for cycling.

byte xdata result[4];

// Take dot products of each matrix row and the column vector.

result[0] = DotProduct( row+0, column );

result[1] = DotProduct( row+3, column );

result[2] = DotProduct( row+2, column );

result[3] = DotProduct( row+1, column );

// Copy temporary result to original column.

column[0] = result[0];

column[1] = result[1];

column[2] = result[2];

column[3] = result[3];

}

void SubBytes( byte * bytes, byte count )

{

do {

*bytes = sBox[ *bytes ]; // Substitute every byte in state.

bytes++;

} while( --count );

}

void InvSubBytesAndXOR( byte * bytes, byte * key, byte count )

{

do {

// *bytes = sBoxInv[ *bytes ] ^ *key; // Inverse substitute every byte in state and add key.

*bytes = block2[ *bytes ] ^ *key; // Use block2 directly. Increases speed.

bytes++;

key++;

} while( --count );

}

void InvShiftRows( byte * state )

{

byte temp;

// Note: State is arranged column by column.

// Cycle second row right one time.

temp = state[ 1 + 3*4 ];

state[ 1 + 3*4 ] = state[ 1 + 2*4 ];

state[ 1 + 2*4 ] = state[ 1 + 1*4 ];

state[ 1 + 1*4 ] = state[ 1 + 0*4 ];

state[ 1 + 0*4 ] = temp;

// Cycle third row right two times.

temp = state[ 2 + 0*4 ];

state[ 2 + 0*4 ] = state[ 2 + 2*4 ];

state[ 2 + 2*4 ] = temp;

temp = state[ 2 + 1*4 ];

state[ 2 + 1*4 ] = state[ 2 + 3*4 ];

state[ 2 + 3*4 ] = temp;

// Cycle fourth row right three times, ie. left once.

temp = state[ 3 + 0*4 ];

state[ 3 + 0*4 ] = state[ 3 + 1*4 ];

state[ 3 + 1*4 ] = state[ 3 + 2*4 ];

state[ 3 + 2*4 ] = state[ 3 + 3*4 ];

state[ 3 + 3*4 ] = temp;

}

void ShiftRows( byte * state )

{

byte temp;

// Note: State is arranged column by column.

// Cycle second row left one time.

temp = state[ 1 + 0*4 ];

state[ 1 + 0*4 ] = state[ 1 + 1*4 ];

state[ 1 + 1*4 ] = state[ 1 + 2*4 ];

state[ 1 + 2*4 ] = state[ 1 + 3*4 ];

state[ 1 + 3*4 ] = temp;

// Cycle third row left two times.

temp = state[ 2 + 0*4 ];

state[ 2 + 0*4 ] = state[ 2 + 2*4 ];

state[ 2 + 2*4 ] = temp;

temp = state[ 2 + 1*4 ];

state[ 2 + 1*4 ] = state[ 2 + 3*4 ];

state[ 2 + 3*4 ] = temp;

// Cycle fourth row left three times, ie. right once.

temp = state[ 3 + 3*4 ];

state[ 3 + 3*4 ] = state[ 3 + 2*4 ];

state[ 3 + 2*4 ] = state[ 3 + 1*4 ];

state[ 3 + 1*4 ] = state[ 3 + 0*4 ];

state[ 3 + 0*4 ] = temp;

}

void InvMixColumns( byte * state )

{

InvMixColumn( state + 0*4 );

InvMixColumn( state + 1*4 );

InvMixColumn( state + 2*4 );

InvMixColumn( state + 3*4 );

}

void MixColumns( byte * state )

{

MixColumn( state + 0*4 );

MixColumn( state + 1*4 );

MixColumn( state + 2*4 );

MixColumn( state + 3*4 );

}

void XORBytes( byte * bytes1, byte * bytes2, byte count )

{

do {

*bytes1 ^= *bytes2; // Add in GF(2), ie. XOR.

bytes1++;

bytes2++;

} while( --count );

}

void CopyBytes( byte * to, byte * from, byte count )

{

do {

*to = *from;

to++;

from++;

} while( --count );

}

void KeyExpansion( byte * expandedKey )

{

byte xdata temp[4];

byte i;

byte xdata Rcon[4] = { 0x01, 0x00, 0x00, 0x00 }; // Round constant.

unsigned char xdata *key;

unsigned char xdata a[16];

key=a;

//以下为加解密密码,共16字节。可以选择任意值

key[0]=0x30;

key[1]=0x30;

key[2]=0x30;

key[3]=0x30;

key[4]=0x30;

key[5]=0x30;

key[6]=0x30;

key[7]=0x30;

key[8]=0x30;

key[9]=0x30;

key[10]=0x30;

key[11]=0x30;

key[12]=0x30;

key[13]=0x30;

key[14]=0x30;

key[15]=0x30;

////////////////////////////////////////////

// Copy key to start of expanded key.

i = KEYLENGTH;

do {

*expandedKey = *key;

expandedKey++;

key++;

} while( --i );

// Prepare last 4 bytes of key in temp.

expandedKey -= 4;

temp[0] = *(expandedKey++);

temp[1] = *(expandedKey++);

temp[2] = *(expandedKey++);

temp[3] = *(expandedKey++);

// Expand key.

i = KEYLENGTH;

while( i BLOCKSIZE*(ROUNDS+1) ) {

// Are we at the start of a multiple of the key size?

if( (i % KEYLENGTH) == 0 ) {

CycleLeft( temp ); // Cycle left once.

SubBytes( temp, 4 ); // Substitute each byte.

XORBytes( temp, Rcon, 4 ); // Add constant in GF(2).

*Rcon = (*Rcon 1) ^ (*Rcon 0x80 ? BPOLY : 0);

}

// Keysize larger than 24 bytes, ie. larger that 192 bits?

#if KEYLENGTH 24

// Are we right past a block size?

else if( (i % KEYLENGTH) == BLOCKSIZE ) {

SubBytes( temp, 4 ); // Substitute each byte.

}

#endif

// Add bytes in GF(2) one KEYLENGTH away.

XORBytes( temp, expandedKey - KEYLENGTH, 4 );

// Copy result to current 4 bytes.

*(expandedKey++) = temp[ 0 ];

*(expandedKey++) = temp[ 1 ];

*(expandedKey++) = temp[ 2 ];

*(expandedKey++) = temp[ 3 ];

i += 4; // Next 4 bytes.

}

}

void InvCipher( byte * block, byte * expandedKey )

{

byte round = ROUNDS-1;

expandedKey += BLOCKSIZE * ROUNDS;

XORBytes( block, expandedKey, 16 );

expandedKey -= BLOCKSIZE;

do {

InvShiftRows( block );

InvSubBytesAndXOR( block, expandedKey, 16 );

expandedKey -= BLOCKSIZE;

InvMixColumns( block );

} while( --round );

InvShiftRows( block );

InvSubBytesAndXOR( block, expandedKey, 16 );

}

void Cipher( byte * block, byte * expandedKey ) //完成一个块(16字节,128bit)的加密

{

byte round = ROUNDS-1;

XORBytes( block, expandedKey, 16 );

expandedKey += BLOCKSIZE;

do {

SubBytes( block, 16 );

ShiftRows( block );

MixColumns( block );

XORBytes( block, expandedKey, 16 );

expandedKey += BLOCKSIZE;

} while( --round );

SubBytes( block, 16 );

ShiftRows( block );

XORBytes( block, expandedKey, 16 );

}

void aesInit( unsigned char * tempbuf )

{

powTbl = block1;

logTbl = block2;

CalcPowLog( powTbl, logTbl );

sBox = tempbuf;

CalcSBox( sBox );

expandedKey = block1; //至此block1用来存贮密码表

KeyExpansion( expandedKey );

sBoxInv = block2; // Must be block2. block2至此开始只用来存贮SBOXINV

CalcSBoxInv( sBox, sBoxInv );

}

//对一个16字节块解密,参数buffer是解密密缓存,chainBlock是要解密的块

void aesDecrypt( unsigned char * buffer, unsigned char * chainBlock )

{

//byte xdata temp[ BLOCKSIZE ];

//CopyBytes( temp, buffer, BLOCKSIZE );

CopyBytes(buffer,chainBlock,BLOCKSIZE);

InvCipher( buffer, expandedKey );

//XORBytes( buffer, chainBlock, BLOCKSIZE );

CopyBytes( chainBlock, buffer, BLOCKSIZE );

}

//对一个16字节块完成加密,参数buffer是加密缓存,chainBlock是要加密的块

void aesEncrypt( unsigned char * buffer, unsigned char * chainBlock )

{

CopyBytes( buffer, chainBlock, BLOCKSIZE );

//XORBytes( buffer, chainBlock, BLOCKSIZE );

Cipher( buffer, expandedKey );

CopyBytes( chainBlock, buffer, BLOCKSIZE );

}

//加解密函数,参数为加解密标志,要加解密的数据缓存起始指针,要加解密的数据长度(如果解密运算,必须是16的整数倍。)

unsigned char aesBlockDecrypt(bit Direct,unsigned char *ChiperDataBuf,unsigned char DataLen)

{

unsigned char xdata i;

unsigned char xdata Blocks;

unsigned char xdata sBoxbuf[256];

unsigned char xdata tempbuf[16];

unsigned long int xdata OrignLen=0; //未加密数据的原始长度

if(Direct==0)

{

*((unsigned char *)OrignLen+3)=ChiperDataBuf[0];

*((unsigned char *)OrignLen+2)=ChiperDataBuf[1];

*((unsigned char *)OrignLen+1)=ChiperDataBuf[2];

*((unsigned char *)OrignLen)=ChiperDataBuf[3];

DataLen=DataLen-4;

}

else

{

memmove(ChiperDataBuf+4,ChiperDataBuf,DataLen);

OrignLen=DataLen;

ChiperDataBuf[0]=OrignLen;

ChiperDataBuf[1]=OrignLen8;

ChiperDataBuf[2]=OrignLen16;

ChiperDataBuf[3]=OrignLen24;

}

cleardog();

aesInit(sBoxbuf); //初始化

if(Direct==0) //解密

{

Blocks=DataLen/16;

for(i=0;iBlocks;i++)

{

cleardog();

aesDecrypt(tempbuf,ChiperDataBuf+4+16*i);

}

memmove(ChiperDataBuf,ChiperDataBuf+4,OrignLen);

cleardog();

return(OrignLen);

}

else //加密

{

if(DataLen%16!=0)

{

Blocks=DataLen/16+1;

//memset(ChiperDataBuf+4+Blocks*16-(DataLen%16),0x00,DataLen%16); //不足16字节的块补零处理

}

else

{

Blocks=DataLen/16;

}

for(i=0;iBlocks;i++)

{

cleardog();

aesEncrypt(tempbuf,ChiperDataBuf+4+16*i);

}

cleardog();

return(Blocks*16+4);

}

}

//#endif

以上是C文件。以下是头文件

#ifndef AES_H

#define AES_H

extern void aesInit( unsigned char * tempbuf );

extern void aesDecrypt(unsigned char *buffer, unsigned char *chainBlock);

extern void aesEncrypt( unsigned char * buffer, unsigned char * chainBlock );

extern void aesInit( unsigned char * tempbuf );

extern void aesDecrypt( unsigned char * buffer, unsigned char * chainBlock );

extern void aesEncrypt( unsigned char * buffer, unsigned char * chainBlock );

extern unsigned char aesBlockDecrypt(bit Direct,unsigned char *ChiperDataBuf,unsigned char DataLen);

#endif // AES_H

这是我根据网上程序改写的。只支持128位加解密。没有使用占内存很多的查表法。故运算速度会稍慢。


本文标题:c语言aes加密函数 c语言 aes加密
网页链接:http://kswjz.com/article/hhhcoe.html
扫二维码与项目经理沟通

我们在微信上24小时期待你的声音

解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流