主动调用型
Security 模块不会自动接入 Data / Web 执行链路,需要在业务代码中显式调用各检测方法。检测方法均为无状态纯静态调用,线程安全。

初始化(可选)

// 以默认配置初始化(可省略,首次调用时自动懒初始化)
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 -rfcurlwgetncbash -cpowershell 等)。危险命令与元字符同时出现评为 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");
Key Wrapping 优势
轮转 KEK 只需更新密钥加密密钥,历史密文无需重新加密即可继续解密(旧版本 KEK 保留在 KeyStore 中)。 适合需要频繁轮转密钥、同时要求旧密文依然可解密的场景。

文件加解密

基于 AES-256-GCM + DEK/KEK 双层密钥(Key Wrapping),支持任意类型文件(文本、图片、PDF、ZIP 等二进制文件)的流式加解密。每次加密随机生成一次性 DEK,由 KeyStore 中的 KEK 包裹后写入文件头,KEK 轮换后历史文件无需重加密即可继续解密。

与 encryptWithKeyId 的区别
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);
}
解密内存说明
vkf2(当前默认):每块独立 AES-GCM 解密,内存消耗约 1 MB + GCM 标签(16 字节),与文件总大小无关,可安全处理 GB 级文件。
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 由调用方显式传入。

前置条件
字段加密依赖 KeyStore,需先调用 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 的缓存
字段级密钥隔离与跨字段防护
每个 columnKeyId 拥有独立的 DEK,不同字段(或不同表的同名字段)之间密钥完全隔离。
vkf3 密文头携带 keyIdHash(columnKeyId 的 SHA-256 前 4 字节)。Session 解密时校验密文的 keyIdHash 与本 Session 的 columnKeyId 是否一致,不匹配则抛 VKSecurityException(防止用字段 A 的 Session 解密字段 B 的密文)。

VKFieldType 枚举

枚举值入参类型fromBytes 返回类型序列化方式
STRINGStringStringUTF-8
INTEGERNumberInteger十进制字符串 UTF-8
LONGNumberLong十进制字符串 UTF-8
DOUBLENumberDoubletoString() UTF-8
BIG_DECIMALBigDecimalBigDecimaltoString() UTF-8
LOCAL_DATELocalDateLocalDateyyyy-MM-dd UTF-8
LOCAL_DATE_TIMELocalDateTimeLocalDateTimeISO 格式 UTF-8
BOOLEANBooleanBoolean"1"/"0" UTF-8
BYTESbyte[]byte[]直接,无中转

VKFieldEncryptConfig 配置参数

参数类型默认值说明
dekCacheTtlSecondsint300DEK 内存缓存 TTL(秒);0 = 禁用缓存,每次操作均从 KeyStore 加载
blindKeyIdSuffixString".blind"Blind Key 文件名后缀(追加到 columnKeyId 后),更改后历史 Blind Index 失效
nullPolicyVKNullPolicyNULL_PASSTHROUGHNULL_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

参数类型默认值说明
enabledbooleantrue是否启用安全模块检测
strictModebooleanfalse严格模式:启用额外检测规则(如更多 SQL 关键字)
allowMultiStatementbooleanfalse是否允许 SQL 多语句(; 分隔)
allowCommentTokenbooleanfalse是否允许 SQL 注释符(-- /* */ #
maxSqlLengthint10000SQL 最大允许长度(超出触发 sql-length 规则)
riskThresholdVKSecurityRiskLevelMEDIUM判定为不安全的最低风险等级(低于此等级的结果视为安全)
builtinRulesEnabledbooleantrue是否启用内置 SQL 检测规则
failOnInvalidInputbooleantrue输入非法(如 null 或超长)时是否直接判为不安全
auditLogbooleanfalse是否开启安全审计日志
whitelistPatternsList<String>白名单正则:匹配时跳过检测
blacklistPatternsList<String>黑名单正则:匹配时直接判为不安全

VKKeyStoreConfig

参数类型默认值说明
baseDirString~/.vostok/keystore密钥文件存储目录
masterKeyStringvostok-default-master-key-change-me主密钥,用于加密存储的密钥文件,生产环境必须替换
autoCreatebooleantrue目录不存在时是否自动创建

API 速查

生命周期

方法返回值说明
init()void以默认配置初始化(幂等)
init(VKSecurityConfig)void以自定义配置初始化(幂等)
reinit(VKSecurityConfig)void强制重新初始化
started()boolean是否已初始化
config()VKSecurityConfig获取当前配置副本
close()void关闭安全模块

注入检测

方法返回值说明
checkSql(sql)VKSqlCheckResultSQL 注入检测
checkSql(sql, params...)VKSqlCheckResultSQL 注入检测(含参数)
isSafeSql(sql)booleanSQL 安全快速检查
assertSafeSql(sql)voidSQL 安全断言,不安全时抛 VKSecurityException
checkXss(input)VKSecurityCheckResultXSS 检测
assertSafeXss(input)voidXSS 安全断言
sanitizeXss(input)StringXSS 净化(HTML 实体编码)
checkCommandInjection(input)VKSecurityCheckResult命令注入检测
assertSafeCommand(input)void命令注入安全断言
checkPathTraversal(path)VKSecurityCheckResult路径穿越检测
assertSafePath(path)void路径穿越安全断言
checkSsrf(url)VKSecurityCheckResultSSRF 检测
assertSafeSsrf(url)voidSSRF 安全断言
checkXxe(xml)VKSecurityCheckResultXXE 检测
assertSafeXxe(xml)voidXXE 安全断言
checkCrlf(headerValue)VKSecurityCheckResultCRLF 注入检测
assertSafeCrlf(headerValue)voidCRLF 安全断言
checkNoSqlInjection(input)VKSecurityCheckResultNoSQL 注入检测
assertSafeNoSqlInjection(input)voidNoSQL 安全断言

文件与响应

方法返回值说明
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)StringAES-256-GCM 加密
decrypt(cipher, secret)StringAES-256-GCM 解密
generateRsaKeyPair()VKRsaKeyPair生成 RSA-2048 密钥对(PEM 格式)
encryptByPublicKey(plain, pubPem)StringRSA 公钥加密(OAEP-SHA256)
decryptByPrivateKey(cipher, privPem)StringRSA 私钥解密
sign(text, privPem)StringRSA-SHA256 签名(Base64)
verify(text, sig, pubPem)booleanRSA-SHA256 签名验证
sha256(text)StringSHA-256 哈希(Base64 输出)
sha256Hex(text)StringSHA-256 哈希(Hex 输出)
hmacSha256(text, secret)StringHMAC-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)StringKey Wrapping 加密(vk2 格式,含 keyId + KEK 版本)
decryptWithKeyId(cipherPayload)StringKey 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>查询当前激活的所有规则名称