Security 安全模块
主动调用型安全工具集,提供 SQL 注入、XSS、命令注入、路径穿越、SSRF、XXE、CRLF、NoSQL 注入检测,响应敏感数据检测与脱敏,文件类型验证,以及 AES-256-GCM / RSA-2048 加解密、SHA-256 哈希、HMAC-SHA256,和带 TTL 自动轮转的 Key Store 管理。
初始化(可选)
// 以默认配置初始化(可省略,首次调用时自动懒初始化)
Vostok.Security.init();
// 自定义配置
Vostok.Security.init(new VKSecurityConfig()
.strictMode(true) // 严格模式:启用更多检测规则
.auditLog(true) // 开启安全审计日志
.riskThreshold(VKSecurityRiskLevel.HIGH) // 仅 HIGH 才判为不安全
.allowMultiStatement(false) // 禁止多语句(;分隔)
.allowCommentToken(false) // 禁止 SQL 注释符
.maxSqlLength(8192) // SQL 最大长度
);
// 强制重新初始化
Vostok.Security.reinit(new VKSecurityConfig().strictMode(true));
// 查询初始化状态
boolean ready = Vostok.Security.started();
SQL 注入检测
基于规则引擎检测,内置 5 条规则:长度限制、控制字符、注释符、多语句、关键字模式(OR 1=1、UNION SELECT、sleep、load_file、xp_cmdshell 等)。支持自定义规则扩展。
检测方法
// 完整检测(返回 VKSqlCheckResult)
VKSqlCheckResult result = Vostok.Security.checkSql(userInput);
if (!result.isSafe()) {
String reason = result.getReasons().toString();
String rule = result.getMatchedRules().toString();
VKSecurityRiskLevel level = result.getRiskLevel(); // LOW / MEDIUM / HIGH
int score = result.getScore(); // 数值风险分
throw new SecurityException("SQL injection: " + reason);
}
// 带参数检测(参数值也纳入扫描)
VKSqlCheckResult r = Vostok.Security.checkSql("SELECT * FROM users WHERE id = ?", userId);
// 规范化 SQL(检测后可直接使用)
String normalizedSql = result.getNormalizedSql();
// 便捷方法:只返回 boolean
boolean safe = Vostok.Security.isSafeSql(userInput);
// 断言式:不安全时直接抛 VKSecurityException
Vostok.Security.assertSafeSql(userInput);
自定义 SQL 规则
// 实现 VKSecurityRule 接口
VKSecurityRule noExecRule = new VKSecurityRule() {
@Override public String name() { return "no-exec"; }
@Override public VKSecurityFinding apply(VKSecurityContext ctx) {
// ctx.getScannedSql() 是字面量已屏蔽、全小写的 SQL
if (ctx.getScannedSql().contains("exec")) {
return new VKSecurityFinding("no-exec", VKSecurityRiskLevel.HIGH, 9, "EXEC keyword detected");
}
return null; // null 表示通过
}
};
Vostok.Security.registerRule(noExecRule);
Vostok.Security.registerRules(List.of(rule1, rule2)); // 批量注册
Vostok.Security.clearCustomRules(); // 清除所有自定义规则
// 查看当前激活的所有规则名称
List<String> rules = Vostok.Security.listRules();
VKSecurityContext 提供的 SQL 变体:
| 方法 | 说明 |
|---|---|
getRawSql() | 调用方传入的原始 SQL |
getNormalizedSql() | 空白规范化后的 SQL |
getScannedSql() | 字符串字面量已屏蔽、全小写,用于模式匹配 |
getParams() | SQL 参数数组(克隆副本) |
getConfig() | 当前 VKSecurityConfig |
XSS 检测与净化
检测 script 标签、事件处理器(on*=)、javascript: 协议、iframe 等。对 URL 编码执行两轮解码以防绕过。
// 检测(返回 VKSecurityCheckResult)
VKSecurityCheckResult r = Vostok.Security.checkXss(input);
if (!r.isSafe()) {
r.getRiskLevel(); // VKSecurityRiskLevel
r.getReasons(); // List<String>
r.getMatchedRules(); // List<String>
}
// 断言式:不安全时抛 VKSecurityException
Vostok.Security.assertSafeXss(input);
// 净化:HTML 实体编码(& < > " ' ` 转义),使危险字符失效
String clean = Vostok.Security.sanitizeXss(userHtml);
命令注入检测
检测 Shell 元字符(; & | ` $(...) \n \r)和危险命令(rm -rf、curl、wget、nc、bash -c、powershell 等)。危险命令与元字符同时出现评为 HIGH(9分),单独出现评为 MEDIUM(6分)。
VKSecurityCheckResult r = Vostok.Security.checkCommandInjection(userInput);
if (!r.isSafe()) { ... }
Vostok.Security.assertSafeCommand(userInput); // 断言式
路径穿越检测
检测 ../(及 Windows ..\ 形式)和空字节(%00 / \0)。对 URL 编码最多循环解码 3 轮以防多层编码绕过。
VKSecurityCheckResult r = Vostok.Security.checkPathTraversal(filePath);
if (!r.isSafe()) { ... }
Vostok.Security.assertSafePath(filePath); // 断言式
SSRF 检测
检测私有 IP(RFC 1918:10.x、172.16-31.x、192.168.x)、localhost、IPv6 回环(::1)、云元数据地址(169.254.169.254),以及危险协议(file://、gopher://、dict://、ldap://、sftp:// 等)。对 URL 进行解码预处理以防编码绕过。
VKSecurityCheckResult r = Vostok.Security.checkSsrf(url);
if (!r.isSafe()) { ... }
Vostok.Security.assertSafeSsrf(url); // 断言式
XXE 检测
检测 XML 中的外部实体注入风险:外部资源引用(file://、http:// 等)、SYSTEM / PUBLIC 关键字、实体声明(<!ENTITY)、含内部子集的 DOCTYPE。按风险等级依次:外部资源引用 > SYSTEM/PUBLIC > 实体 > DOCTYPE。
VKSecurityCheckResult r = Vostok.Security.checkXxe(xmlContent);
if (!r.isSafe()) { ... }
Vostok.Security.assertSafeXxe(xmlContent); // 断言式
CRLF 注入检测
检测 HTTP 响应头分割攻击:原始 CR/LF 字符、URL 编码形式(%0d / %0a)、双重编码(%250d 等)。最多循环解码 3 轮。
VKSecurityCheckResult r = Vostok.Security.checkCrlf(headerValue);
if (!r.isSafe()) { ... }
Vostok.Security.assertSafeCrlf(headerValue); // 断言式
NoSQL 注入检测
针对 MongoDB,检测 $where JavaScript 注入(HIGH 9 分)、JSON 对象中的操作符注入如 {"$ne": null}(HIGH 8 分),以及独立 NoSQL 操作符(MEDIUM 6 分)。
VKSecurityCheckResult r = Vostok.Security.checkNoSqlInjection(mongoQuery);
if (!r.isSafe()) { ... }
Vostok.Security.assertSafeNoSqlInjection(mongoQuery); // 断言式
文件类型检测
通过魔数(文件头字节)识别文件真实类型,防止伪装后缀上传。支持识别:PNG、JPEG、GIF、PDF、ZIP、GZIP、ELF、PE(Windows 可执行)、Mach-O、Java Class、Script。
// 检测文件真实类型(通过魔数,不依赖扩展名)
VKFileType type = Vostok.Security.detectFileType(fileBytes);
// VKFileType: UNKNOWN / PNG / JPEG / GIF / PDF / ZIP / GZIP / ELF / PE / MACH_O / JAVA_CLASS / SCRIPT
// 校验是否为允许的类型(防伪装后缀上传)
VKSecurityCheckResult r = Vostok.Security.checkFileMagic(fileBytes, VKFileType.JPEG, VKFileType.PNG);
if (!r.isSafe()) {
throw new SecurityException("Disallowed file type: " + r.getReasons());
}
// 检测是否为可执行脚本(通过扩展名 + 魔数 + 内容特征;文件名含空字节时拒绝)
VKSecurityCheckResult script = Vostok.Security.checkExecutableScriptUpload(fileName, fileBytes);
if (!script.isSafe()) { ... }
响应脱敏
内置检测:邮箱、手机号(中国大陆)、身份证号(中国)、银行卡号。支持注册自定义正则。
// 检测响应体是否包含敏感信息
VKSecurityCheckResult r = Vostok.Security.checkSensitiveResponse(responseJson);
if (!r.isSafe()) {
r.getMatchedRules(); // 匹配到的规则名(如 "email"、"phone-cn")
}
// 对响应体敏感字段自动打码(返回脱敏后的字符串)
String safeJson = Vostok.Security.maskSensitiveResponse(responseJson);
// 注册自定义敏感模式(正则)
Vostok.Security.registerSensitivePattern("\\b[A-Z]{2}\\d{7}\\b"); // 护照号示例
// 清除所有自定义敏感模式
Vostok.Security.clearSensitivePatterns();
AES 加解密
使用 AES-256-GCM 模式,每次加密随机生成 IV,密钥支持 Base64 编码的原始密钥或任意字符串(自动 SHA-256 派生)。
// 生成 AES-256 密钥(Base64 编码)
String keyBase64 = Vostok.Security.generateAesKey();
// 加密(secret 可以是 Base64 密钥或任意字符串,内部自动派生)
String cipher = Vostok.Security.encrypt("hello world", keyBase64);
// 解密
String plain = Vostok.Security.decrypt(cipher, keyBase64);
RSA 加解密与签名
使用 RSA-2048 / OAEP-SHA256 加密,SHA256withRSA 签名。密钥以 PEM 格式保存。
// 生成 RSA-2048 密钥对(PEM 格式)
VKRsaKeyPair pair = Vostok.Security.generateRsaKeyPair();
String pubKeyPem = pair.getPublicKeyPem();
String privKeyPem = pair.getPrivateKeyPem();
// 公钥加密 / 私钥解密
String cipher = Vostok.Security.encryptByPublicKey("hello", pubKeyPem);
String plain = Vostok.Security.decryptByPrivateKey(cipher, privKeyPem);
// 私钥签名 / 公钥验证(RSA-SHA256)
String sig = Vostok.Security.sign("message", privKeyPem);
boolean ok = Vostok.Security.verify("message", sig, pubKeyPem);
Hash 与 HMAC
// SHA-256 哈希(Base64 输出)
String hashB64 = Vostok.Security.sha256("password");
// SHA-256 哈希(Hex 输出,小写十六进制)
String hashHex = Vostok.Security.sha256Hex("password");
// HMAC-SHA256(Base64 输出;secret 为密钥字符串)
String mac = Vostok.Security.hmacSha256("message", secretKey);
Key Store(密钥存储)
基于本地文件系统的密钥存储,支持 AES 密钥和 RSA 密钥对的持久化、TTL 自动轮转,以及 Key Wrapping(使用 KEK 加密保护 DEK)。
初始化
Vostok.Security.initKeyStore(new VKKeyStoreConfig()
.baseDir("./keys") // 密钥存储目录(默认 ~/.vostok/keystore)
.masterKey("my-master-secret") // 主密钥(用于加密存储的密钥文件,务必修改默认值)
.autoCreate(true) // 目录不存在时自动创建(默认 true)
);
masterKey 默认值为 "vostok-default-master-key-change-me",生产环境必须替换为随机高熵字符串,否则存储的密钥文件形同明文。
基础操作
// 按 keyId 获取或创建 AES 密钥(Base64 编码字符串)
String aesKey = Vostok.Security.getOrCreateAesKey("aes-key-v1");
// 按 keyId 获取或创建 RSA 密钥对
VKRsaKeyPair pair = Vostok.Security.getOrCreateRsaKeyPair("rsa-key-v1");
// 轮转:生成新密钥并替换旧密钥
Vostok.Security.rotateAesKey("aes-key-v1");
Vostok.Security.rotateRsaKeyPair("rsa-key-v1");
TTL 自动轮转
// 检查密钥是否超过 TTL(基于文件修改时间)
boolean expired = Vostok.Security.isExpiredAesKey("aes-key-v1", 86400L); // TTL 单位:秒
boolean expired = Vostok.Security.isExpiredRsaKeyPair("rsa-key-v1", 86400L);
// 带 TTL 的获取/创建(超过 TTL 时自动轮转,透明返回新密钥)
String aesKey = Vostok.Security.getOrCreateAesKey("aes-key-v1", 86400L);
VKRsaKeyPair pair = Vostok.Security.getOrCreateRsaKeyPair("rsa-key-v1", 86400L);
Key Wrapping(vk2 格式)
使用 KEK(Key Encryption Key)加密保护 DEK(Data Encryption Key),加密结果携带 keyId 和 KEK 版本号,解密时自动查找对应版本的 KEK。
// 使用指定 keyId 的 AES 密钥加密明文(vk2 格式密文,携带 keyId + KEK 版本)
String cipherPayload = Vostok.Security.encryptWithKeyId("sensitive data", "aes-key-v1");
// 解密 vk1/vk2 格式密文(自动从 payload 中提取 keyId 和 KEK 版本)
String plain = Vostok.Security.decryptWithKeyId(cipherPayload);
// 轮转 KEK(追加新版本,保留历史版本以便解密旧密文,无需重加密)
Vostok.Security.rotateKek("aes-key-v1");
文件加解密
基于 AES-256-GCM + DEK/KEK 双层密钥(Key Wrapping),支持任意类型文件(文本、图片、PDF、ZIP 等二进制文件)的流式加解密。每次加密随机生成一次性 DEK,由 KeyStore 中的 KEK 包裹后写入文件头,KEK 轮换后历史文件无需重加密即可继续解密。
encryptWithKeyId / decryptWithKeyId 仅适用于短文本字段(数据库字段、Token、配置项等),内部以 String 传递明文,不适合二进制文件。文件加解密专用接口直接操作字节流,二进制安全,加密与解密均采用 1 MB 分块流式处理,内存消耗恒定,不受文件大小限制。
vkf2 文件格式(当前默认)
加密文件以 VKFC 魔数开头,文件头携带解密所需的全部元数据,正文采用 1 MB 分块结构:
┌─────────────────────────────────────────────────────────────────┐
│ 4 bytes Magic: 'V','K','F','C' │
│ 1 byte 版本号: 0x02 │
│ 1 byte keyId 字节长度(uint8,max 255) │
│ N bytes keyId(UTF-8) │
│ 8 bytes kekVersion(int64 big-endian) │
│ 2 bytes wrappedDek 字节长度(uint16 big-endian) │
│ M bytes wrappedDek(AES-256-GCM 加密后的 DEK) │
├─────────────────────────────────────────────────────────────────┤
│ [分块正文,循环直到终止符] │
│ 4 bytes chunk_len(int32,密文长度,含 16 字节 GCM 标签) │
│ 12 bytes chunk_iv(SecureRandom 每块独立生成) │
│ chunk_len bytes AES-256-GCM 密文 + 认证标签 │
│ ...(每块最大 1 MB 明文 + 16 字节标签) │
├─────────────────────────────────────────────────────────────────┤
│ 4 bytes 终止符: 0x00000000 │
└─────────────────────────────────────────────────────────────────┘
vkf1 格式(遗留,只读兼容)
版本号 0x01 的旧格式使用单个 GCM 实例覆盖全文,decryptFile / decryptStream 自动识别并回退至 v1 解密路径,无需任何手动干预。新加密操作始终写出 vkf2 格式。
┌─────────────────────────────────────────────────────────────────┐
│ [与 vkf2 相同的文件头,版本号 0x01] │
├─────────────────────────────────────────────────────────────────┤
│ 12 bytes GCM IV(全文唯一) │
│ rest AES-256-GCM 密文(末尾含 16 字节 GCM 认证标签) │
└─────────────────────────────────────────────────────────────────┘
Path 接口(推荐)
// 初始化 KeyStore(首次调用前,或与 encryptWithKeyId 共享同一 KeyStore)
Vostok.Security.initKeyStore(new VKKeyStoreConfig()
.baseDir("./keys")
.masterKey("my-master-secret"));
// 加密文件(任意类型:文本、图片、PDF 等)
Vostok.Security.encryptFile(
Path.of("report.pdf"), // 明文源文件
Path.of("report.vkf"), // 加密目标文件(自动创建或覆盖)
"file-key-v1" // keyId:对应 KeyStore 中的 KEK
);
// 解密文件(自动从文件头提取 keyId 和 KEK 版本,无需传入密钥;支持 vkf2/vkf1 格式)
Vostok.Security.decryptFile(
Path.of("report.vkf"), // 加密源文件(vkf2 或 vkf1 遗留格式)
Path.of("report_dec.pdf") // 明文目标文件(失败时不写入任何字节)
);
// KEK 轮换后,旧文件仍可直接解密(无需重加密)
Vostok.Security.rotateKek("file-key-v1");
Vostok.Security.decryptFile(Path.of("report.vkf"), Path.of("report_dec.pdf"));
Stream 接口
// 流式加密(适合从网络流、MultipartFile 等非文件来源加密)
try (InputStream in = request.getInputStream();
OutputStream out = Files.newOutputStream(encryptedPath)) {
Vostok.Security.encryptStream(in, out, "upload-key");
}
// 流式解密(in/out 均不会被方法内部关闭)
try (InputStream in = Files.newInputStream(encryptedPath);
OutputStream out = response.getOutputStream()) {
Vostok.Security.decryptStream(in, out);
}
vkf1(遗留格式):单 GCM 实例覆盖全文,解密时仍需将密文全部读入内存,对大文件需确保 JVM 堆充裕。建议将 v1 文件重新加密为 v2 格式。
原子写保护:
decryptFile(Path, Path) 内部先解密到临时文件(.vktmp.{nanoTime}),成功后原子替换目标文件;认证失败或 IO 错误时临时文件自动删除,目标文件不会写入任何字节。
数据库字段加密(vkf3)
专为数据库列存储设计的字段级加解密方案。采用 AES-256-GCM + DEK/KEK 双层密钥,支持多版本 DEK 持久化、可搜索 Blind Index、Session 批量操作和自描述解密(解密时无需传入 columnKeyId)。
每个字段独立 columnKeyId,独立 DEK,密钥完全隔离。一个字段的密钥轮换或泄露不会影响其他字段。当通过 Data 模块集成时,columnKeyId 默认由 Data 模块按 表名-列名 自动推导(如 users-phone);直接调用 Security API 时,columnKeyId 由调用方显式传入。
initKeyStore() 初始化,再配置字段加密选项并注册 columnKeyId。
vkf3 密文格式
┌──────────────────────────────────────────────────────┐
│ 1 byte 版本标识: 0x03 │
│ 4 bytes keyIdHash SHA-256(columnKeyId)[0:4],大端 │
│ 4 bytes dekVersion int32 大端 │
│ 12 bytes GCM IV(SecureRandom 独立生成) │
│ N bytes AES-256-GCM 密文 + 16字节认证标签 │
└──────────────────────────────────────────────────────┘
整体 Base64 编码后存入数据库列,固定 37 字节最小开销。
初始化与配置
// 初始化 KeyStore(字段加密依赖 KeyStore)
Vostok.Security.initKeyStore(new VKKeyStoreConfig()
.baseDir("./keys")
.masterKey("my-master-secret")
);
// 配置字段加密选项(可选,不调用则使用默认配置)
Vostok.Security.configureFieldEncrypt(new VKFieldEncryptConfig()
.dekCacheTtlSeconds(300) // DEK 缓存 TTL(秒),0 = 禁用缓存
.blindKeyIdSuffix(".blind") // Blind Key 文件名后缀
.nullPolicy(VKNullPolicy.NULL_PASSTHROUGH) // null 值处理策略
);
// 注册 columnKeyId(建立密钥哈希映射,支持自描述解密;可在 fieldSession 内自动触发)
Vostok.Security.registerColumnKey("users");
Session API(推荐)
Session 绑定单个 columnKeyId,内部复用缓存的 DEK 和 Blind Key,适合批量加解密场景。Session 实现 AutoCloseable,建议配合 try-with-resources 使用。
try (VKFieldSession session = Vostok.Security.fieldSession("users")) {
// 加密字符串字段
String cipher = session.encrypt(phoneNumber);
// 解密(自动识别 DEK 版本,支持跨版本解密)
String plain = session.decrypt(cipher);
// 可搜索 Blind Index(HMAC-SHA256 + Blind Key → 64 字符 hex)
String idx = session.blindIndex(phoneNumber);
// 类型安全加密(自动序列化为 UTF-8 字节)
String encDate = session.encryptTyped(localDate, VKFieldType.LOCAL_DATE);
LocalDate date = (LocalDate) session.decryptTyped(encDate, VKFieldType.LOCAL_DATE);
// 重加密(DEK 轮换后迁移旧密文,纯字节路径不经 String 中转)
String newCipher = session.reEncrypt(oldCipher);
}
静态便捷 API
// 加密字段(String → vkf3 Base64 密文)
String cipher = Vostok.Security.encryptField(plain, "users");
// 自描述解密(从密文头解析 columnKeyId,无需传入)
String plain = Vostok.Security.decryptField(cipher);
// 可搜索 Blind Index
String idx = Vostok.Security.blindIndex(phoneNumber, "users");
// 类型安全加密 / 解密
String encVal = Vostok.Security.encryptTyped(bigDecimalAmount, "orders", VKFieldType.BIG_DECIMAL);
Object val = Vostok.Security.decryptTyped(encVal, VKFieldType.BIG_DECIMAL);
DEK 轮换与迁移
// 轮换 DEK(生成下一版本;旧密文仍可解密,新加密使用新版本)
Vostok.Security.rotateDek("users");
// 批量重加密(用旧 DEK 解密后立即用当前 DEK 重加密,fail-fast)
List<String> oldCiphers = /* 从数据库读取 */ new ArrayList<>();
List<String> newCiphers = Vostok.Security.reEncryptFields(oldCiphers, "users");
// 失效 DEK 缓存(KEK 轮换后调用,强制从 KeyStore 重新加载)
Vostok.Security.invalidateDekCache("users");
Vostok.Security.invalidateAllDekCache(); // 失效全部 columnKeyId 的缓存
vkf3 密文头携带
keyIdHash(columnKeyId 的 SHA-256 前 4 字节)。Session 解密时校验密文的 keyIdHash 与本 Session 的 columnKeyId 是否一致,不匹配则抛 VKSecurityException(防止用字段 A 的 Session 解密字段 B 的密文)。
VKFieldType 枚举
| 枚举值 | 入参类型 | fromBytes 返回类型 | 序列化方式 |
|---|---|---|---|
STRING | String | String | UTF-8 |
INTEGER | Number | Integer | 十进制字符串 UTF-8 |
LONG | Number | Long | 十进制字符串 UTF-8 |
DOUBLE | Number | Double | toString() UTF-8 |
BIG_DECIMAL | BigDecimal | BigDecimal | toString() UTF-8 |
LOCAL_DATE | LocalDate | LocalDate | yyyy-MM-dd UTF-8 |
LOCAL_DATE_TIME | LocalDateTime | LocalDateTime | ISO 格式 UTF-8 |
BOOLEAN | Boolean | Boolean | "1"/"0" UTF-8 |
BYTES | byte[] | byte[] | 直接,无中转 |
VKFieldEncryptConfig 配置参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| dekCacheTtlSeconds | int | 300 | DEK 内存缓存 TTL(秒);0 = 禁用缓存,每次操作均从 KeyStore 加载 |
| blindKeyIdSuffix | String | ".blind" | Blind Key 文件名后缀(追加到 columnKeyId 后),更改后历史 Blind Index 失效 |
| nullPolicy | VKNullPolicy | NULL_PASSTHROUGH | NULL_PASSTHROUGH(null 输入返回 null);REJECT(null 输入抛异常) |
字段加密 API 速查
| 方法 | 返回值 | 说明 |
|---|---|---|
configureFieldEncrypt(VKFieldEncryptConfig) | void | 配置字段加密选项(可多次调用;未调用则使用默认配置) |
registerColumnKey(columnKeyId) | void | 注册 columnKeyId,建立哈希映射(自描述解密所需) |
fieldSession(columnKeyId) | VKFieldSession | 创建绑定到 columnKeyId 的 Session(AutoCloseable) |
encryptField(plain, columnKeyId) | String | 加密字符串字段,返回 vkf3 Base64 密文 |
decryptField(cipher) | String | 自描述解密(从密文头自动识别 columnKeyId) |
blindIndex(plain, columnKeyId) | String | 计算可搜索 Blind Index(HMAC-SHA256,64 字符 hex) |
encryptTyped(value, columnKeyId, VKFieldType) | String | 类型安全加密(自动序列化) |
decryptTyped(cipher, VKFieldType) | Object | 类型安全解密(自动反序列化) |
rotateDek(columnKeyId) | void | 轮换 DEK(生成下一版本,旧密文仍可解密) |
reEncryptFields(ciphers, columnKeyId) | List<String> | 批量重加密(fail-fast,1:1 映射) |
invalidateDekCache(columnKeyId) | void | 失效指定 columnKeyId 的 DEK + Blind Key 缓存 |
invalidateAllDekCache() | void | 失效全部 DEK + Blind Key 缓存 |
检测结果类型
VKSecurityCheckResult(通用检测结果)
XSS、命令注入、路径穿越、SSRF、XXE、CRLF、NoSQL、响应脱敏、文件类型检测均返回此类型。
| 方法 | 返回值 | 说明 |
|---|---|---|
isSafe() | boolean | 是否安全 |
getRiskLevel() | VKSecurityRiskLevel | 风险等级:LOW / MEDIUM / HIGH |
getScore() | int | 数值风险分(越高越危险) |
getReasons() | List<String> | 检测发现的原因描述列表 |
getMatchedRules() | List<String> | 命中的规则名称列表 |
VKSqlCheckResult(SQL 专用结果)
继承 VKSecurityCheckResult 的所有方法,额外提供:
| 方法 | 返回值 | 说明 |
|---|---|---|
isSafe() | boolean | 是否安全 |
getRiskLevel() | VKSecurityRiskLevel | 风险等级 |
getScore() | int | 数值风险分 |
getNormalizedSql() | String | 空白规范化后的 SQL(可直接用于后续处理) |
getReasons() | List<String> | 原因描述列表 |
getMatchedRules() | List<String> | 命中的规则名称列表 |
配置参数
VKSecurityConfig
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| enabled | boolean | true | 是否启用安全模块检测 |
| strictMode | boolean | false | 严格模式:启用额外检测规则(如更多 SQL 关键字) |
| allowMultiStatement | boolean | false | 是否允许 SQL 多语句(; 分隔) |
| allowCommentToken | boolean | false | 是否允许 SQL 注释符(-- /* */ #) |
| maxSqlLength | int | 10000 | SQL 最大允许长度(超出触发 sql-length 规则) |
| riskThreshold | VKSecurityRiskLevel | MEDIUM | 判定为不安全的最低风险等级(低于此等级的结果视为安全) |
| builtinRulesEnabled | boolean | true | 是否启用内置 SQL 检测规则 |
| failOnInvalidInput | boolean | true | 输入非法(如 null 或超长)时是否直接判为不安全 |
| auditLog | boolean | false | 是否开启安全审计日志 |
| whitelistPatterns | List<String> | — | 白名单正则:匹配时跳过检测 |
| blacklistPatterns | List<String> | — | 黑名单正则:匹配时直接判为不安全 |
VKKeyStoreConfig
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| baseDir | String | ~/.vostok/keystore | 密钥文件存储目录 |
| masterKey | String | vostok-default-master-key-change-me | 主密钥,用于加密存储的密钥文件,生产环境必须替换 |
| autoCreate | boolean | true | 目录不存在时是否自动创建 |
API 速查
生命周期
| 方法 | 返回值 | 说明 |
|---|---|---|
init() | void | 以默认配置初始化(幂等) |
init(VKSecurityConfig) | void | 以自定义配置初始化(幂等) |
reinit(VKSecurityConfig) | void | 强制重新初始化 |
started() | boolean | 是否已初始化 |
config() | VKSecurityConfig | 获取当前配置副本 |
close() | void | 关闭安全模块 |
注入检测
| 方法 | 返回值 | 说明 |
|---|---|---|
checkSql(sql) | VKSqlCheckResult | SQL 注入检测 |
checkSql(sql, params...) | VKSqlCheckResult | SQL 注入检测(含参数) |
isSafeSql(sql) | boolean | SQL 安全快速检查 |
assertSafeSql(sql) | void | SQL 安全断言,不安全时抛 VKSecurityException |
checkXss(input) | VKSecurityCheckResult | XSS 检测 |
assertSafeXss(input) | void | XSS 安全断言 |
sanitizeXss(input) | String | XSS 净化(HTML 实体编码) |
checkCommandInjection(input) | VKSecurityCheckResult | 命令注入检测 |
assertSafeCommand(input) | void | 命令注入安全断言 |
checkPathTraversal(path) | VKSecurityCheckResult | 路径穿越检测 |
assertSafePath(path) | void | 路径穿越安全断言 |
checkSsrf(url) | VKSecurityCheckResult | SSRF 检测 |
assertSafeSsrf(url) | void | SSRF 安全断言 |
checkXxe(xml) | VKSecurityCheckResult | XXE 检测 |
assertSafeXxe(xml) | void | XXE 安全断言 |
checkCrlf(headerValue) | VKSecurityCheckResult | CRLF 注入检测 |
assertSafeCrlf(headerValue) | void | CRLF 安全断言 |
checkNoSqlInjection(input) | VKSecurityCheckResult | NoSQL 注入检测 |
assertSafeNoSqlInjection(input) | void | NoSQL 安全断言 |
文件与响应
| 方法 | 返回值 | 说明 |
|---|---|---|
detectFileType(bytes) | VKFileType | 通过魔数检测文件真实类型 |
checkFileMagic(bytes, types...) | VKSecurityCheckResult | 校验文件类型是否在允许列表中 |
checkExecutableScriptUpload(fileName, bytes) | VKSecurityCheckResult | 检测是否为可执行脚本上传 |
checkSensitiveResponse(payload) | VKSecurityCheckResult | 检测响应体中的敏感信息 |
maskSensitiveResponse(payload) | String | 响应体敏感信息打码 |
registerSensitivePattern(regex) | void | 注册自定义敏感信息正则 |
clearSensitivePatterns() | void | 清除所有自定义敏感模式 |
加解密与哈希
| 方法 | 返回值 | 说明 |
|---|---|---|
generateAesKey() | String | 生成 AES-256 密钥(Base64 编码) |
encrypt(plain, secret) | String | AES-256-GCM 加密 |
decrypt(cipher, secret) | String | AES-256-GCM 解密 |
generateRsaKeyPair() | VKRsaKeyPair | 生成 RSA-2048 密钥对(PEM 格式) |
encryptByPublicKey(plain, pubPem) | String | RSA 公钥加密(OAEP-SHA256) |
decryptByPrivateKey(cipher, privPem) | String | RSA 私钥解密 |
sign(text, privPem) | String | RSA-SHA256 签名(Base64) |
verify(text, sig, pubPem) | boolean | RSA-SHA256 签名验证 |
sha256(text) | String | SHA-256 哈希(Base64 输出) |
sha256Hex(text) | String | SHA-256 哈希(Hex 输出) |
hmacSha256(text, secret) | String | HMAC-SHA256(Base64 输出) |
Key Store
| 方法 | 返回值 | 说明 |
|---|---|---|
initKeyStore(VKKeyStoreConfig) | void | 初始化密钥存储 |
getOrCreateAesKey(keyId) | String | 获取或创建 AES 密钥(Base64) |
getOrCreateAesKey(keyId, ttlSeconds) | String | 获取或创建 AES 密钥(超过 TTL 自动轮转) |
isExpiredAesKey(keyId, ttlSeconds) | boolean | 检查 AES 密钥是否超过 TTL |
rotateAesKey(keyId) | void | 轮转 AES 密钥(生成新密钥替换旧密钥) |
getOrCreateRsaKeyPair(keyId) | VKRsaKeyPair | 获取或创建 RSA 密钥对 |
getOrCreateRsaKeyPair(keyId, ttlSeconds) | VKRsaKeyPair | 获取或创建 RSA 密钥对(超过 TTL 自动轮转) |
isExpiredRsaKeyPair(keyId, ttlSeconds) | boolean | 检查 RSA 密钥对是否超过 TTL |
rotateRsaKeyPair(keyId) | void | 轮转 RSA 密钥对 |
encryptWithKeyId(plain, keyId) | String | Key Wrapping 加密(vk2 格式,含 keyId + KEK 版本) |
decryptWithKeyId(cipherPayload) | String | Key Wrapping 解密(自动提取 keyId 和 KEK 版本) |
rotateKek(keyId) | void | 轮转 KEK(追加新版本,历史版本保留以解密旧密文) |
文件加解密
| 方法 | 返回值 | 说明 |
|---|---|---|
encryptFile(src, dst, keyId) | void | 加密文件(Path → Path),vkf2 分块流式 AES-256-GCM + Key Wrapping,二进制安全,适合任意类型文件 |
decryptFile(src, dst) | void | 解密 vkf2(或 vkf1 遗留)格式文件(Path → Path),自动从文件头提取 keyId 和 KEK 版本;先写临时文件,成功后原子替换目标,失败时目标不写入任何字节 |
encryptStream(in, out, keyId) | void | 流式加密(InputStream → OutputStream),in/out 不会被关闭 |
decryptStream(in, out) | void | 流式解密(InputStream → OutputStream),in/out 不会被关闭 |
规则管理
| 方法 | 返回值 | 说明 |
|---|---|---|
registerRule(VKSecurityRule) | void | 注册单条自定义 SQL 安全规则 |
registerRules(List<VKSecurityRule>) | void | 批量注册自定义规则(只触发一次扫描器重建) |
clearCustomRules() | void | 清除所有自定义规则 |
listRules() | List<String> | 查询当前激活的所有规则名称 |