Java 核心类型与错误
Java 核心类型与错误
Java 密码 API 处理 byte[]。String 重载只是把文本按某个 Charset 转为字节;协议 key、IV、密文和签名不能混用文本编码与二进制编码。
可执行案例
package cn.gmkit;
import cn.gmkit.core.Base64Codec;
import cn.gmkit.core.BcProviders;
import cn.gmkit.core.ByteEncodings;
import cn.gmkit.core.Bytes;
import cn.gmkit.core.GmSecurityContext;
import cn.gmkit.core.GmkitException;
import cn.gmkit.core.HexCodec;
import cn.gmkit.core.InputFormat;
import cn.gmkit.core.OutputFormat;
import cn.gmkit.core.Texts;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ManualJavaCoreTest {
@Test
void usesExplicitTextAndBinaryFormats() {
// 1. 准备二进制:该字节序列包含 NUL、非 ASCII 字节和字母 A。
byte[] binary = new byte[] {0x00, (byte) 0xff, (byte) 0x80, 0x41};
// 2. 编码二进制:协议字段分别输出为小写 Hex 和 RFC 4648 Base64。
String hex = ByteEncodings.encode(binary, OutputFormat.HEX);
String base64 = ByteEncodings.encode(binary, OutputFormat.BASE64);
assertEquals("00ff8041", hex);
assertEquals("AP+AQQ==", base64);
// 3. 显式解码:接收方按协议声明的格式恢复相同字节。
assertArrayEquals(binary, ByteEncodings.decode(hex, InputFormat.HEX, "payload"));
assertArrayEquals(binary, ByteEncodings.decode(base64, InputFormat.BASE64, "payload"));
// 4. UTF-8 往返:文本转换与任意二进制转换分开处理。
String plaintext = "订单 GMKIT-DEMO-0001";
assertEquals(plaintext, Texts.text(Texts.bytes(plaintext, StandardCharsets.UTF_8), StandardCharsets.UTF_8));
assertEquals(hex, HexCodec.encode(HexCodec.decodeStrict(hex, "payload")));
assertEquals(base64, Base64Codec.encode(Base64Codec.decode(base64, "payload")));
// 5. 比较失败断言:内容不同的认证值必须返回 false。
byte[] tampered = binary.clone();
tampered[3] ^= 0x01;
assertFalse(Bytes.constantTimeEquals(binary, tampered));
// 6. 非法输入断言:奇数长度或包含非 Hex 字符时必须抛出 GmkitException。
assertThrows(GmkitException.class, () -> HexCodec.decodeStrict("abc", "payload"));
assertThrows(GmkitException.class, () -> HexCodec.decodeStrict("00xz", "payload"));
// 7. 创建安全上下文:固定 Provider 和 SecureRandom,关闭自动全局注册。
GmSecurityContext context = GmSecurityContext.builder()
.provider(BcProviders.create())
.secureRandom(new SecureRandom())
.registerProvider(false)
.build();
assertFalse(context.registerProvider());
assertEquals("BC", context.provider().getName());
}
}数据类型
Texts.bytes(text, null) 和 Texts.text(bytes, null) 在 0.10.1 中回落到 UTF-8。主手册仍显式传 StandardCharsets.UTF_8,使代码审查能直接看出协议字符集。
编码入口
主手册不传 format = null。新 schema 必须携带 HEX 或 BASE64,接收端按该枚举解码;没有格式字段的历史数据见旧系统迁移。
数组所有权
byte[] 是可变对象。0.10.1 的选项和结果对象对公开 getter 使用防御性拷贝;调用方仍应:
- 不在加密调用进行时修改传入数组;
- 不把可变 key 数组跨请求共享给会改写它的代码;
- 在业务允许时清零临时 key 副本;
- 通过
Bytes.constantTimeEquals比较已解码的 MAC/tag。
安全上下文
GmSecurityContext 保存三个值:Provider、SecureRandom 和 registerProvider。Builder 的确切默认值是:
- Provider:
BcProviders.defaultProvider(); - 随机源:新的
SecureRandom(); registerProvider:true。
设置 registerProvider(false) 后,context.provider() 直接返回指定实例,不调用 Security.addProvider。这适合应用服务器与测试;算法通过 Provider 实例执行,不要求它出现在全局列表。
失败语义
应用层可以统一捕获密码失败,但不应把 key、明文、完整签名或内部异常堆栈返回给对端。
完整类型与方法见 Java core API。