我正在编写一个简单的程序,该程序会生成一定长度的随机密码,带有或不带有特殊字符,只是为了学习C语言。最后,根据以下输出,我可以很好地执行此操作:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>


char *generate_random_password(int password_lenght, int has_special_characters)
{
    const char *letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    const char *digits = "0123456789";
    const char *special_characters = "!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~";

    char *random_password = malloc(sizeof(char) * (password_lenght+1));

    srandom(time(NULL));

    if(has_special_characters)
    {
        char to_be_used[95] = "q4312078q";

        strcat(to_be_used, letters);
        strcat(to_be_used, digits);
        strcat(to_be_used, special_characters);

        for(int i = 0; i < password_lenght; i++)
        {
            const int random_index = random() % strlen(to_be_used);
            const char random_character = to_be_used[random_index];

            random_password[i] = random_character;
        }
    }
    else
    {
        char to_be_used[63] = "q4312078q";

        strcat(to_be_used, letters);
        strcat(to_be_used, digits);

        for(int i = 0; i < password_lenght; i++)
        {
            const int random_index = random() % strlen(to_be_used);
            const char random_character = to_be_used[random_index];

            random_password[i] = random_character;
        }
    }

        return random_password;

        free(random_password);
}


int main(void)
{
    printf("%s\n", generate_random_password(17, 1));
    printf("%s\n", generate_random_password(17, 0));

    return 0;
}


输出为:

 |ZzN>^5}8:i-P8197

vPrbfzBEGzmSdaPPP
 


它正在工作!

但是我完全怀疑这些字符串,指针,字符数组等。我不知道这是否写成“正确的方式”,或者怎么会更好。我担心是否为每个字符串/字符数组分配了正确的数量,并且它是否有可能在将来崩溃或崩溃。

PS:我是C编程新手,所以我不这样做

如果对指针和内存管理了解不多,请给我一些反馈,我将不胜感激!

评论

欢迎来到CodeReview,特立独行。由于使用了随机性和随机性,您的代码当前不独立于平台。如果需要这样做,请编辑您的帖子以包括您的目标和主机编程平台,因为审阅者可以添加依赖平台的注释。您也可以添加相关标签。如果您打算创建独立于平台的代码,请随时编辑您的帖子以表明这一点。

@Mast:此版本确实具有SO注释中指出的一些错误修复。 (例如,to_be_used现在在第一个strcat读取它之前被初始化。我本来会使用strcpy而不是将堆栈上的该本地清零,然后再使用strcat。或者实际上我会完成@Baldrickk的建议而不进行任何复制)。但是无论如何,这是一个修正了一些错误的重新发布。我想仍然是一个十字路口,但我想知道为什么没人在SO评论中提到这些事情)

我知道您只是为了运动而做,但是请看为什么我们不应该自己滚?在安全堆栈上。

@MooingDuck:我在双引号引起来的字符串中看到一个双反斜杠\\,所以字符文字中有一个反斜杠。还是您在输入空格作为特殊字符?

@PeterCordes:我发誓我在发布前检查了三遍。但是它似乎没有被编辑,所以我一定失去了主意

#1 楼

错字

lenght的拼写是length

魔术数字

95代表什么?您需要将其放在命名为#defineconst中。

分配失败

调用malloc后,请始终检查是否为您提供了非空指针。分配失败确实发生在现实生活中。

缩进

您将要通过自动格式化程序运行此操作,因为您的if块具有奇特的缩进,并且需要在右侧添加更多列。

无法访问的语句


    return random_password;

    free(random_password);


永远不会调用此free;删除它。

Random

该程序的更大概念问题是它使用了密码学上很弱的伪随机数生成器。这是一个很大且相当复杂的主题,因此您需要做一些阅读,但是从熵控制的系统源中请求随机数据已经比使用C rand更好。

:您没有调用rand,而是在调用random


random()函数使用非线性加法反馈随机数生成器,该生成器使用大小为31个长整数的默认表返回连续的伪随机数,范围为0到RAND_MAX。此随机数生成器的周期非常大,大约为16 *((2 ^ 31)-1)。


