crypto 加密与编码
大约 12 分钟
crypto 加密与编码
JSXHook 当前推荐统一使用 crypto.*。老脚本里如果还在用 $crypto 或 $base64,现在仍然有兼容别名,但新文档都按 crypto 来写。
这一页我会把最容易踩坑的地方直接写死:
input到底能填什么。output到底能填什么。- 哪些字段有默认值,哪些没有。
- 算法写简写时,运行时会自动补成什么。
- 哪些场景必须写
iv、dest。
先记住这 8 条
digest()默认输出hex,encrypt()/decrypt()默认输出bytes。input只支持string、bytes、base64、hex、file。output只支持bytes、string、base64、hex、file。output: "file"时,dest必填。encoding默认是utf-8,写错编码名会回退到utf-8。AES、DES、RSA这种简写会自动补成完整 transformation。- 只有
CBC、CFB、CTR、OFB、GCM、PCBC这些模式才真正会用到iv。 - 十六进制输入支持
0x前缀和空白,但必须是偶数长度。
先看总览
crypto 里常用的接口
crypto.base64.encode(data, encoding?)crypto.base64.decode(base64Text, encoding?)crypto.digest(data, algorithm, options?)crypto.encrypt(data, key, algorithm, options?)crypto.decrypt(data, key, algorithm, options?)new crypto.Key(data, options?)new crypto.KeyPair(publicKey, privateKey, options?)crypto.generateKeyPair("RSA", 2048)
options 最常见的字段
{
input: "string", // string | bytes | base64 | hex | file
output: "hex", // bytes | string | base64 | hex | file
encoding: "utf-8", // 任意 Java Charset 名称
iv: "1234567890abcdef",
dest: "/sdcard/Download/out.bin",
algorithm: "RSA" // 主要给 Key / KeyPair 作为算法提示
}
输入输出格式总表
input 可填值
| 值 | 适用接口 | 说明 | 典型输入 |
|---|---|---|---|
string | 全部 | 把输入当普通文本,再按 encoding 转字节 | "hello" |
bytes | 全部 | 把输入当字节序列 | new Uint8Array(...) 风格不可直接写,常见写法是 ByteArray / 数字数组 / key.data |
base64 | digest / encrypt / decrypt / Key / KeyPair | 先按 Base64 解码,再参与后续处理 | "SGVsbG8=" |
hex | digest / encrypt / decrypt / Key / KeyPair | 先按十六进制解码,再参与后续处理 | "48656c6c6f" |
file | digest / encrypt / decrypt / Key / KeyPair | 把输入值当文件路径,直接读取文件内容 | "/sdcard/in.bin" |
output 可填值
| 值 | 适用接口 | 返回结果 | 典型用途 |
|---|---|---|---|
bytes | digest / encrypt / decrypt | 原始字节数组 | 继续交给别的 API 处理 |
string | digest / encrypt / decrypt | 按 encoding 解码后的字符串 | 明文文本输出 |
base64 | digest / encrypt / decrypt | Base64 字符串 | 网络传输、JSON 存储 |
hex | digest / encrypt / decrypt | 十六进制字符串 | 日志、摘要、调试 |
file | digest / encrypt / decrypt | 把结果写入 dest 指向的文件,并返回路径字符串 | 文件落盘 |
自动推断规则
如果你不写 options.input,源码不是“一律按字符串”,而是按下面规则自动判断:
| 传入值类型 | 自动推断成什么 |
|---|---|
String | string |
null / undefined | 使用接口默认值,通常是 string |
| 其他类型 | bytes |
这意味着下面两种写法实际效果不同:
crypto.digest("abc", "MD5"); // 按 string 处理
crypto.digest([97, 98, 99], "MD5"); // 按 bytes 处理
crypto.base64.encode(data, encoding?)
参数
| 参数 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
data | any | 字符串、字节数组、数字数组、带 data 字段的对象等 | 无 | 内部会尽量转成字节 |
encoding | string | 任意 Java Charset 名称,如 utf-8、gbk、utf-16le | utf-8 | 只在“需要把文本转字节”时生效 |
返回
string
说明
- 如果
data本身已经是字节数组,会直接编码。 - 如果
data是字符串,会先按encoding编码,再做 Base64。 - 如果
encoding名称非法,会自动回退到utf-8。
例子
const a = crypto.base64.encode("Hello JSXHook");
const b = crypto.base64.encode("中文", "gbk");
const c = crypto.base64.encode([72, 101, 108, 108, 111]);
log(a);
log(b);
log(c);
crypto.base64.decode(base64Text, encoding?)
参数
| 参数 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
base64Text | any | 通常传 Base64 字符串 | 无 | 会先转成文本再解码 |
encoding | string | 任意 Java Charset 名称 | utf-8 | 用于把解码后的字节转成字符串 |
返回
string
说明
- 这个接口返回的是字符串,不是字节数组。
- 如果你想拿到字节结果,再继续做加解密,通常更适合在
encrypt / decrypt / digest里直接用input: "base64"。
例子
const encoded = crypto.base64.encode("Hello JSXHook");
const decoded = crypto.base64.decode(encoded);
log(encoded);
log(decoded);
crypto.digest(data, algorithm, options?)
参数
| 字段 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
data | any | 文本、字节、Base64、Hex、文件路径等 | 无 | 实际解释方式由 options.input 决定 |
algorithm | string | 常见如 MD5、SHA-1、SHA-256、SHA-512 | 必填 | 传空字符串会报错 |
options.input | string | string / bytes / base64 / hex / file | 自动推断 | 控制输入怎么转字节 |
options.output | string | bytes / string / base64 / hex / file | hex | 控制摘要结果怎么输出 |
options.encoding | string | 任意 Java Charset 名称 | utf-8 | 用于文本输入和 output: "string" |
options.dest | string | 文件路径 | 空字符串 | 只在 output: "file" 时必须填写 |
默认行为
crypto.digest("abc", "SHA-256");
等价于:
crypto.digest("abc", "SHA-256", {
input: "string",
output: "hex",
encoding: "utf-8"
});
例子 1:最常见的字符串摘要
const sha256 = crypto.digest("Hello JSXHook", "SHA-256");
log(sha256); // 默认就是 hex
例子 2:把结果直接输出成 Base64
const sha256Base64 = crypto.digest("Hello JSXHook", "SHA-256", {
output: "base64"
});
log(sha256Base64);
例子 3:读取文件计算 MD5
const fileMd5 = crypto.digest("/sdcard/Download/demo.apk", "MD5", {
input: "file",
output: "hex"
});
log(fileMd5);
例子 4:用十六进制作为输入
const result = crypto.digest("0x48656c6c6f", "SHA-1", {
input: "hex",
output: "hex"
});
log(result);
crypto.encrypt(data, key, algorithm, options?)
参数
| 字段 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
data | any | 文本、字节、Base64、Hex、文件路径等 | 无 | 实际解释方式由 options.input 决定 |
key | any | 见下方“key 能传什么” | 必填 | 不能为空 |
algorithm | string | AES、AES/CBC/PKCS5Padding、RSA、RSA/ECB/PKCS1Padding 等 | 必填 | 传空字符串会报错 |
options.input | string | string / bytes / base64 / hex / file | 自动推断 | 控制明文输入怎么读 |
options.output | string | bytes / string / base64 / hex / file | bytes | 控制密文怎么返回 |
options.encoding | string | 任意 Java Charset 名称 | utf-8 | 文本转字节或字节转文本时使用 |
options.iv | `string | byte[] | number[]` | 任意能转字节的值 |
options.dest | string | 文件路径 | 空字符串 | output: "file" 时必须填写 |
默认行为
crypto.encrypt("hello", key, "AES");
等价于“输入按字符串、输出按字节”,并且 AES 会自动补成:
"AES/ECB/PKCS5Padding"
key 能传什么
当前源码明确支持这些类型:
new crypto.Key(...)new crypto.KeyPair(...)- Java 原生
Key ByteArrayListArrayString- 带
data字段的对象
最稳妥的做法仍然是:
- 对称加密用
new crypto.Key(...) - 非对称加密用
crypto.generateKeyPair(...)或new crypto.KeyPair(...)
例子 1:AES 最简单写法
const key = new crypto.Key("0123456789abcdef");
const encrypted = crypto.encrypt("hello", key, "AES", {
output: "base64"
});
log(encrypted);
例子 2:AES/CBC 带 IV
const key = new crypto.Key("0123456789abcdef");
const encrypted = crypto.encrypt("payload", key, "AES/CBC/PKCS5Padding", {
iv: "1234567890abcdef",
output: "base64"
});
log(encrypted);
例子 3:把密文直接落盘
const key = new crypto.Key("0123456789abcdef");
const savedPath = crypto.encrypt("/sdcard/Download/in.bin", key, "AES", {
input: "file",
output: "file",
dest: "/sdcard/Download/out.enc"
});
log(savedPath);
crypto.decrypt(data, key, algorithm, options?)
参数
和 crypto.encrypt(...) 完全同一套规则,差别只在于这里传入的是“密文”。
默认行为
crypto.decrypt(cipherBytes, key, "AES");
默认等价于:
crypto.decrypt(cipherBytes, key, "AES", {
output: "bytes",
encoding: "utf-8"
});
例子 1:Base64 密文解回字符串
const key = new crypto.Key("0123456789abcdef");
const encrypted = crypto.encrypt("hello", key, "AES", {
output: "base64"
});
const plain = crypto.decrypt(encrypted, key, "AES", {
input: "base64",
output: "string"
});
log(plain);
例子 2:AES/CBC 解密时也要保持同一个 IV
const key = new crypto.Key("0123456789abcdef");
const iv = "1234567890abcdef";
const encrypted = crypto.encrypt("payload", key, "AES/CBC/PKCS5Padding", {
iv,
output: "base64"
});
const plain = crypto.decrypt(encrypted, key, "AES/CBC/PKCS5Padding", {
input: "base64",
output: "string",
iv
});
log(plain);
例子 3:文件解密
const key = new crypto.Key("0123456789abcdef");
crypto.decrypt("/sdcard/Download/out.enc", key, "AES", {
input: "file",
output: "file",
dest: "/sdcard/Download/out.dec"
});
new crypto.Key(data, options?)
这个构造器适合创建“单把密钥”,常见于 AES、DES、Blowfish 这类对称加密,也可以拿来包装已经存在的公钥或私钥字节。
参数
| 字段 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
data | any | 字符串、字节、Base64、Hex、文件路径等 | 无 | 真正的密钥内容 |
options.input | string | string / bytes / base64 / hex / file | 自动推断 | 控制 data 怎么转字节 |
options.encoding | string | 任意 Java Charset 名称 | utf-8 | input: "string" 时生效 |
options.algorithm | string | 如 AES、RSA、EC | 空字符串 | 主要作为算法提示,方便后续推断 |
重要说明
new crypto.Key(...)内部最终保存的是字节内容。options.algorithm不是强校验字段,不会立刻做复杂合法性判断,它更像“提示信息”。- 构造出来的对象会暴露一个可读可写的
data字段。
例子 1:普通文本密钥
const key = new crypto.Key("0123456789abcdef");
log(String(key));
例子 2:从 Base64 构造密钥
const key = new crypto.Key("MDEyMzQ1Njc4OWFiY2RlZg==", {
input: "base64",
algorithm: "AES"
});
例子 3:从文件读取密钥
const key = new crypto.Key("/sdcard/keys/aes.key", {
input: "file",
algorithm: "AES"
});
修改 key.data
const key = new crypto.Key("old-key");
key.data = "new-key";
说明:
- 给
data重新赋值后,内部缓存的 Java Key 会被清掉并重新生成。 - 如果你要精确控制二进制内容,推荐直接赋字节数组或数字数组。
new crypto.KeyPair(publicKey, privateKey, options?)
这个构造器用于“你已经有一对密钥内容,只想包装成 JSXHook 能直接用的对象”。
参数
| 字段 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
publicKey | any | 字符串、字节、Base64、Hex、文件路径等 | 无 | 公钥内容 |
privateKey | any | 字符串、字节、Base64、Hex、文件路径等 | 无 | 私钥内容 |
options.input | string | string / bytes / base64 / hex / file | 自动推断 | 同时作用于公钥和私钥 |
options.encoding | string | 任意 Java Charset 名称 | utf-8 | 文本输入时生效 |
options.algorithm | string | RSA、DSA、DH、EC 等 | 空字符串 | 作为算法提示 |
例子 1:Base64 公私钥
const pair = new crypto.KeyPair(publicBase64, privateBase64, {
input: "base64",
algorithm: "RSA"
});
log(String(pair.publicKey));
log(String(pair.privateKey));
例子 2:从文件读取 PEM / DER 内容
const pair = new crypto.KeyPair(
"/sdcard/keys/public.der",
"/sdcard/keys/private.der",
{
input: "file",
algorithm: "RSA"
}
);
可读可写字段
pair.publicKeypair.privateKey
你也可以直接改:
pair.publicKey = newPublicBytes;
pair.privateKey = newPrivateBytes;
crypto.generateKeyPair(algorithm, length?)
参数
| 参数 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
algorithm | string | 常见是 RSA、DSA、DH、EC | 必填 | 会先做算法名归一化 |
length | number | 正整数 | 按算法自动给默认值 | 位数或曲线强度 |
默认长度
| 算法 | 默认值 |
|---|---|
RSA | 2048 |
DSA | 2048 |
DH | 2048 |
EC | 256 |
| 其他 | 256 |
说明:
- 传入的
length小于1时,最终会被修正到至少1。 - 是否真正支持某个算法,最终还取决于 Android / JCE 提供者本身。
例子
const rsaPair = crypto.generateKeyPair("RSA", 2048);
const ecPair = crypto.generateKeyPair("EC");
log(String(rsaPair.publicKey));
log(String(ecPair.privateKey));
算法简写与自动补全
常见简写会补成什么
| 你传入的值 | 实际使用的 transformation |
|---|---|
AES | AES/ECB/PKCS5Padding |
DES | DES/ECB/PKCS5Padding |
DESede | DESede/ECB/PKCS5Padding |
RSA | RSA/ECB/PKCS1Padding |
Blowfish | Blowfish/ECB/PKCS5Padding |
如果你传的是完整 transformation
例如:
"AES/CBC/PKCS5Padding"
"RSA/ECB/PKCS1Padding"
这种情况下,运行时基本会原样使用,只会把前面的算法基名标准化一下。
支持的算法名归一化
| 你常见可能会写的值 | 归一化结果 |
|---|---|
AES | AES |
RSA | RSA |
DSA | DSA |
DH | DH |
EC | EC |
DES | DES |
DESEDE | DESede |
TRIPLEDES | DESede |
BLOWFISH | Blowfish |
iv 到底什么时候要写
并不是所有算法都需要 iv。
当前源码里,只要 transformation 包含下面这些模式之一,iv 才会真正参与初始化:
/CBC//CFB/CTR/OFB/GCM/PCBC
也就是说:
AES/ECB/PKCS5Padding不需要ivAES/CBC/PKCS5Padding需要ivAES/GCM/NoPadding需要iv
iv 推荐写法
| 写法 | 是否推荐 | 说明 |
|---|---|---|
"1234567890abcdef" | 推荐 | 最直观,适合 ASCII / UTF-8 字符串 IV |
[1, 2, 3, 4] | 推荐 | 需要精确控制字节时很好用 |
someByteArray | 推荐 | 已经有原始字节时最稳妥 |
注意:
iv字符串会按 UTF-8 转字节。- 如果算法需要 16 字节 IV,你就要自己保证传进去的字节数正确,源码不会替你自动补齐。
encoding 说明
可填什么
encoding 最终走的是 Java Charset.forName(...),所以理论上可以填任意合法 Charset 名称,比如:
utf-8utf-16utf-16legbkgb2312iso-8859-1
写错会怎样
不会直接报错,而是自动回退到:
utf-8
最适合什么时候改它
- 你明确知道输入文本不是 UTF-8。
- 你在和旧系统、旧接口、旧文件格式对接。
十六进制输入规则
当你写 input: "hex" 时,源码会按下面规则处理:
- 会自动去掉空白字符。
- 支持
0x/0X前缀。 - 长度必须是偶数。
合法例子
"48656c6c6f"
"48 65 6c 6c 6f"
"0x48656c6c6f"
非法例子
"abc"
这种会报错:
Hex string must have even length
文件输入输出规则
input: "file"
- 传入值必须是文件路径字符串。
- 运行时会直接读取这个文件的全部字节。
- 文件不存在时会抛文件相关异常。
output: "file"
dest必须填写且不能为空。- 运行时会自动创建目标文件的父目录。
- 返回值是目标路径字符串,不是字节数组。
例子
const out = crypto.digest("/sdcard/in.bin", "SHA-256", {
input: "file",
output: "file",
dest: "/sdcard/out.sha256"
});
log(out);
常见错误与排查
1. Unsupported input type: ...
原因:
options.input写成了不支持的值。
只能填:
string / bytes / base64 / hex / file
2. Unsupported output type: ...
原因:
options.output写成了不支持的值。
只能填:
bytes / string / base64 / hex / file
3. output=file requires dest
原因:
- 你写了
output: "file",但没写dest。
4. encrypt requires algorithm / decrypt requires algorithm / digest requires algorithm
原因:
- 算法名为空字符串。
5. 解密失败但没有明显提示
最常见的排查顺序:
- 算法是不是和加密端完全一致。
input/output写法是不是配套。iv是不是一样。- key 的真实字节是不是一样。
- 文本编码是不是一致。
实战建议
- 文本数据优先用
output: "base64"或output: "hex",方便日志和网络传输。 - 文件场景优先用
input: "file"/output: "file",不用自己手动读写字节。 - 对称密钥尽量用
new crypto.Key(...)包一下,再传给encrypt / decrypt。 - 非对称场景优先用
crypto.generateKeyPair("RSA", 2048)起步,最省心。 - 只要你看到“同一个值到底是字符串还是字节不确定”,就显式写
input/output,不要依赖自动推断。
如果你还在补脚本里的历史用法,可以顺手对照 内置手册全量示例,里面已经把运行环境附带的很多演示脚本整理成了可检索页面。
