我正在从事这个c项目,该项目处理用户注册和登录程序。问题是该程序成功,但仅对一个用户有效。你们能解释什么地方错了,我应该改变什么?预先感谢。
P.S 这是我在stackoverflow中的第一个问题
英语不是我的主要语言
无论如何,下面是我在C语言中的代码:
#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static int i=0;
struct w
{
char nama[30],pass[30];
}
w[100];
int n;
void login(void);
void reg(void);
int main(void)
{
menu:
system("cls");
printf("\n\n\n\n\n\t\t\t\tWELCOME!");
printf("\n\n\n\n\t\t\t\t\t\t[ENTER]");
if(getch()==13)
{
system("cls");
}
else
{
goto menu;
}
menu_main:
printf("\n\n\n\t\t\t[1] LOGIN\t\t[2] REGISTRATION\t\t[3] EXIT APP");
printf("\n\n\n\t\t\t\t INPUT YOUR SELECTION THEN PRESS [ENTER]: ");
scanf("%d",&n);
switch(n)
{
case 1: system("cls");
login();
break;
case 2: system("cls");
reg();
break;
case 3:system("cls");
printf("\n\n\t\t\t\tTHANK YOU FOR USING THIS APP\n");
break;
default: system("cls"); printf("\n\n\t\t\t\tNOT AVAILABLE");
printf("\n\n\t\t\tPRESS ANY KEY TO GO BACK");
getch();
system("cls"); goto menu_main;
}
}
void reg()
{
FILE *fp;
char c,username[30]; int z=0;
fp=fopen("file.txt","ab+");
system("cls");
printf("\n\n\t\tPLEASE INPUT USERNAME & PASSWORD");
for(i=0;i<99;i++)
{
printf("\n\n\t\t\t\t USERNAME: ");
scanf("%s",username);
while(!feof(fp))
{
fread(&w[i],sizeof(w[i]),1,fp);
if(strcmp(username,w[i].nama)==0)
{
system("cls");
printf("\n\n\t\t\t USERNAME IS NOT AVAILABLE");
printf("\n\n\t\t\t PRESS ANY KEY TO GO BACK");
getch();
system("cls"); reg();
}
else
{
strcpy(w[i].nama,username);
break;
}
}
z=0;
printf("\n\n\t\t\t\t PASSWORD: ");
while((c=getch())!=13)
{
w[i].pass[z++]=c;
printf("%c",'*');
}
fwrite(&w[i],sizeof(w[i]),1,fp);
fclose(fp);
printf("\n\n\tPRESS [ENTER] IF YOU AGREE");
if((c=getch())==13)
{
system("cls");
printf("\n\n\t\tYOU ARE REGISTERED!");
printf("\n\n\t\tWOULD YOU LIKE TO LOGIN?\n\n\t\t ");
printf(" PRESS [1] FOR YES\n\n\t\t PRESS [2] FOR NO\n\n\t\t\t\t");
scanf("%d",&n);
if(n==1)
{
system("cls");
login();
}
else if(n==2)
{
system("cls");
printf("\n\n\n\t\t\t\tTHANK YOU FOR REGISTERING IN THIS APP\n");
}
}
break;
}
}
void login()
{
FILE *fp;
char c,nama[10],pass[10]; int z=0;
int cekun,cekpw;
fp=fopen("file.txt","r");
for(i=0;i<=10;i++)
{
printf("\n\n\t\t\t\t USERNAME: ");
scanf("%s",nama);
system("cls");
printf("\n\n\t\t\t\t PASSWORD: ");
while((c=getch())!=13)
{
pass[z++]=c;
printf("%c",'*');
}
pass[z]='\0';
while(!feof(fp))
{
fread(&w[i],sizeof(w[i]),1,fp);
cekun=strcmp(nama,w[i].nama);
cekpw=strcmp(pass,w[i].pass);
if(cekun==0&&cekpw==0)
{
system("cls");
printf("\n\n\n\t\t\tLOGIN SUCCESSFUL!");
break;
}
else if(cekun==0)
{
printf("\n\n\n\t\t\tWRONG PASSWORD!");
printf("\n\n\t\t\t\t (PRESS [Y] TO RE-LOGIN)");
if(getch()=='y'||getch()=='Y')
system("cls"); login();
}
else if(cekun!=0&&cekpw!=0)
{
h:
printf("\n\n\n\t\t\tYOU ARE NOT REGISTERED\n \t\t\tPRESS [ENTER] TO REGISTER");
if(getch()==13)
system("cls"); reg();
}
else if(cekun!=0&&cekpw==0)
{
goto h;
}
}
break;
}
getch();
}
答案 0 :(得分:0)
不适用于多个用户的一个原因是myFormData.append('myJSObject', JSON.stringify(myObject));
var myFile = document.getElementById('myinput').files[0];
myFormData.append('myfile', myFile);
函数的逻辑结构。
$.ajax({
type: "POST",
url: "/SomeController",
async:false,
data: myFormData,
contentType: "application/json; charset=utf-8",
dataType: "json",
该代码将拒绝任何登录尝试,除非用户名和密码与文件中的第一个条目 匹配。
相反,您应该实现以下逻辑:
login
答案 1 :(得分:0)
与其编写提示和检查功能,不如在单独的功能中进行检查。
例如,假设您使用
#define MAX_NAME_LEN 31
#define MAX_PASS_LEN 31
typedef struct {
char name[MAX_NAME_LEN + 1];
char pass[MAX_PASS_LEN + 1];
} userinfo;
存储有关已知用户的信息。您的用户数据库可能就是
#define MAX_USERS 100
static userinfo known_user[MAX_USERS];
static size_t known_users = 0;
要注册新用户,请编写一个以用户名和密码作为参数的函数:
int register_user(const char *user, const char *pass)
{
const size_t userlen = (user) ? strlen(user) : 0;
const size_t passlen = (pass) ? strlen(pass) : 0;
size_t i;
if (userlen < 1)
return -1; /* Empty username */
if (userlen > MAX_NAME_LEN)
return -2; /* Username is too long */
if (passlen < 1)
return -3; /* Empty password */
if (passlen > MAX_PASS_LEN)
return -4; /* Password is too long */
/* Check if this user is already known. */
for (i = 0; i < known_users; i++)
if (!strcmp(user, known_user[i].name))
return -5; /* Username is already registered. */
if (known_users >= MAX_USERS)
return -6; /* User database is full. */
/* Add this user to the user database. */
strncpy(known_user[known_users].name, user, MAX_NAME_LEN + 1);
strncpy(known_user[known_users].pass, pass, MAX_PASS_LEN + 1);
known_users++;
return 0; /* Success */
}
请注意,(user) ? strlen(user) : 0
是三元表达式。这意味着如果user
不为NULL,则表达式的计算结果为strlen(user)
,否则它的计算结果为0。(是的,它类似于if
子句的紧凑形式。)经常看到strlen()
像这样包装,因为strlen(NULL)
不安全,并且可能使程序崩溃:根据C标准,它被可怕地 Undefined Behaviour 。 (他们说 UB 可能会导致守护程序从您的鼻子中飞出,但是我认为这是一个白胡子使我们认真对待 UB 的恐怖故事。)
(!strcmp(...))
与(strcmp(...) == 0)
等效,如果两个字符串完全匹配,则为true。
类似地,要检查用户名和密码对是否与已知用户匹配,
int verify_user(const char *user, const char *pass)
{
const size_t userlen = (user) ? strlen(user) : 0;
const size_t passlen = (pass) ? strlen(pass) : 0;
size_t i;
if (userlen < 1)
return -1; /* Empty username */
if (userlen > MAX_NAME_LEN)
return -2; /* Username is too long */
if (passlen < 1)
return -3; /* Empty password */
if (passlen > MAX_PASS_LEN)
return -4; /* Password is too long */
for (i = 0; i < known_users; i++)
if (!strcmp(user, known_user[i].name)) {
if (!strcmp(pass, known_user[i].pass))
return 0; /* Valid user */
else
return -6; /* Wrong password */
}
return -7; /* No such user. */
}
这时,您可以编写一个小的测试程序,并测试上述功能是否有效。这称为unit testing:
int main(void)
{
int result;
result = register_user("foo", "bar");
if (result) {
fprintf(stderr, "register_user(\"foo\", \"bar\") failed: %d.\n", result);
exit(EXIT_FAILURE);
} else
fprintf(stderr, "register_user(\"foo\", \"bar\") worked as expected (0).\n");
result = register_user("user", "pass");
if (result) {
fprintf(stderr, "register_user(\"user\", \"pass\") failed: %d.\n", result);
exit(EXIT_FAILURE);
} else
fprintf(stderr, "register_user(\"user\", \"pass\") worked as expected (0).\n");
result = register_user("foo", "irrelevant");
if (result) {
fprintf(stderr, "register_user(\"foo\", \"irrelevant\") failed. Expected -5, received %d.\n", result);
exit(EXIT_FAILURE);
} else
fprintf(stderr, "register_user(\"foo\", \"irrelevant\") worked as expected (-5).\n");
result = verify_user("no such", "user or password");
if (result != -7) {
fprintf(stderr, "verify_user(\"no such\", \"user or password\") failed. Expected -7, received %d.\n", result);
exit(EXIT_FAILURE);
} else
fprintf(stderr, "verify_user(\"no such\", \"user or password\") worked as expected (-7).\n");
result = verify_user("foo", "wrong password");
if (result != -6) {
fprintf(stderr, "verify_user(\"foo\", \"wrong password\") failed. Expected -6, received %d.\n", result);
exit(EXIT_FAILURE);
} else
fprintf(stderr, "verify_user(\"foo\", \"wrong password\") worked as expected (-6).\n");
result = verify_user("foo", "bar");
if (result != 0) {
fprintf(stderr, "verify_user(\"foo\", \"bar\") failed. Expected 0, received %d.\n", result);
exit(EXIT_FAILURE);
} else
fprintf(stderr, "verify_user(\"foo\", \"bar\") worked as expected (0).\n");
printf("All tests successful.\n");
return EXIT_SUCCESS;
}
当您精通编程时,可以使用多种方法“自动”进行此类测试。
仅当您的测试程序可以正确进行测试(并同时测试正确和不正确的输入及其结果!)时,才继续解决原始问题。
这样,您知道这两个功能都可以工作,并且知道任何问题或错误都必须在其他地方。 (如果不确定,可以将该输入添加到单元测试程序中,然后进行验证!)
以纯文本存储密码不是一个好主意。曾经您也不需要这样做。
您应该做的是使用hash function将密码转换为大量密码;哈希函数应该是密码学上安全的。密码也应为salted:随机数或字符串,以纯文本格式保存,并在密码之前(或附加或同时附加),并提供给哈希函数。为了使任何字典攻击变慢,通常会大量应用散列函数。这个数字可以变化,但是固定的回合数通常同样安全。
在这种情况下,用户信息类似于
#define MAX_NAME_LEN 31
#define HASH_SIZE 64 /* for 512-bit hashes like SHA512 */
#define SALT_SIZE 16 /* for 128-bit salt */
#define HASH_ROUNDS 5000
typedef struct {
char name[MAX_NAME_LEN + 1];
unsigned char salt[SALT_SIZE];
unsigned char hash[HASH_SIZE];
} userinfo;
假设我们可以使用的加密安全哈希函数声明为
void crypto_hash(unsigned char *hash, const void *data, const size_t size);
,我们有一个加密安全的随机数生成器,当需要使用时会生成一个随机盐
void crypto_salt(unsigned char *salt);
然后我们的用户注册功能变为
int register_user(const char *user, const char *pass)
{
const size_t userlen = (user) ? strlen(user) : 0;
const size_t passlen = (pass) ? strlen(pass) : 0;
unsigned char *temp;
unsigned char hash[2][HASH_SIZE];
size_t i;
if (userlen < 1)
return -1; /* Empty username */
if (userlen > MAX_NAME_LEN)
return -2; /* Username is too long */
if (passlen < 1)
return -3; /* Empty password */
/* TODO: Password strength checking! */
/* Check if this user is already known. */
for (i = 0; i < known_users; i++)
if (!strcmp(user, known_user[i].name))
return -5; /* Username is already registered. */
if (known_users >= MAX_USERS)
return -6; /* User database is full. */
/* Generate a random salt for this new user. */
crypto_salt(known_user[i].salt);
/* Allocate a temporary buffer for the salt and the password.
Here, for simplicity, we just prepend the salt to the password,
and include the string-terminating nul byte, '\0'.
This is to protect against a certain type of tail attacks. */
temp = malloc(SALT_SIZE + pass_len + 1);
if (!temp)
return -8; /* Not enough memory. */
/* Combine the salt and the password, including '\0' at end. */
memcpy(temp, known_user[known_users].salt, SALT_SIZE);
memcpy(temp + SALT_SIZE, pass, passlen + 1);
/* First hashing round. */
crypto_hash(hash[0], temp, SALT_SIZE + passlen + 1);
/* The temporary buffer is no longer needed.
We wipe, then free it. */
memset(temp, 0, SALT_SIZE + passlen + 1);
free(temp);
/* Repeated rounds. */
for (i = 0; i < HASH_ROUNDS - 2; i++)
crypto_hash(hash[(i+1) & 1], hash[i & 1], HASH_SIZE);
/* Final round. Since i was incremented after the last
crypto_hash() call, the last hash is in hash[i & 1]. */
crypto_hash(known_user[known_users].hash, hash[i & 1], HASH_SIZE);
/* Wipe the temporary hash table. */
memset(hash[0], 0, HASH_SIZE);
memset(hash[1], 0, HASH_SIZE);
/* Add this user to the user database. */
strncpy(known_user[known_users].name, user, MAX_NAME_LEN + 1);
known_users++;
return 0; /* Success */
}
使用了两个临时哈希槽,因为通常crypto_hash()
函数期望结果位于源之外的其他地方。
用户名和密码检查变为
int verify_user(const char *user, const char *pass)
{
const size_t userlen = (user) ? strlen(user) : 0;
const size_t passlen = (pass) ? strlen(pass) : 0;
unsigned char *temp;
unsigned char hash[2][HASH_SIZE];
size_t i, u;
int result;
if (userlen < 1)
return -1; /* Empty username */
if (userlen > MAX_NAME_LEN)
return -2; /* Username is too long */
if (passlen < 1)
return -3; /* Empty password */
for (u = 0; i < known_users; i++)
if (!strcmp(user, known_user[u].name))
break; /* u will be less than known_users */
if (u >= known_users)
return -7; /* No such user */
/* known_user[u] matches the name. */
/* Allocate a temporary area for combining this users' salt
and the supplied password. */
temp = malloc(HASH_SIZE + passlen + 1);
if (!temp)
return -8; /* Out of memory. */
/* Combine the salt and the password the same way as when
the user and password was registered. */
memcpy(temp, known_user[u].salt, SALT_SIZE);
memcpy(temp + SALT_SIZE, pass, passlen + 1);
/* Calculate the hash the same way. First pass: */
crypto_hash(hash[0], temp, SALT_SIZE + passlen + 1);
/* Temporary buffer is no longer needed, so clear and free it. */
memset(temp, 0, SALT_SIZE + passlen + 1);
free(temp);
/* Repeated rounds, including the final round. */
for (i = 0; i < HASH_ROUNDS - 1; i++)
crypto_hash(hash[(i+1) & 1], hash[i & 1], HASH_SIZE);
/* Because i was incremented after the last crypto_hash() call,
hash[i & 1] is where the final hash is. Compare. */
if (!memcmp(hash[i & 1], known_user[u].hash))
result = 0; /* Valid password for the user! */
else
result = -6; /* Wrong password. */
/* Wipe the temporary hash buffer. */
memset(hash[0], 0, HASH_SIZE);
memset(hash[1], 0, HASH_SIZE);
return result;
}
在实际应用中,最好不要区分“未知用户” 和“错误密码” ,因为知道哪些用户名是有效的,可以使某些攻击更容易。