它可能不适用于加密目的。请仔细阅读以下内容:

https://stackoverflow.com/questions/822323/how-to-generate-a-random-int-in-c/39475626#39475626

评论


\ $ \ begingroup \ $
感谢您的接受,但我建议您在决定接受的答案之前,不接受,投票并等待其他用户提出其他建议。
\ $ \ endgroup \ $
– Reinderien
19-09-30在20:38

\ $ \ begingroup \ $
return终止该函数,因此将不执行任何操作。但是您可以正确地识别出通常应该释放分配的内存。在这种情况下,这样做是主要的责任。
\ $ \ endgroup \ $
– Reinderien
19-09-30在20:44

\ $ \ begingroup \ $
@PeterJennings密钥和密码的生成绝对是需要加密强度的活动。考虑到密码生成只需要很少的数据并且只需要发生一次,那么边际增加的成本和复杂性就值得了,即使它将密码攻击从“不可行”变为“极不可行”。
\ $ \ endgroup \ $
– Reinderien
19-10-1的1:39

\ $ \ begingroup \ $
@PeterJennings srandom函数用时间的返回值初始化。如果我们知道生成密码的年份,那么可能的密码少于3200万。对于“知道精确的随机算法不会帮助您破解密码”,您也错了-有很多密码,即使初始种子足够,使用随机密码也无法生成。
\ $ \ endgroup \ $
–马丁·邦纳(Martin Bonner)支持莫妮卡(Monica)
19年10月1日在9:44

\ $ \ begingroup \ $
使用srandom(time(NULL))显然是限制性的错误;如果在服务器端使用,它将为攻击者打开仓库大门,要求密码重设,并在成功之前尝试几种选择。客户端或服务器端,这意味着立即进行新的注册。最好选择一个更好的熵源,即使您不求助于OS /硬件源,但是,鉴于将很大范围的rand映射到很小的符号范围,除非使用lib实现,否则单独使用rand并不是问题。兰德的身分严重受损。密码生成器的意义只是合理的熵,而不是最大值。
\ $ \ endgroup \ $
– SilverbackNet
19-10-2在4:07

#2 楼

在您的代码有效的同时,您可以尝试进行多种简化。


如Reinderien所说,摆脱“魔术”数字包含所有95个字符的字符串,最后带有特殊字符。这将消除所有strcat代码。
has_special_characters声明为bool类型是一个好习惯。您必须包含<stdbool.h>
然后可以对其进行测试,以将整数变量modulus_divider设置为与1)中相同的正确const#define值。
然后可以获取随机数的模数使用modulus_divider时,您无需继续使用strlen(to_be_used),只需要一个生成循环。

您实际上并不需要for循环中的所有中间变量。假设您已将char_set设置为2中的完整94个字符数组,则整个for循环可能变为:

for(int i = 0; i < password_lenght; i++)
{
    random_password[i] = char_set[random() % modulus_divider];
}



我并不是说这是完美的,但这是我的版本。我目前尚未安装C编译器,但是它可以通过onlinegdb.com上的在线编译器进行编译并运行。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdbool.h>

