|
Per leggere la password da input usiamo la stessa funzione che utilizziamo per il DES: des_read_pw_string() [leggete la sez. 2]. La chiave dovra' essere di tipo BF_KEY. /* BF_set_key(): imposta la chiave (di tipo BF_KEY) usando la stringa data di lunghezza len */ void BF_set_key(BF_KEY *key, int len, const unsigned char *data); /* BF_ecb_encrypt(): e' la funzione base per cifrare/decifrare offerta dalla libreria. Cifra/Decifra blocchi di 8 bytes a seconda che mode sia BF_ENCRYPT o BF_DECRYPT. */ void BF_ecb_encrypt(const unsigned char *in, unsigned char *out, BF_KEY *key, int enc); 4. Funzioni & headers IDEA #include <openssl/idea.h> Lo schedule della chiave deve essere impostato a IDEA_KEY_SCHEDULE. Ricordo, inoltre, che il ciphertext e il plaintext devono essere blocchi di 8 bytes. Per leggere la password da input usiamo la stessa funzione che utilizziamo per il DES: des_read_pw_string() [leggete la sez. 2]. /* idea_set_encrypt_key(): imposta il key schedule della chiave per la fase di cifrazione */ void idea_set_encrypt_key(const unsigned char *key, IDEA_KEY_SCHEDULE *ks); /* idea_set_decrypt_key(): imposta il key schedule della chiave per la fase di decifrazione partendo dal key schedule generato con idea_set_encrypt_key() */ void idea_set_decrypt_key(IDEA_KEY_SCHEDULE *ek, IDEA_KEY_SCHEDULE *dk); /* idea_ecb_encrypt(): cifra/decifra [a seconda del key schedule passatogli] i dati contenuti nel blocco di 8 bytes in e li mette nel blocco di 8 bytes out. */ void idea_ecb_encrypt(const unsigned char *in, unsigned char *out, IDEA_KEY_SCHEDULE *ks); 5. Funzioni & headers MD5 #include <openssl/md5.h> Questa libreria include alcune funzioni per l'hashing dei messaggi. /* MD5(): performa l'hash del messaggio passatogli */ unsigned char *MD5(const unsigned char *d, unsigned long n, unsigned char *md); [Se md e' NULL il message digest viene messo in un array statico] A livello applicativo, invece di usare questa funzione conviene utilizzare le routines EVP, che lavorano a piu' alto livello. #include <openssl/evp.h> /* EVP_md5(): ritorna una struttura EVP_MD per l'algoritmo md5 */ EVP_MD *EVP_md5(void); /* EVP_DigestInit(): inizializza un context CTX per usare un digest di tipo type*/ void EVP_DigestInit(EVP_MD_CTX *ctx, const EVP_MD *type); /* EVP_DigestUpdate(): computa l'hash di cnt bytes di d nel CTX */ void EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, unsigned int cnt); /* EVP_DigestFinal(): mette l'hash contenuto nel context CTX in md*/ void EVP_DigestFinal(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s); man md5 Ora che abbiamo visto le principali funzioni potete guardarvi il codice sorgente di UNISFED. [Il codice contiene anche l'algoritmo di crittografia a chiave pubblica RSA, descritte nel numero 0A di Ondaquadra] Da compilarsi [una volta suddivisi i files] in questo modo: gcc unisfed.c -o unisfed -lssl Ciao a tutti e alla prossima. -------------------block_ciphers.c------------------------------------- #include <openssl/des.h> #include <openssl/blowfish.h> #include <openssl/idea.h>
#define MAXPASSLEN 31 /* DES * If mode = 1 encrypts *filein file with DES algorithm * If mode = 0 decrypts *filein file with DES algorithm * output is written in *fileout file * Returns 1 if the function runs successfully * Works on 8 bytes blocks * */ void des_encrypt_decrypt(int mode, char *filein, char *fileout) { FILE *fpin,*fpout; char buf[MAXPASSLEN]; des_cblock key,inmsg,outmsg; /* key, plaintext, ciphertext must be 8 byte blocks */ des_key_schedule sched; if (strcmp(filein,fileout) == 0) { fprintf(stderr,"Error: input and output files must not \ be the same file.\n"); exit(EXIT_FAILURE); } /* des_read_pw_string reads password from stdin and stores it in buf this function automatically asks to re-enter password and checks it */ memset(buf,'\0',MAXPASSLEN); if (mode == 1) { printf("Encrypting file '%s' with des cipher.\n", \ filein); if (des_read_pw_string(buf,MAXPASSLEN - 1,"Enter the \ password:\n",1) != 0) { fprintf(stderr,"Error: failed to read \ password.\n"); exit(EXIT_FAILURE); } } else { printf("Decrypting file '%s' with des cipher.\n",\ filein); if (des_read_pw_string(buf,MAXPASSLEN - 1,"Enter the \ password:\n",0) != 0) { fprintf(stderr,"Error: failed to read \ password.\n"); exit(EXIT_FAILURE); } } /* des_string to key convers the password to a key */ des_string_to_key(buf,&key); /* des_set_key_checked checks that a key passed in of odd parity and set up the key schedule */ des_set_key_checked(&key,sched); fpin = fopen(filein,"r"); if ((fpout = fopen(fileout,"w")) == NULL) { fprintf(stderr,"Error: failed to open output file.\n"); exit(EXIT_FAILURE); } /* reads 8 bytes at a time(block=8bytes),encrypts/decrypts each block with ecb */ while (fread(inmsg,1,8,fpin)) { memset(outmsg,'\0',8); des_ecb_encrypt(&inmsg,&outmsg,sched,mode); fwrite(outmsg,1,8,fpout); memset(inmsg,'\0',8); } fclose(fpin); fclose(fpout); printf("Done.\n"); return; } /* Blowfish * If mode = 1 encrypts *filein file with Blowfish algorithm * If mode = 0 decrypts *filein file with Blowfish algorithm * output is written in *fileout file * Returns 1 if the function runs successfully * Works on 8 bytes blocks * */ void bf_encrypt_decrypt(int mode, char *filein, char *fileout) { FILE *fpin,*fpout; char buf[MAXPASSLEN]; unsigned char inmsg[8],outmsg[8]; /* blowfish operates on 8 byte blocks */ BF_KEY key; if (strcmp(filein,fileout) == 0) { fprintf(stderr,"Error: input and output files must \ not be the same file.\n"); exit(EXIT_FAILURE); } /* reads password from stdin using the same function used for des passwords */ memset(buf,'\0',MAXPASSLEN); if (mode == 1) { printf("Encrypting file '%s' with BlowFish cipher.\n",\ filein); if (des_read_pw_string(buf,MAXPASSLEN - 1,"Enter the \ password:\n",1) != 0) { fprintf(stderr,"Error: failed to read \ password.\n"); exit(EXIT_FAILURE); } } else { printf("Decrypting file '%s' with BlowFish cipher.\n",\ filein); if (des_read_pw_string(buf,MAXPASSLEN - 1,"Enter the \ password:\n",0) != 0) { fprintf(stderr,"Error: failed to read \ password.\n"); exit(EXIT_FAILURE); } } /* set up the key using password stored in buf from des_read_pw_string */ BF_set_key(&key,strlen(buf),buf); fpin = fopen(filein,"r"); if ((fpout = fopen(fileout,"w")) == NULL) { fprintf(stderr,"Error: failed to open output file.\n"); exit(EXIT_FAILURE); } /* reads 8 bytes at a time(block=8bytes),encrypts/decrypts each block with ecb */ while (fread(inmsg,1,8,fpin)) { memset(outmsg,'\0',8); BF_ecb_encrypt(inmsg,outmsg,&key,mode); fwrite(outmsg,1,8,fpout); memset(inmsg,'\0',8); } fclose(fpin); fclose(fpout); printf("Done.\n"); return; } /* IDEA * If mode = 1 encrypts *filein file with IDEA algorithm * If mode = 0 decrypts *filein file with IDEA algorithm * output is written in *fileout file * Returns 1 if the function runs successfully * Works on 8 bytes blocks * */ void idea_encrypt_decrypt(int mode, char *filein, char *fileout) { FILE *fpin,*fpout; char buf[MAXPASSLEN]; unsigned char inmsg[8],outmsg[8]; /* idea operates on 8 byte blocks */ IDEA_KEY_SCHEDULE sched,dc_sched; if (strcmp(filein,fileout) == 0) { fprintf(stderr,"Error: input and output files must not\ be the same file.\n"); exit(EXIT_FAILURE); } /* reads password from stdin using the same function used for des passwords */ memset(buf,'\0',MAXPASSLEN); if (mode == 1) { printf("Encrypting file '%s' with IDEA cipher.\n",\ filein); if (des_read_pw_string(buf,MAXPASSLEN - 1,"Enter the \ password:\n",1) != 0) { fprintf(stderr,"Error: failed to read \ password.\n"); exit(EXIT_FAILURE); } } else { printf("Decrypting file '%s' with IDEA cipher.\n",\ filein); if (des_read_pw_string(buf,MAXPASSLEN - 1,"Enter the \ password:\n",0) != 0) { fprintf(stderr,"Error: failed to read \ password.\n"); exit(EXIT_FAILURE); } } /* set up the key schedule for encryption */ idea_set_encrypt_key(buf,&sched); /* setting up the key schedule for decryption in mode == 0 */ if (mode == 0) { idea_set_decrypt_key(&sched,&dc_sched); sched = dc_sched; } fpin = fopen(filein,"r"); if ((fpout = fopen(fileout,"w")) == NULL) { fprintf(stderr,"Error: failed to open output file.\n"); exit(EXIT_FAILURE); } /* reads 8 bytes at a time(block=8bytes),encrypts/decrypts each\ block with ecb */ while (fread(inmsg,1,8,fpin)) { memset(outmsg,'\0',8); idea_ecb_encrypt(inmsg,outmsg,&sched); fwrite(outmsg,1,8,fpout); memset(inmsg,'\0',8); } fclose(fpin); fclose(fpout); printf("Done.\n"); return; } -------------------end block_ciphers.c--------------------------------- -------------------hash.c---------------------------------------------- #include <openssl/md5.h> #include <openssl/evp.h> void make_hex(u_char *in, u_char *out) { const char *hex = "0123456789abcdef"; int i; for(i = 0; i < 16; i++, in++) { *out++ = hex[*in >> 4]; *out++ = hex[*in & 0xf]; } *out = 0x00; return; } /* MD5 * Hashing algorithm * md5hash() performs the hash of a given file. */ void md5hash(char *filein) { FILE *fp = NULL; unsigned int tmpsize=0,linesize=0; char *hashbuf = NULL; unsigned char *hash = NULL; EVP_MD_CTX ctx; printf("MD5 hash of '%s' file is:\n",filein); EVP_DigestInit(&ctx,EVP_md5()); /* initializes digest context*/ fp = fopen(filein,"r"); while ((linesize = getline(&hashbuf,&tmpsize,fp)) != -1) { EVP_DigestUpdate(&ctx,hashbuf,linesize); /* hashes linesize bytes of data at hashbuf */ linesize = 0; } fclose(fp); hash = (unsigned char *)malloc(16); EVP_DigestFinal(&ctx,(unsigned char *)hash,NULL); /* retrieves the digest value */ make_hex(hash, hashbuf); printf("%s\n",hashbuf); free(hashbuf); free(hash); return; } /* MD5 * Hashing algorithm * md5cmp() compares the hash of two given files. */ void md5cmp(char *file1, char *file2) { FILE *fp = NULL; unsigned int tmpsize=0,linesize=0; char *hashbuf = NULL; unsigned char *hash1 = NULL, *hash2 = NULL; EVP_MD_CTX ctx; printf("Comparing MD5 hashes of '%s' and '%s' files.\n",\ file1,file2); EVP_DigestInit(&ctx,EVP_md5()); fp = fopen(file1,"r"); while ((linesize = getline(&hashbuf,&tmpsize,fp)) != -1) { EVP_DigestUpdate(&ctx,hashbuf,linesize); linesize = 0; } fclose(fp); hash1 = (unsigned char *)malloc(16); EVP_DigestFinal(&ctx,(unsigned char *)hash1,NULL); EVP_DigestInit(&ctx,EVP_md5()); /* initializes the digest context */ linesize = 0; tmpsize = 0; fp = fopen(file2, "r"); while ((linesize = getline(&hashbuf,&tmpsize,fp)) != -1) { EVP_DigestUpdate(&ctx,hashbuf,linesize); /* hashes linesize bytes of data at hashbuf */ linesize = 0; } fclose(fp); hash2 = (unsigned char *)malloc(16); EVP_DigestFinal(&ctx,(unsigned char *)hash2,NULL); /* retrieves the digest value */ if (strcmp(hash1,hash2) == 0) printf("MD5 hashes match.\n"); else printf("MD5 hashes don't match.\n"); free(hash1); free(hash2); free(hashbuf); return; } -------------------end hash.c------------------------------------------ -------------------misc.c---------------------------------------------- void help(char *name) { printf("UniSFED [Simple File Encrypter/Decrypter] v%s\n",\ VERSION); printf("Coded by Paolo Ardoino <paolo.ardoino@gmail.com>\n"); printf("Usage:\n"); printf("%s -h\t:displays this help\n",name); printf("%s -fh <file_to_hash>\t:performs the MD5 hash of \ file_to_hash.\n",name); printf("%s -fc <file1_to_cmp> <file2_to_cmp>\t:compares the \ MD5 hash of the two files.\n",name); printf("%s -e <-idea|-des|-bf|-rsa> <file_to_crypt> \ <file_output> [rsa_pub.pem]]\t:encrypts file_to_crypt with \ choosen cipher.\n",name); printf("%s -d <-idea|-des|-bf|-rsa> <file_to_decrypt> \ <file_output> [rsa_sec.pem]\t:decrypts file_to_decrypt with choosen cipher.\n",name); printf("%s -grsa <numbits> <secfile> <pubfile>\t:generates a \ RSA key pair.\n", name); printf("-idea: idea block cipher.\n"); printf(" -des: des block cipher.\n"); printf(" -bf: blowfish block cipher.\n"); printf("Ex: %s -e -bf passwords.txt crypto_pass.txt\n",name); printf("Ex: %s -d -bf crypto_pass.txt pass_file.txt\n",name); printf("Ex: %s -fc file1 file2\n",name); printf("\nPlease report bugs to <ardoino.gnu@disi.unige.it>\n"); return; } /* * fexists() checks for the existence of a given file. * return 0 if the file doesn't exist and 1 if it exists. */ int fexists(char *file) { FILE *fp; if ((fp = fopen(file,"r")) == NULL) { return 0; } else { fclose(fp); return 1; } } -------------------end misc.c------------------------------------------ -------------------rsa.c----------------------------------------------- #include <openssl/pem.h>
#include <openssl/err.h> #include <openssl/rsa.h> #define READPUB 0 #define READSEC 1 void rsa_ed(int mode, char *fin, char *fout, char *pemfile) { int size=0,len=0,ks=0; RSA *key=NULL; FILE *fpin=NULL, *fpout=NULL; unsigned char *cipher=NULL,*plain=NULL; if (strcmp(fin, fout) == 0) { fprintf(stderr,"Error: input and output files must not\ be the same file.\n"); exit(EXIT_FAILURE); } if (mode == 0) { fpin = fopen(fin, "r"); key = (RSA *)readpemkeys(READPUB, pemfile); fpout = fopen(fout, "w"); ks = RSA_size(key); plain = (unsigned char *)malloc(ks * \ sizeof(unsigned char)); cipher = (unsigned char*)malloc(ks * \ sizeof(unsigned char)); printf("Encrypting '%s' file.\n", fin); while(!feof(fpin)) { memset(plain,'\0',ks + 1); memset(cipher, '\0', ks + 1); len = fread(plain, 1, ks - 11, fpin); size = rsa_encrypt(key, plain, len, &cipher); fwrite(cipher, 1, size, fpout); } fclose(fpout); fclose(fpin); free(cipher); free(plain); RSA_free(key); printf("Done.\n"); } else if (mode == 1) { fpin = fopen(fin, "r"); key = (RSA *)readpemkeys(READSEC, pemfile); fpout = fopen(fout, "w"); ks = RSA_size(key); cipher = (unsigned char*)malloc(ks * \ sizeof(unsigned char)); plain = (unsigned char*)malloc(ks * \ sizeof(unsigned char)); printf("Decrypting '%s' file.\n", fin); while(!feof(fpin)) { memset(cipher, '\0', ks); memset(plain, '\0', ks); if ((len = fread(cipher, 1, ks, fpin)) == 0) break; size = rsa_decrypt(key, cipher, len, &plain); fwrite(plain, 1, size, fpout); } fclose(fpout); fclose(fpin); free(plain); free(cipher); RSA_free(key); printf("Done.\n"); } return; } void genkey(int size, char *secfile, char *pubfile) { RSA *key=NULL; FILE *fp; printf("Generating RSA keys[%d bits].\n", size); if (size < 64) { fprintf(stderr, "Error: RSA Key pair size too small.\n"); fprintf(stderr, "size >= 64\n"); exit(EXIT_FAILURE); } if((key = RSA_generate_key(size,3,NULL,NULL)) == NULL) { fprintf(stderr,"%s\n",ERR_error_string(ERR_get_error(),NULL)); exit(EXIT_FAILURE); } if(RSA_check_key(key) < 1) { fprintf(stderr,"Error: Problems while generating RSA Key.\n \ Retry.\n"); exit(EXIT_FAILURE); } fp=fopen(secfile,"w"); if(PEM_write_RSAPrivateKey(fp,key,NULL,NULL,0,0,NULL) == 0) { fprintf(stderr,"Error: problems while writing RSA Private \ Key.\n"); exit(EXIT_FAILURE); } fclose(fp); fp=fopen(pubfile,"w"); if(PEM_write_RSAPublicKey(fp,key) == 0) { fprintf(stderr,"Error: problems while writing RSA Public Key.\n"); exit(EXIT_FAILURE); } fclose(fp); RSA_free(key); printf("Done.\n"); return; } void* readpemkeys(int type, char *pemfile) { FILE *fp; RSA *key=NULL; if(type == READPUB) { if((fp = fopen(pemfile,"r")) == NULL) { fprintf(stderr,"Error: Public Key file doesn't exists.\n"); exit(EXIT_FAILURE); } if((key = PEM_read_RSAPublicKey(fp,NULL,NULL,NULL)) == NULL) { fprintf(stderr,"Error: problems while reading Public Key.\n"); exit(EXIT_FAILURE); } fclose(fp); return key; } if(type == READSEC) { if((fp = fopen(pemfile,"r")) == NULL) { fprintf(stderr,"Error: Private Key file doesn't exists.\n"); exit(EXIT_FAILURE); } if((key = PEM_read_RSAPrivateKey(fp,NULL,NULL,NULL)) == NULL) { fprintf(stderr,"Error: problmes while reading Private Key.\n"); exit(EXIT_FAILURE); } fclose(fp); if(RSA_check_key(key) == -1) { fprintf(stderr,"Error: Problems while reading RSA Private Key in \ '%s' file.\n",pemfile); exit(EXIT_FAILURE); } else if(RSA_check_key(key) == 0) { fprintf(stderr,"Error: Bad RSA Private Key readed in '%s' \ file.\n",pemfile); exit(EXIT_FAILURE); } else return key; } return key; } int rsa_encrypt(void *key, unsigned char *plain, int len, \ unsigned char **cipher) { int clen=0; srand(time(NULL)); if((clen = RSA_public_encrypt(len, plain, *cipher, (RSA*)key, \ RSA_PKCS1_PADDING)) == -1) { fprintf(stderr, "%s\n", ERR_error_string(ERR_get_error(), NULL)); exit(EXIT_FAILURE); } else return clen; } int rsa_decrypt(void *key, unsigned char *cipher, int len, \ unsigned char **plain) { int plen=0; if((plen = RSA_private_decrypt(len, cipher, *plain, (RSA*)key, \ RSA_PKCS1_PADDING)) == -1) { fprintf(stderr, "%s\n", ERR_error_string(ERR_get_error(), NULL)); exit(EXIT_FAILURE); } else return plen; } -------------------end rsa.c------------------------------------------- -------------------unisfed.c------------------------------------------- #include "unisfed.h" int main(int argc, char *argv[]) { if (argc == 1 || strcmp(argv[1],"-h") == 0 || \ ((strcmp(argv[1],"-e") != 0 && strcmp(argv[1],"-d") != 0 && \ strcmp(argv[1],"-fh") != 0 && strcmp(argv[1],"-fc") != 0 && \ strcmp(argv[1], "-grsa") != 0))) help(argv[0]); else if (strcmp(argv[1],"-d") == 0 && (argc == 5 || argc == 6) \ && (strcmp(argv[2],"-idea") == 0 ||strcmp(argv[2],"-des") == 0 \ || strcmp(argv[2],"-bf") == 0||strcmp(argv[2], "-rsa") == 0)){ \ if (strcmp(argv[2],"-idea") == 0) { if (fexists(argv[3]) == 1) idea_encrypt_decrypt(0,argv[3],argv[4]); else fprintf(stderr,"Error: input file not \ found.\n"); } else if (strcmp(argv[2],"-des") == 0) { if (fexists(argv[3]) == 1) des_encrypt_decrypt(0,argv[3],argv[4]); else fprintf(stderr,"Error: input file not \ found.\n"); } else if (strcmp(argv[2],"-bf") == 0) { if (fexists(argv[3]) == 1) bf_encrypt_decrypt(0,argv[3],argv[4]); else fprintf(stderr,"Error: input file not \ found.\n"); } else if (strcmp(argv[2],"-rsa") == 0) { if (fexists(argv[3]) == 1) rsa_ed(1, argv[3], argv[4], argv[5]); else fprintf(stderr,"Error: input file not \ found.\n"); } else; } else if (strcmp(argv[1],"-e") == 0 && \ (argc == 5 || argc == 6) && (strcmp(argv[2],"-idea") == 0 || \ strcmp(argv[2],"-des") == 0 || strcmp(argv[2],"-bf") == 0 || \ strcmp(argv[2], "-rsa") == 0)) { if (strcmp(argv[2],"-idea") == 0) { if (fexists(argv[3]) == 1) idea_encrypt_decrypt(1,argv[3], \ argv[4]); else fprintf(stderr,"Error: input file not \ found.\n"); } else if (strcmp(argv[2],"-des") == 0) { if (fexists(argv[3]) == 1) des_encrypt_decrypt(1,argv[3],argv[4]); else fprintf(stderr,"Error: input file not \ found.\n"); } else if (strcmp(argv[2],"-bf") == 0) { if (fexists(argv[3]) == 1) bf_encrypt_decrypt(1,argv[3],argv[4]); else fprintf(stderr,"Error: input file not \ found.\n"); } else if (strcmp(argv[2],"-rsa") == 0) { if (fexists(argv[3]) == 1) rsa_ed(0, argv[3], argv[4], argv[5]); else fprintf(stderr,"Error: input file not \ found.\n"); } else; } else if (strcmp(argv[1],"-fh") == 0 && argc == 3) { if (fexists(argv[2]) == 1) md5hash(argv[2]); else fprintf(stderr,"Error: input file not found.\n"); } else if (strcmp(argv[1],"-fc") == 0 && argc == 4) { if (fexists(argv[2]) == 1 && fexists(argv[3]) == 1) md5cmp(argv[2],argv[3]); else fprintf(stderr,"Error: input files not found.\n"); } else if (strcmp(argv[1], "-grsa") == 0 && argc == 5) genkey(atoi(argv[2]), argv[3], argv[4]); else help(argv[0]); return 0; } -------------------end unisfed.c--------------------------------------- -------------------unisfed.h------------------------------------------- #define _GNU_SOURCE #include <stdio.h>
#include <stdlib.h> #include <string.h> #define VERSION "0.1" /* Miscellaneous functions */ int fexists(char *file); void help(char *name); /* RSA fucntions */ void* readpemkeys(int type, char *pemfile); /*RSA*/ void genkey(int size, char *secfile, char *pubfile);/*RSA*/ int rsa_encrypt(void *key, unsigned char *plain, int len, \ unsigned char **cipher);/*RSA*/ int rsa_decrypt(void *key, unsigned char *cipher, int len, \ unsigned char **plain);/*RSA*/ /* Block ciphers fucntions */ void bf_encrypt_decrypt(int mode, char *filein, \ char *fileout); /* BlowFish */ void des_encrypt_decrypt(int mode, char *filein, \ char *fileout); /* DES */ void idea_encrypt_decrypt(int mode, char *filein, \ char *fileout); /* IDEA */ /* Hash functions */ void md5cmp(char *file1, char *file2); /* MD5 */ void md5hash(char *filein); /* MD5 */ #include "rsa.c" #include "block_ciphers.c" #include "hash.c" #include "misc.c" -------------------end unisfed.h---------------------------------------
|Archiver|手机版|小黑屋|软路由 ( 渝ICP备15001194号-1|渝公网安备 50011602500124号 )
GMT+8, 2024-5-20 16:20 , Processed in 0.038392 second(s), 5 queries , Gzip On, Redis On.
Powered by Discuz! X3.5 Licensed
© 2001-2023 Discuz! Team.