0x00 一句话
如果想直接使用解决方案的话,可以参考下列代码即可实现。
下面的正则表达式设置了5个规则。
- 必须包含一个小写字母。
- 必须包含一个大写字母。
- 必须包含一个数字。
- 必须包含一个特殊符号,定义于Password special characters
- 长度必须在8-32之间。
下列的 PASSWORD_STRENGTH_1 和 PASSWORD_STRENGTH_2等价,
只是不同的写法,
PASSWORD_STRENGTH_1把各个逻辑块分开了,便于理解。
/// <summary>
/// 包含小写字母
/// </summary>
private static readonly string REG_CONTAIN_LOWERCASE_ASSERTION =
@"(?=.*[a-z])";
/// <summary>
/// 包含大写字母
/// </summary>
private static readonly string REG_CONTAIN_UPPERCASE_ASSERTION =
@"(?=.*[A-Z])";
/// <summary>
/// 包含数字
/// </summary>
private static readonly string REG_CONTAIN_DIGIT_ASSERTION =
@"(?=.*\d)";
/// <summary>
/// 包含特殊字符(https://www.owasp.org/index.php/Password_special_characters)
/// </summary>
private static readonly string REG_CONTAIN_SPECIAL_CHAR_ASSERTION =
@"(?=.*[ !""#$%&'()*+,-./:;<=>?@\[\]\^_`{|}~])";
public static readonly string PASSWORD_STRENGTH_1 =
$"{REG_CONTAIN_LOWERCASE_ASSERTION}" +
$"{REG_CONTAIN_UPPERCASE_ASSERTION}" +
$"{REG_CONTAIN_DIGIT_ASSERTION}" +
$"{REG_CONTAIN_SPECIAL_CHAR_ASSERTION}" +
@"^.{8,32}$";
/// <summary>
/// PASSWORD_STRENGTH_1 的另一种写法
/// </summary>
public static readonly string PASSWORD_STRENGTH_2 = @"(?=(.*[a-z]))(?=(.*[A-Z]))(?=(.*\d))(?=(.*[ !""#$%&'()*+,-./:;<=>?@\[\]\^_`{|}~]))^.{8,32}$";
public static void TestPattern(string pattern, string input)
{
Regex regex = new Regex(pattern);
Console.WriteLine(regex.IsMatch(input));
}
0x01 Prerequisite
阅读下列内容需要对以下的内容有涉猎。
- 正则表达式的基本概念
- C#创建String语法: 《C# Special Characters》
0x02 Emmm,一点废话
在软件行业里,和数据可以说是密不可分。我们所写的代码本质就是为数据服务。
传输数据、对数据做变换、数据存储等等。
软件里有很多种数据,有int、string、float、double、Decimal等。而对于有UI的客户端来说,在画面上显示的大部分都是字符串、而从画面上由用户键盘输入的也大部分都是字符串。别的Audio/Video数据本文暂不讨论。
所以这里有三个概念要提及。
- 格式化(Formating): 把数据按照一定的格式(format)转换成字符串便于显示在画面上。
- 校验(Validation): 校验用户输入是否符合规则比如密码强度是否达标,输入的年龄是否是正常范围的数字等。
- 解析(Parsing): 把用户输入的字符串按照一定规则解析到指定的数据类型上,这个数据类型可以是DateTime、Double、Decimal、Boolean也可以是Object。
而正则表达式则是验证数据的好帮手,主流的编程语言都会支持正则表达式。本文要解决的问题就是“如何使用正则表达式验证密码是否符合某种规则?“
0x03 那么,如何使用正则表达式验证密码是否符合某种规则?
为了实现这个目标,我们需要用到正则表达式里的一个概念叫做零宽度正预测先行断言(Zero-Width Positive Lookahead Assertions)。
长这个样子,
(?= 子表达式 )
使用这个构造便能够在正则表达式里使用先行断言了。可以参考以下例子。
public static void Test()
{
Regex regex = new Regex(@"(?=.*12)^.*$"); // 匹配是否有12的序列
Console.WriteLine(regex.IsMatch("23312312")); // True
}
public static void Test2()
{
Regex regex = new Regex(@"(?=.*\d)^.*$"); // 是否至少有一个数字
Console.WriteLine(regex.IsMatch("Aadakjdhak!@#!$")); // False
Console.WriteLine(regex.IsMatch("Aadakjdhak!1#!$")); // True
}
而当我们想实现文章最开始提到的5个规则的时候,依次实现即可。
@"(?=.*[a-z])" // 包含一个小写字母
@"(?=.*[A-Z])" // 包含一个大写字母
@"(?=.*\d)" // 包含一个数字
@"(?=.*[ !""#$%&'()*+,-./:;<=>?@\[\]\^_`{|}~])"; // 包含一个特殊字符
@"^.{8,32}$ // 长度限制,匹配 \n以外的任何单字符8-32次。'.'可以替换成任意的字符类(Character class)
以上的各个Pattern字符串拼接起来便能实现验证密码强度是否这5个规则了。
0x04 结语
数据验证是保证程序安全正确运行不可或缺的一步。错误的输入会导致不可预想的后果。
在《Effective Java The 3rd Edition》中
Item62: Avoid strings where other types are more appropriate.
讲到在存储用户输入数据时候,除非输入的数据就是文本类型,否则请转换成对于的正确的类型。而用户输入数据一定一定要做验证,因为你永远不知道你的用户会输入什么样奇葩的数据。
而验证数据最好的帮手之一,一定有正则表达式,学一点点正则表达式,能让数据验证更加简单。