char *generate_random_password(const int password_length, bool has_special_characters)
{
    const int alphamerics = 64; /* length of alphameric character set */
    const int alphamerics_plus = 94; /* length of alphameric character set plus special chatacters */
    const char character_set[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"; /* full character set */
    int n =  has_special_characters ? alphamerics_plus : alphamerics; /* set the length of character_set to be used */

    char *random_password = malloc(sizeof(char) * (password_length + 1)); /* allocate memory for password */

    srandom(time(NULL)); /* initialise random number generator, srandom(clock()); works just as well*/
    for (int i = 0; i < password_length; i++)
    {
        random_password[i] = character_set[random() % n]; /* get a character from character_set indexed by the remainder when random() os divided by n */
    }
    random_password[password_length] = '
W$Mg-tT?oTwa~EF$S
xGLMrqJBS6IB96xvp 
'; /* append terminating null to string */ return random_password; } int main(void) { printf("%s\n", generate_random_password(17, true)); printf("%s\n", generate_random_password(17, false)); return 0; }


典型输出为

q4312078q

评论


\ $ \ begingroup \ $
为什么不评论变量的描述,为什么不使用描述性名称?编译器不在乎该变量是否具有长名称,例如alphanumerics_plus_special_characters或length_of_characters_to_be_used。
\ $ \ endgroup \ $
–dustytrash
19年10月1日在15:39

\ $ \ begingroup \ $
@dustytrash在某种程度上,这是个人喜好问题。长变量名是双刃武器。是的,它们具有描述性,但是它们可能变得笨拙,并使代码行过长且难以阅读。我的看法是使用简短但描述性的名称,并在注释中扩展其使用以消除任何疑问。至于评论,我是老学校。我从事C语言的开发工作已超过35年(Kernighan和Ritchie是我的课本),并且始终坚持注释最重要的几行以帮助下一个需要编辑代码的人的想法。
\ $ \ endgroup \ $
– Peter Jennings
19年10月1日在22:18

\ $ \ begingroup \ $
@dustytrash我承认'n'而不是'length_of_characters_to_be_used'简直就是笔误!至少应该更具描述性。我对这些评论持开放态度的另一个原因是,它可以帮助学习者OP理解代码。
\ $ \ endgroup \ $
– Peter Jennings
19年10月1日在22:25

\ $ \ begingroup \ $
建议的代码泄漏内存
\ $ \ endgroup \ $
–鸭鸭
19-10-2在23:14

\ $ \ begingroup \ $
@MooingDuck我从未说过这是完美的。我喜欢Ronald关于在调用函数(主函数)中声明内存并传递指针的想法的一些变体。那应该解决它。如果这是一个完整的密码生成器,则您将使用用户输入的密码长度,分配内存,调用此函数,并在使用完密码后在调用函数中释放内存,
\ $ \ endgroup \ $
– Peter Jennings
19-10-3在0:13

#3 楼

如果您只需要调用代码将密码存储空间传递给您,那么您就可以摆脱所有复杂的内存分配。看起来可能像这样:

 #include <stdio.h>
#include <stdlib.h>

void generate_password(char *password, size_t password_size) {
    for (size_t i = 0; i < password_size - 1; i++) {
        password[i] = alphabet[rnd_int(alphabet_len)];
    }
    password[password_size - 1] = 'char *';
}
 


char *的意思是“指向字符的指针” 。在C语言中,指向字符的指针也可以表示“指向字符的指针和超出范围的内存”。这通常用于指代字符串。然后,''0''指向字符串的第一个字符,并且字符串继续直到到达字符alphabet为止,该字符为二进制0。请勿与字符alphabet_len混淆,后者是数字零。

当然,在上面的代码中未声明变量rnd_int[0, n)。与int main(void) { char password[80]; generate_password(password, sizeof password); fprintf("password: %s\n", password); } 函数相同,该函数从q4312079q范围内生成一个随机数。

代码将被这样调用:

 q4312079q 


评论


\ $ \ begingroup \ $
这是一个很好的观点;在不需要合同的地方创建合同是一个坏主意。期望调用者总是忘记并泄漏内存,或者更糟的是使用错误的免费类型,即使您告诉他们调用deallocate_password()。也就是说,出于安全原因,可能需要调用以从内存中擦除密码。
\ $ \ endgroup \ $
– SilverbackNet
19年10月2日,下午3:16

\ $ \ begingroup \ $
第一行中的意思是“无”吗?
\ $ \ endgroup \ $
–巴尔德里克
19-10-2在15:02

\ $ \ begingroup \ $
@Baldrickk谢谢
\ $ \ endgroup \ $
–罗兰·伊利格(Roland Illig)
19-10-2在15:36

#4 楼

它已被触及(例如,在Peter的示例中固定),但没有人明确声明-但是对我来说,最明显的问题是代码重复。

您有以下if语句:

if(has_special_characters)
{
  //codeblock 1
}
else
{
  //codeblock 2
}


其中codeblock 1codeblock 2几乎完全相同。实际上,似乎唯一的区别是您在codeblock 1中具有以下行:

strcat(to_be_used, special_characters);


您可以完全删除重复的代码,并将该行仅包装在if中块。

尽管如此,我也建议使用Peter的第二点,而不是完全不使用strcat。您可以从头开始将所有字符放入一个字符串中,并使用if确定将覆盖的范围:

//adjacent strings are concatenated by the compiler
const char* characters = "abcdefghijklmnopqrstuvwxyz"
                         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                         "0123456789"
                         "!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~";
const unsigned int alphanumerics_len = 62;
const unsigned int all_character_len = 96;

int char_range_max;
if (has_special_characters)
{
  char_range_max = all_character_len;
} 
else
{
  char_range_max = alphanumerics_len;
}

//...intermediate code

const int random_index = random() % char_range_max;

//...more code



然后我们可以通过以下方法进一步改进让编译器通过一些预处理器为我们处理字符串长度,以防止需要重复的内容:

-block更加简洁:

#define AN "abcdefghijklmnopqrstuvwxyz"\
           "ABCDEFGHIJKLMNOPQRSTUVWXYZ"\
           "0123456789"
#define SP "!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"
const int alphanumerics_len = sizeof (AN);
const int all_character_len = sizeof (AN SP);
const char* characters = AN SP;


也具有可以定义为const的优点。

评论


\ $ \ begingroup \ $
太糟糕的C使得将标签部分放置在字符串中并不容易,因此您可以让编译器为您计算alphanumerics_len。在汇编语言中,这很容易:只需在这些字节之后放置一个标签,并在字符串的末尾放置另一个标签,因此您可以执行end1-start或end2-start而不是对任何长度进行硬编码。 (您可能已经使用了一个数组,因此您至少可以对all_character_len使用sizeof。)
\ $ \ endgroup \ $
– Peter Cordes
19-10-2在14:54

\ $ \ begingroup \ $
如果将特殊字符放在首位,则可以使用strchr(characters,'a')查找新的起始位置(智能编译器可以优化指向+ =常量的指针)。但是,以相同的起点选择不同的长度是最有效的。我想如果您想避免与字符串内容匹配的硬编码长度,则可以在运行时进行strrchr。如果使用数组,则为memrchr,因此总长度为编译时常数。
\ $ \ endgroup \ $
– Peter Cordes
19-10-2在14:57



\ $ \ begingroup \ $
@PeterCordes您可以使用sizeof(请参阅stackoverflow.com/a/5022113/4022608),但只能找到最大长度,因此无论如何您都需要一个硬编码的值...更有创造力的方法可以做到,但可能会影响可读性或内存使用。我可以想到使用预处理器的可读替代方法,但是它将依赖于编译器优化未使用数组的创建,这有点脏。
\ $ \ endgroup \ $
–巴尔德里克
19-10-2在15:01

\ $ \ begingroup \ $
那就是我说的:您可以将sizeof用于all_character_len,但不能用于alphanumerics_len。好主意;使用宏,您可以连接所有用于实际定义的内容,或者仅连接sizeof(“ abcdefg ...”)的字母数字部分作为编译时常数整数。是的,它可以工作:godbolt.org/z/ikLz_3我认为我必须将其转换为匿名数组或其他内容。 (仅将字符串文字用作sizeof的操作数并不会使它成为程序的一部分,因此甚至不需要“优化”它)哦,链接到Q上的另一个答案指出了这一点。
\ $ \ endgroup \ $
– Peter Cordes
19-10-2在15:08



\ $ \ begingroup \ $
是的,我将其放入。如果您添加了另一个特殊字符,则对长度进行硬编码需要更新2件事,因此,对此一无所知,并且通常仅出于性能方面的考虑是合理的。推荐硬编码长度/幻数的答案对于编码样式不是很好。由于宏允许我们获得没有硬编码长度的同样有效的机器代码,因此我们应该使用它。 (此外,读起来并不比运行时strcat + strlen差)。您的方法(选择不同的长度)显然更优雅,并且不会冒犯读者的效率感。
\ $ \ endgroup \ $
– Peter Cordes
19-10-2在15:43