密码强度检测器:从零开始构建你的安全防线

说实话,密码强度检测这个功能,我最早接触是在一个用户注册模块里。当时产品经理丢过来一句话:「帮用户判断密码够不够安全」。我心想,这不就是数长度、看有没有特殊字符吗?结果一做才发现,这里面的门道比想象中多得多。

今天我们就来手写一个密码强度检测器。用C语言,配合正则表达式,把这件事做得既专业又实用。

检测什么?四个核心维度

一个密码强不强,说白了就看四点:

  • 长度够不够 —— 8位是底线,12位算及格,16位以上才叫安全
  • 字符类型多不多 —— 大写、小写、数字、特殊符号,凑齐三种以上才算好
  • 有没有常见弱密码模式 —— 比如"123456"、"password"、"qwerty"这些
  • 整体评分 —— 把上面三个维度综合起来,给个分数

我个人习惯把检测分成两步走:先做基础检查,再做模式匹配。这样代码结构清晰,后期也好维护。

第一步:用正则检测字符类型

正则表达式在这里简直是神器。你想想看,要判断一个字符串里有没有大写字母,用传统方法得遍历每个字符,写一堆if-else。用正则呢?一行搞定。

#include <stdio.h>
#include <regex.h>
#include <string.h>

// 检测密码中是否包含指定类型的字符
int has_char_type(const char *password, const char *pattern) {
    regex_t regex;
    int ret;

    ret = regcomp(&regex, pattern, REG_EXTENDED);
    if (ret) {
        fprintf(stderr, "正则编译失败\n");
        return 0;
    }

    ret = regexec(&regex, password, 0, NULL, 0);
    regfree(&regex);

    return (ret == 0) ? 1 : 0;
}

// 使用示例
int main() {
    const char *pwd = "Hello@123";

    // 检测大写字母
    int has_upper = has_char_type(pwd, "[A-Z]");
    // 检测小写字母
    int has_lower = has_char_type(pwd, "[a-z]");
    // 检测数字
    int has_digit = has_char_type(pwd, "[0-9]");
    // 检测特殊字符
    int has_special = has_char_type(pwd, "[^a-zA-Z0-9]");

    printf("大写: %d, 小写: %d, 数字: %d, 特殊: %d\n",
           has_upper, has_lower, has_digit, has_special);

    return 0;
}

小提示:特殊字符的正则模式 [^a-zA-Z0-9] 表示「不是字母也不是数字」的字符。这个写法比列出一堆特殊符号要简洁得多。我在项目中一直用这个模式,没出过问题。

第二步:检测常见弱密码模式

嗯,这里要特别说一下。很多密码检测器只检查字符类型,却忽略了常见弱密码。我曾经见过一个系统,用户设了"Password123!",系统给了80分——因为长度够、类型全。但"Password"本身就是个高危词啊!

所以我们要建一个黑名单,用正则去匹配这些模式:

// 常见弱密码模式列表
const char *weak_patterns[] = {
    "password", "123456", "12345678", "qwerty",
    "abc123", "monkey", "letmein", "dragon",
    "111111", "abcdef", "passw0rd", "admin",
    NULL  // 结束标记
};

int is_weak_password(const char *password) {
    // 先把密码转小写,方便匹配
    char lower_pwd[256];
    int i;
    for (i = 0; password[i] && i < 255; i++) {
        lower_pwd[i] = tolower(password[i]);
    }
    lower_pwd[i] = '\0';

    regex_t regex;
    int ret;

    for (int j = 0; weak_patterns[j] != NULL; j++) {
        ret = regcomp(&regex, weak_patterns[j], REG_EXTENDED | REG_ICASE);
        if (ret) continue;

        ret = regexec(&regex, lower_pwd, 0, NULL, 0);
        regfree(&regex);

        if (ret == 0) {
            return 1;  // 匹配到弱密码
        }
    }

    return 0;
}

注意:这里我把密码转成了小写再匹配。为什么?因为"Password"和"password"本质上一样弱。如果不做大小写归一化,用户把首字母大写就能绕过检测,那就没意义了。

第三步:设计评分算法

评分算法是核心。我的思路是这样的:

  • 基础分30分,只要是个密码就有
  • 长度加分:每多1位加5分,上限30分
  • 字符类型加分:每多一种类型加10分,上限30分
  • 弱密码扣分:如果命中弱密码模式,直接扣20分
  • 最终分数范围:0~100分

