Python学习历程------字符串相关操作及正则表达式
- 一、字符串的常用方法
-
- [1. 大小写转换](#1. 大小写转换)
- [2. 搜索与替换](#2. 搜索与替换)
- [3. 字符串判断](#3. 字符串判断)
- [5. 分割与连接](#5. 分割与连接)
- [6. 对齐与格式化](#6. 对齐与格式化)
- 二、字符串的编码和解码、常用的加密解密基础
-
- [1. 编码和解码](#1. 编码和解码)
-
- [1.1. 基本使用](#1.1. 基本使用)
- [1.2. 常用编码方式对比](#1.2. 常用编码方式对比)
- [1.3. 基于chardet库的编码解码最佳示例](#1.3. 基于chardet库的编码解码最佳示例)
- [2. 加密解密算法扩展------重点!!!](#2. 加密解密算法扩展——重点!!!)
-
- [2.1 哈希算法(不可逆)](#2.1 哈希算法(不可逆))
- [2.2 对称加密(AES)](#2.2 对称加密(AES))
-
- 宏观角度分析对称加密的使用场景
-
- [1. 数据存储加密(全盘加密/文件加密)](#1. 数据存储加密(全盘加密/文件加密))
- [2. 安全网络通信(TLS/SSL, VPN)](#2. 安全网络通信(TLS/SSL, VPN))
- [3. 数据库加密](#3. 数据库加密)
- [4. 消息应用与即时通讯](#4. 消息应用与即时通讯)
- [5. 支付系统与金融交易](#5. 支付系统与金融交易)
- [6. 软件与固件保护](#6. 软件与固件保护)
- Python代码示例
- [2.3 非对称加密(RSA)](#2.3 非对称加密(RSA))
-
- RSA的实现流程
- RSA的程序特性
- RSA使用场景
- RSA代码示例
- RSA和AES的组合使用
- RSA私钥的安全存储方案
-
- [1. 硬件安全模块(HSM)](#1. 硬件安全模块(HSM))
- [2. 云密钥管理服务(KMS)](#2. 云密钥管理服务(KMS))
- [3. 密码加密存储](#3. 密码加密存储)
- [4. 环境变量 + 文件权限](#4. 环境变量 + 文件权限)
- [5. 如何选择合适的加密方案](#5. 如何选择合适的加密方案)
- 三、数据验证的方法
-
- [1. 主流数据验证库介绍](#1. 主流数据验证库介绍)
-
- [1. Pydantic(最推荐)](#1. Pydantic(最推荐))
- [2. Cerberus(轻量灵活)](#2. Cerberus(轻量灵活))
- [3. Voluptuous(语法优雅)](#3. Voluptuous(语法优雅))
- 四、正则表达式的使用
-
- [1. re模块](#1. re模块)
-
- [1.1 基本使用](#1.1 基本使用)
- [1.2 正则表达式语法详解](#1.2 正则表达式语法详解)
- [1.3. 高级特性](#1.3. 高级特性)
- [2. regex库](#2. regex库)
- 总结
一、字符串的常用方法
类别 | 方法 | 描述 | 代码示例 |
---|---|---|---|
大小写 | .upper() |
转大写 | "hi".upper() → "HI" |
.lower() |
转小写 | "HI".lower() → "hi" |
|
.title() |
单词首字母大写 | "hi there".title() → "Hi There" |
|
搜索替换 | .find(x) |
查找子串位置 | "hi".find("i") → 1 |
.count(x) |
统计出现次数 | "hello".count("l") → 2 |
|
.replace(a,b) |
替换子串 | "ha".replace("a","i") → "hi" |
|
字符串判断 | .startswith(x) |
检查前缀 | "hi".startswith("h") → True |
.endswith(x) |
检查后缀 | "hi".endswith("i") → True |
|
.isalpha() |
是否全字母 | "hi".isalpha() → True |
|
去除空白 | .strip() |
移除首尾空白 | " hi ".strip() → "hi" |
分割连接 | .split() |
分割为列表 | "a b".split() → ['a','b'] |
.join(list) |
连接列表 | "-".join(['a','b']) → "a-b" |
|
对齐 | .center(n) |
居中对齐 | "hi".center(4) → " hi " |
1. 大小写转换
python
# 原始字符串
original_str = "hello World, Python3!"
# .upper(): 将所有字符转换为大写
print(original_str.upper()) # 输出: HELLO WORLD, PYTHON3!
# .lower(): 将所有字符转换为小写
print(original_str.lower()) # 输出: hello world, python3!
# .capitalize(): 将字符串首字母大写,其余小写
print(original_str.capitalize()) # 输出: Hello world, python3!
# .title(): 将每个单词的首字母大写
print(original_str.title()) # 输出: Hello World, Python3!
# .swapcase(): 交换大小写
print(original_str.swapcase()) # 输出: HELLO wORLD, pYTHON3!
2. 搜索与替换
python
text = "I love Python. Python is awesome."
# .find(sub): 返回子串首次出现的索引,未找到返回-1
print(text.find("Python")) # 输出: 7
print(text.find("Java")) # 输出: -1
# .index(sub): 与find类似,但未找到时会报错
print(text.index("Python")) # 输出: 7
# print(text.index("Java")) # 这会引发ValueError异常
# .count(sub): 统计子串出现次数
print(text.count("Python")) # 输出: 2
# .replace(old, new): 替换子串
print(text.replace("Python", "Java")) # 输出: I love Java. Java is awesome.
注意:慎重使用
index()
方法,如果没有找到对应的字符串会报错"ValueError
"。
3. 字符串判断
python
# .startswith(prefix): 检查是否以指定前缀开头
filename = "document.pdf"
print(filename.startswith("doc")) # 输出: True
print(filename.startswith("xls")) # 输出: False
# .endswith(suffix): 检查是否以指定后缀结尾
print(filename.endswith(".pdf")) # 输出: True
print(filename.endswith(".docx")) # 输出: False
# .isalpha(): 是否全是字母(中文也被认为是字母)
print("Hello".isalpha()) # 输出: True
print("Hello123".isalpha()) # 输出: False
print("你好".isalpha()) # 输出: True
# .isdigit(): 是否全是数字
print("123".isdigit()) # 输出: True
print("12.3".isdigit()) # 输出: False
# .isalnum(): 是否全是字母或数字
print("Hello123".isalnum()) # 输出: True
print("Hello 123".isalnum()) # 输出: False(包含空格)
# .isspace(): 是否全是空白字符
print(" ".isspace()) # 输出: True
print(" x ".isspace()) # 输出: False
注意:
isalpha方法
是判断全是字母的方法,但是中文也会被识别为字母
。
5. 分割与连接
python
# .split(sep): 使用分隔符分割字符串
csv_data = "apple,banana,orange,grape"
print(csv_data.split(",")) # 输出: ['apple', 'banana', 'orange', 'grape']
sentence = "I love programming"
print(sentence.split()) # 输出: ['I', 'love', 'programming'](默认按空格分割)
# .join(iterable): 将序列中的元素用指定字符串连接
fruits = ['apple', 'banana', 'orange']
print("-".join(fruits)) # 输出: apple-banana-orange
print(" ".join(["Hello", "World"])) # 输出: Hello World
6. 对齐与格式化
对齐
python
text = "Python"
# .center(width): 居中对齐
print(f"'{text.center(10)}'") # 输出: ' Python '
# .ljust(width): 左对齐
print(f"'{text.ljust(10)}'") # 输出: 'Python '
# .rjust(width): 右对齐
print(f"'{text.rjust(10)}'") # 输出: ' Python'
# 可以指定填充字符
print(f"'{text.center(10, '*')}'") # 输出: '**Python**'
格式化
💡 格式化分为三种方式:
- 占位符(不推荐)
- f-string
- format
python
name = "Alice"
age = 25
# f-string (Python 3.6+ 推荐)
message = f"我叫{name},今年{age}岁"
print(message) # 我叫Alice,今年25岁
# 表达式和格式化
price = 15.5
print(f"价格: {price:.2f}元") # 价格: 15.50元
print(f"明年年龄: {age + 1}") # 明年年龄: 26
# format()方法
template = "我叫{},今年{}岁".format(name, age)
named_template = "我叫{name},今年{age}岁".format(name=name, age=age)
# %操作符 (传统方式,不推荐新项目使用)
old_style = "我叫%s,今年%d岁" % (name, age)
format详细解释
Python的format()方法通过格式说明符({}中的:后面部分)提供精细的格式控制。格式说明符的基本语法为:{[字段名]!转换字符:格式说明},其中格式说明又包含[[填充]对齐][符号][#][0][宽度][分组选项][.精度][类型]。
基本用法回顾
python
# 位置参数
print("{} {}".format("Hello", "World")) # 输出: Hello World
# 关键字参数
print("{name} is {age} years old".format(name="Alice", age=25))
# 混合使用
print("{0} scored {score} points".format("Bob", score=95))
# 访问属性或索引
data = {"x": 10, "y": 20}
print("x={0[x]}, y={0[y]}".format(data))
对齐与填充
python
text = "Python"
# 左对齐 <,默认填充空格
print("|{:<10}|".format(text)) # 输出: |Python |
# 右对齐 >
print("|{:>10}|".format(text)) # 输出: | Python|
# 居中对齐 ^
print("|{:^10}|".format(text)) # 输出: | Python |
# 指定填充字符(必须在对齐符号前)
print("|{:*^10}|".format(text)) # 输出: |**Python**|
print("|{:=^10}|".format(text)) # 输出: |==Python==|
数字格式:符号控制
python
num = 42
neg_num = -42
# +: 总是显示符号(正数显示+,负数显示-)
print("{:+} {:+}".format(num, neg_num)) # 输出: +42 -42
# -: 仅负数显示符号(默认行为)
print("{:-} {:-}".format(num, neg_num)) # 输出: 42 -42
# 空格: 正数前加空格,负数前加负号
print("|{: }| |{: }|".format(num, neg_num)) # 输出: | 42| |-42|
数字格式:宽度、精度与千位分隔符
python
price = 1234.5678
# 宽度和精度控制
print("|{:10.2f}|".format(price)) # 输出: | 1234.57|
# 千位分隔符 ,
print("{:,}".format(1000000)) # 输出: 1,000,000
print("{:,.2f}".format(1234567.89)) # 输出: 1,234,567.89
# 组合使用
print("|{:>15,.2f}|".format(price)) # 输出: | 1,234.57|
数字格式:进制转换
python
number = 255
# 十进制 d
print("{:d}".format(number)) # 输出: 255
# 二进制 b(# 显示前缀)
print("{:#b}".format(number)) # 输出: 0b11111111
# 八进制 o
print("{:#o}".format(number)) # 输出: 0o377
# 十六进制 x/X(大小写控制十六进制字符)
print("{:#x}".format(number)) # 输出: 0xff
print("{:#X}".format(number)) # 输出: 0xFF
精度控制与类型指定
python
import math
# 浮点数精度控制
pi = math.pi
print("{:.2f}".format(pi)) # 输出: 3.14
print("{:.4f}".format(pi)) # 输出: 3.1416
# 百分比显示 %
print("{:.1%}".format(0.256)) # 输出: 25.6%
# 科学计数法 e/E
print("{:.2e}".format(123456789)) # 输出: 1.23e+08
print("{:.2E}".format(0.000012345)) # 输出: 1.23E-05
# 一般格式 g/G(在浮点和科学计数法间自动选择)
print("{:.5g}".format(123.456789)) # 输出: 123.46
print("{:.5g}".format(123456789)) # 输出: 1.2346e+08
零填充与特殊格式
python
# 零填充(相当于右对齐并用0填充)
print("{:05d}".format(42)) # 输出: 00042
print("{:08.2f}".format(3.14)) # 输出: 00003.14
# 字符串截断
text = "Hello World"
print("{:.5}".format(text)) # 输出: Hello
f-string扩展,语法基本一致(不可忽视!!!)
python
name = "Alice"
age = 25
price = 19.99
# 基本使用
print(f"Name: {name:>10}, Age: {age:03d}")
# 数字格式化
print(f"Price: ${price:.2f}")
print(f"Percentage: {0.256:.1%}")
# 嵌套表达式
print(f"Result: {age * price:,.2f}")
格式说明符完整语法表
类别 | 组件 | 符号 | 作用 | 示例 |
---|---|---|---|---|
填充 | 填充 | 任意字符 | 填充字符(需与对齐符配合) | {:*<10} |
对齐 | 对齐 | < |
左对齐 | {:<10} |
> |
右对齐 | {:>10} |
||
^ |
居中对齐 | {:^10} |
||
= |
数字符号后填充 | {:=10} |
||
符号 | 符号 | + |
总是显示符号 | {:+} |
- |
仅负数显示符号 | {:-} |
||
空格 | 正数前加空格 | {: } |
||
特殊 | 特殊 | # |
显示进制前缀 | {:#x} |
0 |
零填充 | {:05} |
||
宽度 | 宽度 | 数字 | 最小字段宽度 | {:10} |
分组 | 分组 | , |
千位分隔符 | {:,} |
精度 | 精度 | .数字 |
浮点数精度 | {:.2f} |
类型 | 类型 | s |
字符串 | {:s} |
d |
十进制整数 | {:d} |
||
b |
二进制 | {:b} |
||
o |
八进制 | {:o} |
||
x/X |
十六进制 | {:x} |
||
f/F |
浮点数 | {:.2f} |
||
e/E |
科学计数法 | {:.2e} |
||
g/G |
通用格式 | {:.5g} |
||
% |
百分比 | {:.1%} |
二、字符串的编码和解码、常用的加密解密基础
1. 编码和解码
1.1. 基本使用
python
# 字符串在Python内部以Unicode形式存储
text = "你好,世界!Hello World! 🚀"
print(f"原始字符串: {text}")
print(f"字符串类型: {type(text)}")
# 编码:字符串 → 字节序列
# UTF-8编码(最常用,兼容ASCII)
utf8_bytes = text.encode('utf-8')
print(f"UTF-8编码: {utf8_bytes}")
print(f"编码后类型: {type(utf8_bytes)}")
# GBK编码(中文环境常用)
gbk_bytes = text.encode('gbk')
print(f"GBK编码: {gbk_bytes}")
# 解码:字节序列 → 字符串
# UTF-8解码
decoded_utf8 = utf8_bytes.decode('utf-8')
print(f"UTF-8解码: {decoded_utf8}")
# GBK解码
decoded_gbk = gbk_bytes.decode('gbk')
print(f"GBK解码: {decoded_gbk}")
# 编码解码不匹配会导致乱码或错误
try:
wrong_decode = utf8_bytes.decode('gbk')
print(f"错误解码尝试: {wrong_decode}")
except UnicodeDecodeError as e:
print(f"解码错误: {e}")
1.2. 常用编码方式对比
python
def compare_encodings(text, encodings=None):
"""比较不同编码方式的结果"""
if encodings is None:
encodings = ['utf-8', 'gbk', 'ascii', 'latin-1']
print(f"\n原始文本: {text}")
for encoding in encodings:
try:
encoded = text.encode(encoding)
decoded = encoded.decode(encoding)
print(f"{encoding:>8}: 字节数={len(encoded):2d}, 可逆={text == decoded}")
except Exception as e:
print(f"{encoding:>8}: 错误 - {e}")
# 测试不同文本
compare_encodings("Hello World")
compare_encodings("你好世界")
compare_encodings("Special chars: ñ, ö, 日本")
python
D:\env\pycharm\pythonProject\demo1\demo2.py
原始文本: Hello World
utf-8: 字节数=11, 可逆=True
gbk: 字节数=11, 可逆=True
ascii: 字节数=11, 可逆=True
latin-1: 字节数=11, 可逆=True
原始文本: 你好世界
utf-8: 字节数=12, 可逆=True
gbk: 字节数= 8, 可逆=True
ascii: 错误 - 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)
latin-1: 错误 - 'latin-1' codec can't encode characters in position 0-3: ordinal not in range(256)
原始文本: Special chars: ñ, ö, 日本
utf-8: 字节数=29, 可逆=True
gbk: 错误 - 'gbk' codec can't encode character '\xf1' in position 15: illegal multibyte sequence
ascii: 错误 - 'ascii' codec can't encode character '\xf1' in position 15: ordinal not in range(128)
latin-1: 错误 - 'latin-1' codec can't encode characters in position 21-22: ordinal not in range(256)
Process finished with exit code 0
上面的例子是用来比较不同编码方式对不同字符的支持程序,经过对比我们可以得出以下的结论。
-
UTF-8是最通用的编码,支持所有字符集
-
GBK对中文更高效但兼容性有限
-
ASCII/Latin-1适用范围有限,只能处理有限字符集
1.3. 基于chardet库的编码解码最佳示例
python
import chardet
# 需要安装: pip install chardet
# 直接在控制台使用该命令安装即可
def improved_smart_decode(byte_data):
detection = chardet.detect(byte_data)
encoding = detection['encoding']
confidence = detection['confidence']
print(f"检测结果: {encoding}, 置信度: {confidence:.2f}")
# 添加置信度阈值
if encoding and confidence > 0.6:
try:
return byte_data.decode(encoding)
except UnicodeDecodeError:
pass
print(f"检测结果不可信,通常小于0.3视为完全不符合,小于0.6可信但不准确,当前置信度为:{confidence:^6.2f}不可信")
print("基于常见的编码方式进行解码...")
# 回退方案:尝试常见编码
for enc in ['utf-8', 'gbk', 'gb2312', 'latin-1']:
try:
new_result = byte_data.decode(enc)
print(f"正确的编码方式为:{enc}")
return new_result
except UnicodeDecodeError:
continue
return byte_data.decode('utf-8', errors='replace')
# 测试智能解码
test_bytes = "你好世界".encode('gbk')
result = improved_smart_decode(test_bytes)
print(f"智能解码结果: {result}")
输出结果为:
python
D:\env\pycharm\pythonProject\demo1\demo2.py
检测结果: TIS-620, 置信度: 0.27
检测结果不可信,通常小于0.3视为完全不符合,小于0.6可信但不准确,当前置信度为: 0.27 不可信
基于常见的编码方式进行解码...
正确的编码方式为:gbk
智能解码结果: 你好世界
Process finished with exit code 0
2. 加密解密算法扩展------重点!!!
2.1 哈希算法(不可逆)
python
import hashlib
import hmac
def demo_hash_functions():
"""演示常用哈希函数"""
data = "Hello World! 你好世界!"
print(f"原始数据: {data}")
# MD5 (128位,已不推荐用于安全用途,但仍用于校验)
md5_hash = hashlib.md5(data.encode()).hexdigest()
print(f"MD5: {md5_hash}")
# SHA-256 (更安全)
sha256_hash = hashlib.sha256(data.encode()).hexdigest()
print(f"SHA-256: {sha256_hash}")
# 加盐哈希 (更安全)
salt = "random_salt_123"
salted_hash = hashlib.sha256((data + salt).encode()).hexdigest()
print(f"加盐SHA-256: {salted_hash}")
# HMAC (密钥相关的哈希)
key = "secret_key"
hmac_hash = hmac.new(key.encode(), data.encode(), hashlib.sha256).hexdigest()
print(f"HMAC-SHA256: {hmac_hash}")
demo_hash_functions()
💡 注意阅读上面的代码示例,考虑以下几个问题:
- 哈希算法到底是什么?
哈希算法的用途有哪些?
为什么MD5哈希算法不推荐安全校验了?
- 其余的加盐哈希、SHA-256、HMAC有什么用途,使用场景是什么?
哈希算法到底有什么作用?
哈希函数(常被不严谨地称为Hash加密,但其本质是单向的
,并非加密
)的核心作用是为任意数据生成一个唯一的、固定长度的"数字指纹"
。其主要用途包括:
- 数据完整性验证
- 如上文所述,通过对比哈希值来确认数据在传输或存储过程中是否被改变。
- 安全存储密码
系统不应明文存储用户密码
。当用户注册时,系统对密码进行哈希并将哈希值存入数据库。登录时,对用户输入的密码再次进行同样的哈希操作,然后与数据库中的哈希值对比。即使数据库泄露,攻击者看到的也只是哈希值,很难反推出原始密码。- 这一点深有体会,目前我们公司的产品服务对象多为
国企和事业单位
,近些年来不断要求对数据进行加密存储,许多新客户也要求大部分的数据都要用密文存储
,这导致间接的损失了一些客户。
- 数字签名与证书
- 对
消息或文件进行哈希
,然后对生成的哈希值进行加密(即签名),效率远高于加密整个文件。接收者可以验证哈希值来确认文件的真实性和完整性。
- 对
- 数据唯一性标识
- 例如,Git使用SHA-1哈希来唯一标识每一次提交和每一个文件。区块链使用哈希来链接每一个区块,确保其不可篡改性。
- 消息认证码(HMAC)
- 如代码中所示,HMAC结合一个密钥和消息进行哈希。用于验证消息不仅完整,而且确实来自拥有相同密钥的发送方。
为什么MD5不安全了,但可以用于校验?
MD5为何不安全(针对密码/防篡改):
- 碰撞攻击 :MD5的核心漏洞是能够被轻易地找到"碰撞",即两个不同的输入产生相同的哈希值。
攻击者可以制造一个和原文件MD5值相同的恶意文件
。 - 计算速度过快 :
对于哈希函数来说,速度快在安全领域是缺点
。这使得攻击者可以用"暴力破解 "(尝试所有可能组合)或"彩虹表"(预计算的哈希值数据库)的方式快速反推原始数据。 - 不再抗碰撞:在现代计算机上,可以在可接受的时间内故意制造出MD5碰撞,这使得它无法用于验证数据的唯一性和真实性(即无法保证数据未被恶意替换)。
为何仍可用于校验(针对非恶意错误)
-
数据完整性校验:在非安全敏感的场景下,如软件下载、文件传输,MD5仍然可以用来检查非恶意的数据损坏。例如,下载一个文件后计算MD5,与官网提供的MD5对比。如果不同,说明文件可能在网络传输过程中出现了比特错误。它的计算速度反而成了优点。
-
场景假设不同:在这种使用场景中,我们假设错误是随机发生的(如网络丢包),而不是有一个主动的攻击者故意制造一个具有相同MD5值的恶意文件。对于随机错误,MD5的校验能力依然是足够的。
其他几种加密方式的区别与使用场景
SHA-256
- 是什么:SHA-2家族的一员,生成256位(32字节)的哈希值。
- 与MD5区别 :
- 更安全:目前没有公开的有效碰撞攻击方法。
- 更长的哈希值:256位 vs MD5的128位,大大增加了暴力破解的难度。
- 更慢:计算速度比MD5慢,这在安全上是优点。
- 使用场景 :
- 替代MD5的所有安全场景:数字证书、SSL/TLS、区块链(比特币)、软件完整性校验等。
- 是目前广泛推荐使用的通用安全哈希算法。
加盐哈希(Salted Hash)
- 是什么:在原始数据(如密码)后面拼接一个随机字符串(盐)后再进行哈希。
- 与普通SHA-256区别 :
- 防御彩虹表攻击:即使两个用户的密码相同,由于他们的"盐"不同,最终的哈希值也完全不同。攻击者无法使用一个预先计算好的彩虹表来批量破解密码。
- 强制个性化:即使攻击者破解了其中一个密码,也无法推知其他使用相同密码的账户。
- 使用场景 :
- 密码存储的首选方法。现代最佳实践是使用专门的密码哈希函数(如bcrypt, argon2),它们内置了加盐并且计算速度可调(故意慢),但"SHA-256 + 随机盐"的原理是相同的。
HMAC(密钥散列消息认证码)
- 是什么:一种使用加密哈希函数(如SHA-256)和一个密钥来验证消息完整性和真实性的技术。
- 与普通/加盐哈希的根本区别 :
- 需要密钥:HMAC的计算过程依赖于一个双方共享的密钥。没有密钥,就无法计算出正确的HMAC值。
- 目标不同:普通哈希验证"数据是否被更改",HMAC验证"数据是否被更改,且是否来自合法的、拥有密钥的发送方"。
- 使用场景 :
- API请求认证:客户端和服务器共享一个密钥,客户端在发送请求时计算请求内容的HMAC,服务器收到后使用相同密钥验证HMAC,从而认证客户端身份并确保请求未被篡改。
- JWT(JSON Web Tokens):可以使用HMAC进行签名。
- 安全通信协议:在SSL/TLS等协议中也有应用。
不同哈希加密方式总结和建议
方法 | 核心特点 | 安全性 | 主要使用场景 |
---|---|---|---|
MD5 | 速度快,哈希值短 | 不安全 | 非恶意场景的数据完整性校验(如文件下载) |
SHA-256 | 安全性高,通用性强 | 安全 | 数字签名、证书、区块链、替代MD5的校验 |
加盐哈希 | 密码+随机盐,防彩虹表 | 安全 | 密码存储(但更推荐bcrypt等) |
HMAC | 消息+密钥,验证来源和完整性 | 安全 | API认证、消息防篡改与身份验证 |
2.2 对称加密(AES)
核心模式:"
非对称加密握手 + 对称加密通话
"。几乎所有的现代安全通信协议都遵循这一高效且安全的模式。
宏观角度分析对称加密的使用场景
1. 数据存储加密(全盘加密/文件加密)
💡 这是最直接的应用场景,目的是保护静态数据。
-
全盘加密:像 Windows 的 BitLocker、macOS 的 FileVault、Linux 的 LUKS 都使用 AES 等对称加密算法对整个硬盘驱动器进行加密。当系统启动时,通过密码、TPM 芯片或 USB 密钥来解锁加密密钥,之后的所有读写操作在后台自动加解密,用户无感知。
-
文件/文件夹加密:对单个文件或压缩包进行加密后存储或传输。例如,使用 7-Zip 创建加密的压缩包,或者使用 EFS(加密文件系统)加密特定文件。
2. 安全网络通信(TLS/SSL, VPN)
💡 虽然 TLS/SSL 使用非对称加密来初始握手和身份验证,但一旦建立安全连接,所有后续数据的传输都使用对称加密。
-
HTTPS 网站 :当你访问一个 HTTPS 网站时,浏览器和服务器在握手阶段协商出一个唯一的"会话密钥"。之后,你们之间传输的所有网页内容、Cookie、个人信息都使用这个对称会话密钥进行加密 ,确保传输过程中不被窃听和篡改。这也是为什么要单独购买https流量的主要原因之一,会让你的网页更加安全。
-
虚拟专用网络:VPN(如 IPsec, OpenVPN)同样使用对称加密来加密你的所有网络流量,形成一个安全的"隧道",保护你的上网行为不被本地网络提供商或黑客窥探。
3. 数据库加密
💡 为了保护数据库中存储的敏感信息(如用户密码哈希、个人信息、金融数据),可以使用对称加密。
- 列级加密:对数据库中特定的敏感列(如"信用卡号"列)进行加密。应用程序在写入数据前加密,在读取数据后解密。这样,即使数据库文件被拖库,攻击者没有密钥也无法读取明文信息。
4. 消息应用与即时通讯
💡 像 WhatsApp、Signal、Telegram(秘密聊天)等应用使用端到端加密。其原理是:
- 使用非对称加密协商或交换一个对称密钥。
- 使用该对称密钥加密所有的聊天消息、图片、文件。
- 只有通信的双方持有该密钥,服务提供商本身也无法解密消息内容。
5. 支付系统与金融交易
-
银行卡芯片:EMV 芯片卡在交易时,与 POS 机进行动态认证,过程中使用对称加密来保护交易数据。
-
支付网关:处理信用卡交易时,从商户到支付网关的传输过程中,支付信息通常使用对称加密保护。
6. 软件与固件保护
- 数字版权管理:为了保护版权内容(如流媒体视频、游戏、软件),供应商会使用对称加密对内容本身进行加密,只有合法的、经过授权的播放器或客户端才能获取密钥进行解密播放。
- 固件更新:设备(如路由器、智能手机)在进行固件在线升级时,下载的固件包通常是经过对称加密和签名的,以防止攻击者植入恶意固件。
Python代码示例
注意!!!
- 下面的例子非常全面,每一步都有解释,建议自己在Pycharm仔细阅读。
python
"""
AES对称加密解密工具类
改进版:增加详细注释、错误处理、类型提示和安全性增强
"""
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import base64
import os
from typing import Union, Optional
class AESCipher:
"""
AES加密解密工具类
使用CBC模式,PKCS7填充,支持AES-256加密
"""
"""
__init__是Python对象创建后自动执行的初始化函数
类似Java的构造函数,但是Java的构造函数作用是创建对象
而__init__是创建对象之后自动执行,Python中的构造函数是__new__
"""
"""
self是Python中实例化方法必须要有的一个参数,作用类似于Java中的this
但Java中this是隐式的,而Python中是显式的,而且必须手动申明
"""
"""
key: Optional[bytes]类似于TypeScript写法,用于限制参数的类型,一般写法直接key=None即可
"""
def __init__(self, key: Optional[bytes] = None):
"""
初始化AES加密器
作用:
- 设置加密密钥
- 确定AES块大小
参数:
key: 可选的密钥字节,如果为None则自动生成32字节(256位)随机密钥
"""
# AES块大小固定为16字节
self.block_size = AES.block_size # 值为16
if key:
# 验证密钥长度
if len(key) not in [16, 24, 32]:
raise ValueError("密钥长度必须为16(AES-128)、24(AES-192)或32(AES-256)字节")
self.key = key
else:
# 生成32字节(256位)随机密钥 - AES-256
# get_random_bytes()作用:生成密码学安全的随机字节序列
self.key = get_random_bytes(32)
print(f"[初始化] 密钥长度: {len(self.key)}字节, 块大小: {self.block_size}字节")
# -> str 这种写法是用于定义函数的返回值类型为str字符串
def encrypt(self, plaintext: str) -> str:
"""
加密文本字符串
步骤:
1. 生成随机初始化向量(IV)
2. 创建AES加密器
3. 对明文进行填充
4. 执行加密
5. 组合IV和密文
6. Base64编码返回
参数:
plaintext: 要加密的原始文本
返回:
Base64编码的加密字符串
"""
print(f"\n[加密开始] 明文: '{plaintext}'")
# 步骤1: 生成随机初始化向量(IV)
# IV作用:确保同样的明文每次加密结果不同,增强安全性
iv = get_random_bytes(self.block_size)
print(f"[步骤1] 生成IV: {iv.hex()} (长度: {len(iv)}字节)")
# 步骤2: 创建AES加密器
# AES.new()作用:创建AES加密对象
# 参数说明:
# - self.key: 加密密钥
# - AES.MODE_CBC: 密码块链模式
# - iv: 初始化向量
cipher = AES.new(self.key, AES.MODE_CBC, iv)
print(f"[步骤2] 创建CBC模式加密器完成")
# 步骤3: 将文本编码为字节并填充
# encode()作用:将字符串转换为UTF-8编码的字节序列
plaintext_bytes = plaintext.encode('utf-8')
print(f"[步骤3] 文本编码为字节: {plaintext_bytes} (长度: {len(plaintext_bytes)}字节)")
# pad()作用:使用PKCS7填充方案对数据进行填充,使其长度为块大小的整数倍
# PKCS7填充原理:缺少n个字节就填充n个值为n的字节
padded_bytes = pad(plaintext_bytes, self.block_size)
print(f"[步骤3] PKCS7填充后: {padded_bytes} (长度: {len(padded_bytes)}字节)")
# 步骤4: 执行加密
# encrypt()作用:对填充后的数据进行AES加密
ciphertext = cipher.encrypt(padded_bytes)
print(f"[步骤4] 加密完成,密文: {ciphertext.hex()} (长度: {len(ciphertext)}字节)")
# 步骤5: 组合IV和密文
# IV + 密文的作用:解密时需要相同的IV,一起传输但不会泄露密钥
encrypted_data = iv + ciphertext
print(f"[步骤5] 组合IV和密文: {encrypted_data.hex()} (总长度: {len(encrypted_data)}字节)")
# 步骤6: Base64编码
# base64.b64encode()作用:将二进制数据编码为ASCII字符串,便于传输和存储
# decode()作用:将字节转换为字符串
result = base64.b64encode(encrypted_data).decode('utf-8')
print(f"[步骤6] Base64编码结果: {result}")
print(f"[加密完成] 最终密文长度: {len(result)}字符")
return result
def decrypt(self, encrypted_text: str) -> str:
"""
解密文本字符串
步骤:
1. Base64解码
2. 分离IV和密文
3. 创建AES解密器
4. 执行解密
5. 去除填充
6. 解码为字符串
参数:
encrypted_text: Base64编码的加密字符串
返回:
解密后的原始文本字符串
"""
print(f"\n[解密开始] 密文: {encrypted_text[:50]}...")
try:
# 步骤1: Base64解码
# base64.b64decode()作用:将Base64字符串解码回二进制数据
encrypted_data = base64.b64decode(encrypted_text)
print(f"[步骤1] Base64解码后数据长度: {len(encrypted_data)}字节")
# 步骤2: 分离IV和密文
# IV是前16字节,剩余部分是密文
iv = encrypted_data[:self.block_size]
ciphertext = encrypted_data[self.block_size:]
print(f"[步骤2] 分离IV: {iv.hex()} (长度: {len(iv)}字节)")
print(f"[步骤2] 分离密文: {ciphertext.hex()[:50]}... (长度: {len(ciphertext)}字节)")
# 步骤3: 创建AES解密器
# 使用相同的密钥和IV创建解密器
cipher = AES.new(self.key, AES.MODE_CBC, iv)
print(f"[步骤3] 创建CBC模式解密器完成")
# 步骤4: 执行解密
# decrypt()作用:对密文进行AES解密
decrypted_padded = cipher.decrypt(ciphertext)
print(f"[步骤4] 解密后数据(含填充): {decrypted_padded} (长度: {len(decrypted_padded)}字节)")
# 步骤5: 去除填充
# unpad()作用:移除PKCS7填充,恢复原始数据
decrypted_bytes = unpad(decrypted_padded, self.block_size)
print(f"[步骤5] 去除填充后: {decrypted_bytes} (长度: {len(decrypted_bytes)}字节)")
# 步骤6: 解码为字符串
# decode()作用:将UTF-8字节序列转换回字符串
result = decrypted_bytes.decode('utf-8')
print(f"[步骤6] 解码为字符串: '{result}'")
print(f"[解密完成] 成功恢复明文")
return result
except Exception as e:
error_msg = f"解密失败: {e}"
print(f"[错误] {error_msg}")
raise ValueError(error_msg)
def get_key_base64(self) -> str:
"""
获取Base64编码的密钥(用于安全存储)
作用:
- 将二进制密钥转换为可安全存储的字符串格式
"""
return base64.b64encode(self.key).decode('utf-8')
def set_key_from_base64(self, key_b64: str) -> None:
"""
从Base64字符串设置密钥
作用:
- 从存储的Base64字符串恢复密钥
"""
self.key = base64.b64decode(key_b64)
def demo_aes_encryption():
"""
演示AES加密解密的完整流程
"""
print("\n" + "=" * 60)
print("AES对称加密完整演示")
print("=" * 60)
# 创建加密器实例
print("1. 创建AES加密器...")
cipher = AESCipher()
# 测试数据
test_cases = [
"这是一段需要加密的敏感信息!",
"Hello, World!",
"短文本",
"Very long text " * 10 # 测试长文本
]
# enumerate是内置函数,用于在循环时同时获取索引和元素值,第二个参数是定义开始索引的,表示i的起始值
# 但这不代表我们可以用test_cases[i]取数,这只是内置函数给我们自动将i进行计算而已
for i, original_text in enumerate(test_cases, 1):
print(f"\n{'#' * 40}")
print(f"测试案例 {i}: '{original_text[:30]}{'...' if len(original_text) > 30 else ''}'")
print(f"{'#' * 40}")
try:
# 加密演示
print(f"原始文本长度: {len(original_text)}字符")
encrypted = cipher.encrypt(original_text)
print(f"✅ 加密成功")
# 解密演示
decrypted = cipher.decrypt(encrypted)
print(f"✅ 解密成功")
# 验证结果
if original_text == decrypted:
print(f"✅ 验证通过: 原始文本与解密文本一致")
else:
print(f"❌ 验证失败: 文本不一致")
except Exception as e:
print(f"❌ 加解密过程出错: {e}")
# 密钥管理演示
print(f"\n{'#' * 40}")
print("密钥管理演示")
print(f"{'#' * 40}")
# 保存密钥
saved_key = cipher.get_key_base64()
print(f"Base64编码密钥: {saved_key}")
# 使用保存的密钥创建新加密器
print("\n使用保存的密钥创建新加密器...")
new_cipher = AESCipher()
new_cipher.set_key_from_base64(saved_key)
# 测试密钥兼容性
test_text = "测试密钥兼容性"
encrypted = cipher.encrypt(test_text)
decrypted = new_cipher.decrypt(encrypted)
if test_text == decrypted:
print("✅ 密钥兼容性测试通过")
else:
print("❌ 密钥兼容性测试失败")
def security_considerations():
"""
安全性考虑和建议
"""
print(f"\n{'#' * 40}")
print("安全性考虑和建议")
print(f"{'#' * 40}")
considerations = [
"✅ 使用CBC模式,每次加密生成随机IV,避免相同明文产生相同密文",
"✅ 使用PKCS7填充,符合密码学标准",
"✅ 默认使用AES-256(32字节密钥),提供强安全性",
"⚠️ 密钥必须安全存储,不要硬编码在代码中",
"⚠️ 考虑使用环境变量或密钥管理服务存储密钥",
"⚠️ CBC模式需要正确的IV管理,确保每次加密使用不同IV",
"💡 对于大量数据,考虑使用GCM模式提供认证加密",
"💡 在生产环境中添加完整性校验(MAC)"
]
for consideration in considerations:
print(consideration)
if __name__ == "__main__":
# 执行演示
demo_aes_encryption()
security_considerations()
总结一下上面的例子,非常的nice我只能说,至少用于对称加密的学习那是相当的够用。
- 首先利用get_random_bytes随机生成32位的密钥。
- 之后使用get_random_bytes生成随机的iv,iv的作用是为了相同的内容密文也是不同的。
- 利用上面的iv和密钥创建AES加密容器。
- 将明文用UTF-8进行encode,然后使用pad进行填充,要保证是块大小的整数倍。
- 执行加密,之后组合iv和密文并进行Base64编码,方便存储和传输。
那么上面的加密过程已经清晰了,解密则相反,只不过有一个致命的问题,在生产环境中,密钥怎么进行存储?
总不能传输给后台吧,那不是泄露了吗,毕竟这种规则都是通用的,可以被破解,而密钥的管理要用到非对称加密,我们接下来再说。
2.3 非对称加密(RSA)
RSA的实现流程
- 密钥生成:创建私钥/公钥对
- 公钥加密:使用公钥加密数据
- 私钥解密:使用私钥解密数据
RSA的程序特性
- 非对称性:加密和解密使用不同的密钥。
- 安全性高:底层使用复杂的数学算法,有效的保证了安全。
- 速度较慢:对比AES对称加密慢100-1000倍。
- 长度限制:一次性加密的数据量有限制。
- 密钥对管理:公钥可公开,私钥必须保密。
RSA使用场景
- 安全通信初始化(TLS/SSL握手)
- 数字签名和身份验证
- 安全电子邮件(PGP)
- SSH远程登录
- 区块链和加密货币
- API安全认证
- 数字证书(PKI)
RSA代码示例
python
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
import base64
class RSACipher:
"""RSA非对称加密解密工具类"""
def __init__(self, key_size=2048):
"""
初始化RSA密钥对
步骤作用:
1. 设置密钥长度(2048位是当前安全标准)
2. 生成RSA密钥对
3. 分离公钥和私钥
"""
self.key_size = key_size # 密钥位数,决定安全性
# RSA.generate()作用:生成RSA密钥对,基于大素数分解难题
self.key_pair = RSA.generate(key_size)
# 私钥:用于解密和签名,必须保密
self.private_key = self.key_pair
# 公钥:用于加密和验证,可以公开分发
self.public_key = self.key_pair.publickey()
print(f"[RSA初始化] 生成{key_size}位密钥对完成")
def encrypt(self, plaintext, public_key=None):
"""使用公钥加密
步骤作用:
1. 创建PKCS1_OAEP加密器
2. 将文本转换为字节
3. 检查长度限制(RSA不能加密太长数据)
4. 执行加密
5. Base64编码便于传输
"""
try:
# PKCS1_OAEP.new()作用:创建使用OAEP填充的RSA加密器
# OAEP填充:增加随机性,防止确定性攻击
cipher = PKCS1_OAEP.new(public_key or self.public_key)
# encode()作用:将字符串转换为UTF-8字节序列
plaintext_bytes = plaintext.encode('utf-8')
# RSA长度限制计算:
# - 密钥大小:key_size//8 = 字节数
# - OAEP填充占用42字节
max_length = (self.key_size // 8) - 42
print(f"[RSA加密] 明文长度: {len(plaintext_bytes)}字节, 最大支持: {max_length}字节")
if len(plaintext_bytes) > max_length:
raise ValueError(f"文本过长,最大支持 {max_length} 字节")
# encrypt()作用:使用公钥加密数据
# 原理:使用公钥的指数e和模数n进行模幂运算
ciphertext = cipher.encrypt(plaintext_bytes)
print(f"[RSA加密] 加密完成,密文长度: {len(ciphertext)}字节")
# base64编码:将二进制数据转换为可安全传输的文本格式
return base64.b64encode(ciphertext).decode('utf-8')
except Exception as e:
return f"加密失败: {e}"
def decrypt(self, ciphertext):
"""使用私钥解密
步骤作用:
1. Base64解码
2. 创建PKCS1_OAEP解密器
3. 使用私钥解密
4. 将字节转换回字符串
"""
try:
# 创建使用私钥的解密器
cipher = PKCS1_OAEP.new(self.private_key)
# base64解码:将文本格式转换回二进制数据
ciphertext_bytes = base64.b64decode(ciphertext)
print(f"[RSA解密] 密文长度: {len(ciphertext_bytes)}字节")
# decrypt()作用:使用私钥解密数据
# 原理:使用私钥的指数d和模数n进行模幂运算
decrypted_bytes = cipher.decrypt(ciphertext_bytes)
print(f"[RSA解密] 解密完成,明文长度: {len(decrypted_bytes)}字节")
# decode()作用:将UTF-8字节序列转换回字符串
return decrypted_bytes.decode('utf-8')
except Exception as e:
return f"解密失败: {e}"
def get_public_key_pem(self):
"""获取PEM格式的公钥
作用:将公钥转换为标准PEM格式,便于存储和传输"""
return self.public_key.export_key().decode('utf-8')
def get_private_key_pem(self):
"""获取PEM格式的私钥
作用:将私钥转换为标准PEM格式,便于安全存储"""
return self.private_key.export_key().decode('utf-8')
RSA和AES的组合使用
python
"""
混合加密系统:RSA加密AES密钥 + AES加密实际数据
这是行业标准做法,兼顾安全性和性能
"""
class HybridEncryptionSystem:
def __init__(self):
# RSA用于密钥交换
self.rsa_cipher = RSACipher()
# AES用于数据加密
self.aes_cipher = AESCipher()
def encrypt_data(self, plaintext: str, recipient_public_key) -> dict:
"""
加密流程:
1. 使用AES加密原始数据(高效)
2. 使用RSA加密AES密钥(安全传输)
"""
# AES加密数据
encrypted_data = self.aes_cipher.encrypt(plaintext)
# RSA加密AES密钥
encrypted_key = self.rsa_cipher.encrypt(
self.aes_cipher.get_key_base64(),
recipient_public_key
)
return {
"encrypted_data": encrypted_data,
"encrypted_aes_key": encrypted_key,
"rsa_public_key": self.rsa_cipher.get_public_key_pem() # 可选,用于回复
}
def decrypt_data(self, encrypted_package: dict, rsa_private_key) -> str:
"""
解密流程:
1. 使用RSA私钥解密AES密钥
2. 使用AES密钥解密数据
"""
# 解密AES密钥
decrypted_key_b64 = self.rsa_cipher.decrypt(
encrypted_package["encrypted_aes_key"],
rsa_private_key
)
# 设置AES密钥
self.aes_cipher.set_key_from_base64(decrypted_key_b64)
# 解密数据
return self.aes_cipher.decrypt(encrypted_package["encrypted_data"])
# 使用示例
def hybrid_encryption_demo():
# 发送方
sender = HybridEncryptionSystem()
# 接收方的RSA公钥(假设已安全获取)
recipient_public_key = "接收方的RSA公钥PEM格式"
# 加密数据
original_data = "敏感业务数据"
encrypted_package = sender.encrypt_data(original_data, recipient_public_key)
print("加密完成:")
print(f"- AES加密数据长度: {len(encrypted_package['encrypted_data'])}")
print(f"- RSA加密的AES密钥: {encrypted_package['encrypted_aes_key'][:50]}...")
# 接收方解密(使用自己的RSA私钥)
# decrypted = recipient.decrypt_data(encrypted_package, recipient_private_key)
上述代码的核心就是使用RSA来加密AES的密钥,传输数据的时候将AES加密后的密钥和加密的数据一起传输,之后服务端用私钥进行解密
RSA私钥的安全存储方案
1. 硬件安全模块(HSM)
💡 最安全的方案,私钥永远不出硬件
- 原理: 专用硬件设备,私钥在硬件内生成和存储,永不导出
- 优点: 最高安全性,防物理攻击
- 缺点: 成本高,部署复杂
- 适用: 金融、政府、大型企业
2. 云密钥管理服务(KMS)
- 原理: 使用云服务商的密钥管理服务,应用程序通过API调用加解密
- 优点: 易用,无需管理密钥基础设施
- 缺点: 依赖云服务商,可能有合规问题
- 适用: 云原生应用,中小企业
3. 密码加密存储
python
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Cipher import AES
import os
# 生成加密密钥
salt = os.urandom(32)
key = PBKDF2(password, salt, 32, count=100000)
# 加密私钥
private_key_pem = "RSA私钥PEM内容"
iv = os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted_private_key = cipher.encrypt(pad(private_key_pem.encode(), 16))
# 存储:salt + iv + 加密私钥
storage_data = salt + iv + encrypted_private_key
return {
"存储格式": "salt(32) + iv(16) + 加密私钥",
"解密要求": "需要正确密码",
"安全性": "中等,依赖密码强度"
}
4. 环境变量 + 文件权限
- 将私钥文件放在安全目录
- 设置严格文件权限(chmod 600)
- 通过环境变量指定密钥路径
- 应用程序运行时读取
5. 如何选择合适的加密方案
根据需求和企业的实力进行选择,一般来说大部分使用的都是环境变量+文件的方案,稍微大点的企业使用第三方进行密钥管理也完全足够了,只有像政府和大型企业才会采用最复杂的HSM进行加密。
三、数据验证的方法
💡 这里说的数据验证是通过程序自带的字符串方法检查数据是否简单的符合自己的要求,也就是下面这些方法的使用,上面已经说过了。
python
# .startswith(prefix): 检查是否以指定前缀开头
filename = "document.pdf"
print(filename.startswith("doc")) # 输出: True
print(filename.startswith("xls")) # 输出: False
# .endswith(suffix): 检查是否以指定后缀结尾
print(filename.endswith(".pdf")) # 输出: True
print(filename.endswith(".docx")) # 输出: False
# .isalpha(): 是否全是字母(中文也被认为是字母)
print("Hello".isalpha()) # 输出: True
print("Hello123".isalpha()) # 输出: False
print("你好".isalpha()) # 输出: True
# .isdigit(): 是否全是数字
print("123".isdigit()) # 输出: True
print("12.3".isdigit()) # 输出: False
# .isalnum(): 是否全是字母或数字
print("Hello123".isalnum()) # 输出: True
print("Hello 123".isalnum()) # 输出: False(包含空格)
# .isspace(): 是否全是空白字符
print(" ".isspace()) # 输出: True
print(" x ".isspace()) # 输出: False
1. 主流数据验证库介绍
1. Pydantic(最推荐)
python
"""
Pydantic - 现代Python的数据验证和设置管理
特点:性能高、类型提示友好、功能强大
安装:pip install pydantic
"""
from pydantic import BaseModel, EmailStr, validator, Field
from typing import Optional
import re
class User(BaseModel):
# 基础字段验证
name: str = Field(..., min_length=1, max_length=50)
age: int = Field(..., ge=0, le=150) # 大于等于0,小于等于150
# 内置邮箱验证
email: EmailStr
# 自定义手机号验证
phone: str = Field(..., regex=r'^1[3-9]\d{9}$')
# 自定义验证器
@validator('name')
def name_must_contain_letters(cls, v):
if not any(c.isalpha() for c in v):
raise ValueError('姓名必须包含字母')
return v.title() # 自动格式化
@validator('phone')
def validate_phone_format(cls, v):
if not re.match(r'^1[3-9]\d{9}$', v):
raise ValueError('手机号格式不正确')
return v
# 使用示例
def pydantic_demo():
# 正确数据
try:
user = User(
name="张三",
age=25,
email="test@example.com",
phone="13800138000"
)
print(f"验证成功: {user.dict()}")
except Exception as e:
print(f"验证失败: {e}")
# 错误数据测试
test_cases = [
{"name": "", "age": 25, "email": "invalid", "phone": "123"},
{"name": "John", "age": 200, "email": "test@example.com", "phone": "13800138000"},
]
for data in test_cases:
try:
user = User(**data)
except Exception as e:
print(f"验证失败: {e}")
pydantic_demo()
2. Cerberus(轻量灵活)
python
"""
Cerberus - 轻量级数据验证库
特点:Schema定义简单、灵活性强
安装:pip install cerberus
"""
from cerberus import Validator
# 定义验证规则
user_schema = {
'name': {
'type': 'string',
'minlength': 1,
'maxlength': 50,
'required': True
},
'age': {
'type': 'integer',
'min': 0,
'max': 150,
'required': True
},
'email': {
'type': 'string',
'regex': r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
'required': True
},
'phone': {
'type': 'string',
'regex': r'^1[3-9]\d{9}$',
'required': True
}
}
# 自定义验证规则
def validate_email_domain(field, value, error):
"""自定义邮箱域名验证"""
blocked_domains = ['temp-mail.org', '10minutemail.com']
domain = value.split('@')[1]
if domain in blocked_domains:
error(field, f"不允许使用临时邮箱域名: {domain}")
user_schema['email']['validator'] = validate_email_domain
def cerberus_demo():
validator = Validator(user_schema)
# 测试数据
test_data = {
'name': '李四',
'age': 30,
'email': 'lisi@example.com',
'phone': '13900139000'
}
if validator.validate(test_data):
print("数据验证通过")
print(f"规范化后的数据: {validator.document}")
else:
print("数据验证失败:")
print(f"错误信息: {validator.errors}")
cerberus_demo()
3. Voluptuous(语法优雅)
python
"""
Voluptuous - 专注于数据验证的库
特点:语法优雅、错误信息清晰
安装:pip install voluptuous
"""
from voluptuous import Schema, Required, All, Length, Range, Email, Url, MultipleInvalid
import re
# 定义验证schema
user_schema = Schema({
Required('name'): All(str, Length(min=1, max=50)),
Required('age'): All(int, Range(min=0, max=150)),
Required('email'): Email(),
Required('phone'): All(str, Length(min=11, max=11)),
'website': Url(), # 可选字段
})
# 自定义验证函数
def validate_phone(value):
if not re.match(r'^1[3-9]\d{9}$', value):
raise ValueError('手机号格式不正确')
return value
# 更新schema加入自定义验证
user_schema = Schema({
Required('name'): All(str, Length(min=1, max=50)),
Required('age'): All(int, Range(min=0, max=150)),
Required('email'): Email(),
Required('phone'): All(str, Length(min=11, max=11), validate_phone),
})
def voluptuous_demo():
# 正确数据
valid_data = {
'name': '王五',
'age': 28,
'email': 'wangwu@example.com',
'phone': '13700137000'
}
try:
result = user_schema(valid_data)
print(f"验证成功: {result}")
except MultipleInvalid as e:
print(f"验证失败: {e}")
# 错误数据
invalid_data = {
'name': '',
'age': 200,
'email': 'invalid-email',
'phone': '123'
}
try:
result = user_schema(invalid_data)
except MultipleInvalid as e:
print(f"验证失败详情:")
for error in e.errors:
print(f"- {error}")
voluptuous_demo()
四、正则表达式的使用
1. re模块
1.1 基本使用
python
import re
# re模块核心函数
text = "我的电话是138-0013-8000,邮箱是test@example.com"
# 1. re.search() - 查找第一个匹配
match = re.search(r'\d{3}-\d{4}-\d{4}', text)
if match:
print(f"找到电话: {match.group()}") # 138-0013-8000
# 2. re.findall() - 查找所有匹配
emails = re.findall(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', text)
print(f"所有邮箱: {emails}") # ['test@example.com']
# 3. re.finditer() - 返回迭代器(可获取位置)
for match in re.finditer(r'\d+', text):
print(f"数字: {match.group()}, 位置: {match.span()}")
# 4. re.sub() - 替换
anonymized = re.sub(r'\d{4}$', 'XXXX', '13800138000')
print(f"匿名化: {anonymized}") # 1380013XXXX
# 5. re.split() - 分割
parts = re.split(r'[,。]', "你好,世界。今天天气不错。")
print(f"分割结果: {parts}") # ['你好', '世界', '今天天气不错', '']
函数名 | 基本使用示例 | 功能描述 |
---|---|---|
re.search() |
match = re.search(r'\d{3}-\d{4}-\d{4}', text) if match: print(match.group()) |
在字符串中搜索第一个匹配正则表达式的模式,返回匹配对象或None |
re.findall() |
`emails = re.findall(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Z | a-z]{2,}\b', text)` |
re.finditer() |
for match in re.finditer(r'\d+', text): print(f"数字: {match.group()}, 位置: {match.span()}") |
查找所有匹配项,返回一个迭代器,每个元素都是匹配对象,可获取匹配内容和位置信息 |
re.sub() |
anonymized = re.sub(r'\d{4}$', 'XXXX', '13800138000') |
使用指定的替换字符串替换所有匹配正则表达式的子串 |
re.split() |
parts = re.split(r'[,。]', "你好,世界。今天天气不错。") |
根据正则表达式模式分割字符串,返回分割后的子串列表 |
1.2 正则表达式语法详解
python
"""
正则表达式元字符和语法
"""
def regex_syntax_demo():
text = "价格是¥100.50,折扣后¥80.25,联系人: 张先生"
# 字符类
print("1. 字符类:")
print(re.findall(r'[张李王]先生', text)) # ['张先生']
print(re.findall(r'[0-9]', text)) # ['1', '0', '0', '5', '0', '8', '0', '2', '5']
# 量词
print("\n2. 量词:")
print(re.findall(r'\d+', text)) # ['100', '50', '80', '25'] - 1次或多次
print(re.findall(r'\d*', text)) # 包含空字符串
print(re.findall(r'\d{2,3}', text)) # ['100', '50', '80', '25'] - 2到3次
print(re.findall(r'¥\d+\.?\d*', text)) # ['¥100.50', '¥80.25']
# 边界和位置
print("\n3. 边界匹配:")
print(re.findall(r'^价格', text)) # ['价格'] - 开头
print(re.findall(r'先生$', "张先生")) # ['先生'] - 结尾
print(re.findall(r'\b联系人', text)) # ['联系人'] - 单词边界
# 分组和捕获
print("\n4. 分组捕获:")
match = re.search(r'¥(\d+)\.(\d+)', text)
if match:
print(f"完整匹配: {match.group(0)}") # ¥100.50
print(f"整数部分: {match.group(1)}") # 100
print(f"小数部分: {match.group(2)}") # 50
print(f"所有分组: {match.groups()}") # ('100', '50')
regex_syntax_demo()
1.3. 高级特性
python
import re
def advanced_regex_features():
"""高级正则特性"""
text = """
用户信息:
- 姓名: 张三, 年龄: 25, 邮箱: zhangsan@example.com
- 姓名: 李四, 年龄: 30, 邮箱: lisi@test.org
- 姓名: 王五, 年龄: 28, 邮箱: wangwu@domain.cn
"""
# 1. 命名分组
pattern = r"姓名: (?P<name>[^,]+), 年龄: (?P<age>\d+), 邮箱: (?P<email>[^\s]+)"
for match in re.finditer(pattern, text):
print(f"\n命名分组结果:")
print(f" 姓名: {match.group('name')}")
print(f" 年龄: {match.group('age')}")
print(f" 邮箱: {match.group('email')}")
# 2. 非捕获分组
# (?:...) 不捕获的分组
numbers = "100元 200美元 300欧元"
currencies = re.findall(r'(\d+)(?:元|美元|欧元)', numbers)
print(f"\n非捕获分组: {currencies}") # ['100', '200', '300']
# 3. 前后查找
# (?=...) 正向肯定前瞻, (?!...) 正向否定前瞻
# (?<=...) 正向肯定后顾, (?<!...) 正向否定后顾
# 匹配后面是"元"的数字
prices = re.findall(r'\d+(?=元)', numbers)
print(f"价格(元): {prices}") # ['100']
# 匹配前面是"$"的数字
dollar_text = "价格$100 重量100kg"
dollar_prices = re.findall(r'(?<=\$)\d+', dollar_text)
print(f"美元价格: {dollar_prices}") # ['100']
# 4. 条件匹配
pattern = r'(\d+)(?(1)元|美元)' # 如果有分组1匹配"元",否则匹配"美元"
# 5. 注释和详细模式
complex_pattern = r"""
^ # 字符串开始
(\w+) # 用户名 - 分组1
@ # @符号
(\w+ # 域名前缀 - 分组2
\. # 点号
\w+) # 域名后缀 - 分组3
$ # 字符串结束
"""
email = "user@example.com"
match = re.search(complex_pattern, email, re.VERBOSE)
if match:
print(f"\n详细模式解析:")
print(f" 用户名: {match.group(1)}")
print(f" 域名: {match.group(2)}")
advanced_regex_features()
2. regex库
regex库要比re库更加全面,支持的场景更多,目前可以作为了解,如果有复杂的需求可以考虑使用regex库进行解决,一般的需求re库就可以支持,regex更多用于复杂的需求比如日志分析。
python
"""
regex库 - 更强大、更兼容的正则表达式库
安装: pip install regex
特点: 支持更多特性、更好的Unicode支持、兼容re API
"""
import regex # 注意导入的是regex不是re
def regex_library_demo():
"""regex库高级特性演示"""
text = "测试unicode: 🚀🚀 表情符号和中文"
# 1. 更好的Unicode支持
print("1. Unicode支持:")
# 匹配所有字母字符(包括中文)
print(regex.findall(r'\p{L}+', text)) # ['测试unicode', '表情符号和中文']
# 匹配表情符号
print(regex.findall(r'\p{Emoji}', text)) # ['🚀', '🚀']
# 2. 重叠匹配
print("\n2. 重叠匹配:")
text = "aaa"
# 标准re库只能找到 ['aaa']
# regex可以找到所有重叠匹配
matches = regex.findall(r'a.a', text, overlapped=True)
print(f"重叠匹配: {matches}") # ['aaa']
# 3. 模糊匹配(容错匹配)
print("\n3. 模糊匹配:")
text = "color centre favorite"
# 允许拼写错误(美式vs英式英语)
pattern = regex.compile(r'(?e)(color){e<=1}') # 允许1个错误
matches = pattern.findall(text)
print(f"模糊匹配: {matches}") # ['color', 'colou']
# 4. 递归模式
print("\n4. 递归模式:")
html = "<div>内容<span>嵌套</span>更多内容</div>"
# 匹配嵌套的HTML标签
pattern = regex.compile(r'<([^>]+)>(?:[^<>]|(?R))*</\1>')
matches = pattern.findall(html)
print(f"递归匹配: {matches}")
# 5. 集合操作
print("\n5. 集合操作:")
text = "abc123汉字🚀"
# 匹配数字或字母
matches = regex.findall(r'[[:alnum:]]', text)
print(f"字母数字: {matches}")
# 6. 分支重置
print("\n6. 分支重置:")
text = "日期: 2023-01-15 或 2023/01/15"
# 使用分支重置组,组号在不同分支中保持一致
pattern = r'(\d{4})(?|-(0[1-9]|1[0-2])-([0-2][0-9]|3[01])|/(0[1-9]|1[0-2])/([0-2][0-9]|3[01]))'
matches = regex.findall(pattern, text)
print(f"分支重置匹配: {matches}")
regex_library_demo()
总结
这篇文章的重点还是在加密那里,是非常重要的知识点,我们阅读文章的时候不需要将所有的知识点都牢记,代码还是更多的在编写上,写的多了熟悉就好了,我们可以有一个简单的记忆,等到后面用的时候也知道一个方向,然后利用AI快速的解决问题。