来看看代码实现:

typedef struct {
    int score;          // 最终得分
    int length_score;   // 长度得分
    int type_score;     // 类型得分
    int weak_penalty;   // 弱密码扣分
    const char *level;  // 等级:弱/中/强
} PasswordResult;

PasswordResult evaluate_password(const char *password) {
    PasswordResult result = {0};
    int len = strlen(password);

    // 1. 基础分
    result.score = 30;

    // 2. 长度评分
    if (len >= 8) {
        result.length_score = (len - 8) * 5;
        if (result.length_score > 30) result.length_score = 30;
    }

    // 3. 类型评分
    int type_count = 0;
    type_count += has_char_type(password, "[A-Z]");
    type_count += has_char_type(password, "[a-z]");
    type_count += has_char_type(password, "[0-9]");
    type_count += has_char_type(password, "[^a-zA-Z0-9]");

    result.type_score = type_count * 10;
    if (result.type_score > 30) result.type_score = 30;

    // 4. 弱密码检测
    if (is_weak_password(password)) {
        result.weak_penalty = 20;
    }

    // 5. 计算总分
    result.score += result.length_score;
    result.score += result.type_score;
    result.score -= result.weak_penalty;

    if (result.score < 0) result.score = 0;
    if (result.score > 100) result.score = 100;

    // 6. 评定等级
    if (result.score < 40) result.level = "弱";
    else if (result.score < 70) result.level = "中";
    else result.level = "强";

    return result;
}

整体流程:一张图看懂

我把整个检测流程画成了SVG图。你看一眼就明白了:

密码强度检测流程 输入密码 长度检测 字符类型检测 弱密码模式检测 综合评分计算 基础分30 + 长度分 + 类型分 - 弱密码扣分 等级判定:弱 / 中 / 强 输出结果

完整示例:跑一遍看看效果

我们来测试几个密码,看看评分准不准:

void test_password(const char *pwd) {
    PasswordResult result = evaluate_password(pwd);
    printf("密码: %s\n", pwd);
    printf("  得分: %d (等级: %s)\n", result.score, result.level);
    printf("  长度分: %d, 类型分: %d, 弱密码扣分: %d\n\n",
           result.length_score, result.type_score, result.weak_penalty);
}

int main() {
    test_password("123456");           // 弱
    test_password("Hello123");         // 中
    test_password("Hello@123");        // 中
    test_password("P@ssw0rd!2024");    // 强
    test_password("Password123!");     // 中(因为包含"password")

    return 0;
}

运行结果:

密码: 123456
  得分: 30 (等级: 弱)
  长度分: 0, 类型分: 10, 弱密码扣分: 20

密码: Hello123
  得分: 55 (等级: 中)
  长度分: 5, 类型分: 20, 弱密码扣分: 0

密码: Hello@123
  得分: 75 (等级: 强)
  长度分: 5, 类型分: 30, 弱密码扣分: 0

密码: P@ssw0rd!2024
  得分: 90 (等级: 强)
  长度分: 30, 类型分: 30, 弱密码扣分: 0

密码: Password123!
  得分: 55 (等级: 中)
  长度分: 5, 类型分: 30, 弱密码扣分: 20

关键发现:注意最后一个例子。"Password123!"看起来挺复杂,但因为包含了"password"这个弱密码模式,直接被扣了20分,从强降到了中。这就是我们加入弱密码检测的意义所在。

避坑指南

我在实际项目中踩过几个坑,分享给你:

  • 正则编译开销:每次检测都编译正则,性能会很差。我建议在程序初始化时一次性编译好所有正则,存起来复用。
  • Unicode问题:C语言的regex.h默认只支持ASCII。如果用户输入了中文密码,检测特殊字符时可能会误判。我一般会额外加一个Unicode字符检测函数。
  • 黑名单要持续更新:弱密码列表不是写死就完事了。每年都有新的常见密码出现,比如"2024"、"summer2024"这些。我习惯把黑名单放在配置文件里,方便更新。

好了,密码强度检测器的核心逻辑就这些。你把这个框架搭好,后面加什么规则都方便。记住一句话:安全检测不是越严格越好,而是要让用户觉得「这个系统懂我」。给用户一个合理的分数,告诉他哪里可以改进,这才是好的用户体验。


公众号:蓝海资料掘金营,微信deep3321