简介:一份围绕国密 SM2、SM3 算法落地的 Java 加解密实践包,面向需要在项目中接入国密能力、又不想从零梳理依赖与工具类的开发者,也适合作为密码学课程实验或接口加密改造的参考。压缩包共 5 个文件,以 4 个 Java 源码和 1 个 jar 依赖为主:源码分别承担文件与字符串的加解密处理、SM3 摘要计算及密钥对象封装,jar 则提供 BouncyCastle 相关密码学支撑,整体约 4.89MB,结构精简。已有 428 人学习,说明其在国密入门与快速验证场景中具备一定参考价值。读者可直接获得可运行的 SM2/SM3 字符与文件加解密示例,理解公私钥使用、摘要校验与文件流处理的衔接方式,并借助随包依赖减少环境配置与版本排查成本,便于二次改造到实际业务或实验项目中。
1. 从字符串到文件:SM2/SM3 国密加解密 jar 包解决的是交付前的最后一公里
SM2/SM3 国密字符文件加解密 jar 包这类资源,最容易翻车的地方不是算法不会,而是拿到 zip 后不知道先从哪下手:jar 是可执行工具还是纯库、字符集和密文格式怎么定、文件大了会不会 OOM。它解决的是交付前最后一公里——接口参数要国密签名、日志字段要脱敏、文件流转要摘要校验时,手里有一套能离线跑、能嵌进 Java 工程的实现。适合后端开发、交付实施和需要做国密改造的测试同学;不适合想拿它替代完整密钥管理体系的人。先把它当工具拆,再当依赖接,顺序反了就会在类名和参数上耗掉半天。
2. 先分清 SM2 与 SM3:哈希、加密、签名三条线的选型与最小回环
2.1 SM3 是摘要不是加密:完整性校验的输入边界
SM3 的输出是固定长度摘要,用来判断一段字节有没有被改过,不是拿来还原明文的。字符场景里,同一个“合同编号=HT-001”用 UTF-8 和 GBK 编码得到的是两组不同字节,SM3 结果自然对不上;文件场景里,文本模式读写可能把换行改掉,摘要也会变。我一般先固定“输入按 UTF-8 无 BOM 转字节,摘要输出统一小写 Hex”,再谈对接。下面这段先确认 jar 有没有命令行入口,避免把纯库当工具跑。
# 把文件名替换成 zip 里实际的 jar 名 unzip -l sm2sm3-tool.jar | sed -n '1,30p' unzip -p sm2sm3-tool.jar META-INF/MANIFEST.MF # 若 MANIFEST 里有 Main-Class,再尝试帮助入口 java -jar sm2sm3-tool.jar --help这段命令做三件事:先看 jar 顶层目录和类文件分布,再读 MANIFEST 判断有没有Main-Class,最后用--help探测参数风格。参数上,unzip -p只读不落盘,适合快速看清单;java -jar依赖 MANIFEST 的入口类,没有入口就会报“no main manifest attribute”,这不是 jar 坏了,而是它本来给代码调用。若帮助里出现sm3、digest、file这类子命令,说明资源包同时带离线工具能力。
import java.nio.charset.StandardCharsets; public class Sm3CheckDemo { public static void main(String[] args) throws Exception { // 业务字符串先固定 UTF-8,避免平台默认编码干扰 byte[] data = "orderId=HT-001&amount=99.00".getBytes(StandardCharsets.UTF_8); // Sm3Util 为示意类名,按 jar 实际导出的工具类替换 String hex = Sm3Util.digestHex(data); System.out.println(hex.toLowerCase()); } }这段代码只做一件事:把字符串按 UTF-8 转成字节后算 SM3,再输出小写 Hex。参数上要注意toLowerCase()的位置,有的工具类返回大写,双方比对前先统一;如果业务要求带盐,盐也要参与字节拼接,不能只对原文算摘要。运行时报NoSuchAlgorithmException或找不到Sm3Util,先回到 jar 的类列表里确认工具类名,不要硬猜包名。文件摘要不要Files.readAllBytes,用 8KB 到 64KB 缓冲流式喂给摘要接口,内存占用才稳。
提示:SM3 只能验证“有没有变”,不能证明“谁发的”。要证明来源,得用 SM2 签名;要保密,得用 SM2 加密或封装密钥。
2.2 SM2 加密和签名不是一回事:密钥用途与输出格式先定死
SM2 是非对称算法,公钥加密、私钥解密是一条线;私钥签名、公钥验签是另一条线。实际项目里最常见的误用是拿加密密钥对去验签,或者把公钥当私钥塞进解密接口,结果不是报错就是解出乱码。字符加密还要先定密文格式:C1C2C3 还是 C1C3C2、Base64 还是 Hex、有没有换行。我一般要求对接双方在联调前写死这三项,并保留一组固定明文的回环结果,后面出问题先跑回环,别一上来就怀疑算法。
import java.nio.charset.StandardCharsets; public class Sm2RoundTripDemo { public static void main(String[] args) throws Exception { // 生成一次密钥对,公钥给加密方,私钥留在解密方 Sm2KeyPair kp = Sm2Util.generateKeyPair(); String pubHex = kp.getPublicKeyHex(); String priHex = kp.getPrivateKeyHex(); String text = "hello 国密"; // 密文统一走 Base64,避免 Hex 大小写和换行争议 String cipher = Sm2Util.encryptBase64(text.getBytes(StandardCharsets.UTF_8), pubHex); byte[] plain = Sm2Util.decryptBase64(cipher, priHex); System.out.println(new String(plain, StandardCharsets.UTF_8)); } }这段代码把“生成密钥、加密、解密”串成最小回环。参数上,公钥和私钥都用 Hex 字符串保存时,要确认有没有0x前缀、长度是否固定、大小写是否敏感;密文用 Base64 时,传输层不要自动折行,否则解密端读到的空格和换行会直接破坏密文。Sm2Util、Sm2KeyPair是示意类名,实际以 jar 导出的类为准。若 jar 只提供byte[]接口,就在业务层统一做 Base64 编解码,不要把编码逻辑散落在各个调用点。
SM2 直接加密大文件并不合适:非对称运算慢,密文还会膨胀,逐块加密还要处理块边界和随机数。常见做法是 SM2 只加密一个随机对称密钥,文件主体走对称算法,SM3 做完整性校验;如果资源包只给 SM2/SM3,文件加密要么用 jar 自带的文件方法,要么自己封装分块协议,并把块序号、块长度、摘要写进密文头。这个边界先想清楚,后面才不会把“能解小字符串”误当成“能扛大文件”。
2.3 最小回环:固定输入跑通摘要、加密、解密、验签
拿到资源包后,不要直接接业务表。先准备一组固定输入:一段纯 ASCII 字符串、一段中文、一个 1KB 左右的二进制文件、一个空文件。每个输入跑四件事:SM3 摘要、SM2 加密再解密、SM2 签名再验签、把输出落盘再读回。回环通过后再换真实密钥和真实字段。这个习惯能提前暴露编码、换行、类名和依赖问题,比在联调现场看日志快得多。
# 以 jar 支持子命令为前提,具体子命令以 --help 为准 java -jar sm2sm3-tool.jar sm3 --in vectors/ascii.txt --out out/ascii.sm3 java -jar sm2sm3-tool.jar sm2-encrypt --in vectors/cn.txt --out out/cn.enc --pub keys/pub.hex java -jar sm2sm3-tool.jar sm2-decrypt --in out/cn.enc --out out/cn.dec --pri keys/pri.hex cmp vectors/cn.txt out/cn.dec这段脚本把“摘要、加密、解密、比对”跑成一条链。参数上,--in和--out的路径最好用绝对路径或固定工作目录,避免从不同目录启动时找不到文件;cmp返回非零就说明回环失败,先看解密文件大小和头几个字节,再查密钥和密文格式。若 jar 没有这些子命令,就把同样的输入交给 Java 探针类跑,命令行和代码两条路至少通一条,后面排查才有对照。
注意:签名验签失败不一定是密钥错,签名原文的拼接顺序、时间戳、URL 编码方式都会改变结果。先把签名原文打印成 Hex,再和对方逐字节比对。
3. 拆开 zip 再动手:MANIFEST、依赖和类名这三处先查清
3.1 先看 MANIFEST 和目录:判断是可执行工具还是纯库
zip 里带 jar 不等于能双击运行。先看 MANIFEST 的Main-Class和Class-Path,再看 jar 内有没有META-INF/services之类的扩展声明。没有Main-Class就按库用,有Main-Class也未必能直接跑,因为Class-Path可能指向同目录的依赖 jar。下面这几条命令能把结构、入口和候选类一次看清,避免把时间花在猜包名上。
# 1) 看 zip 内文件分布,确认 jar 和依赖是否同级 unzip -l sm2sm3.zip | sed -n '1,80p' # 2) 读 MANIFEST,重点看 Main-Class 和 Class-Path unzip -p sm2sm3-tool.jar META-INF/MANIFEST.MF # 3) 列类文件,过滤 sm2/sm3 关键字,找候选工具类 jar tf sm2sm3-tool.jar | grep -Ei 'sm2|sm3|util|tool' | sed -n '1,80p'第一步看 zip 里是不是“一个主 jar 加几个依赖 jar”,如果依赖散落在lib/,运行时就要把lib/*一起放进 classpath。第二步的Main-Class决定java -jar能不能用,Class-Path里的相对路径是相对 jar 所在目录,不是相对当前工作目录,移动 jar 后依赖可能断。第三步用关键字过滤类名,找到候选工具类后再用javap看方法签名,别直接照着类名猜参数顺序。
# 用 javap 看候选类的方法签名,包名按上一步结果替换 javap -classpath sm2sm3-tool.jar your.package.Sm2Sm3Tooljavap的输出会列出构造方法、公开方法和参数类型。重点看加密方法是encrypt(byte[], String)还是encrypt(String, String),返回的是byte[]还是 Base64 字符串。参数顺序差一位,编译能过但运行结果完全不对,这类问题在字符加密里很常见。若类太多,先找名字里带Util、Helper、Tool的类,再看哪个类同时有sm3和sm2相关方法。
3.2 用最小 Java 工程挂载 jar:classpath、依赖和入口类
确认是库之后,建一个最小工程,只放一个Demo.java和一个lib/目录。编译和运行都把 jar 放进 classpath,不要解压 jar 后直接引用散落的 class 文件,否则 MANIFEST 和依赖关系会丢。路径分隔符按当前命令行环境调整,冒号还是分号不要写死。先跑通“能加载类”,再跑“能调用方法”,顺序不能反。
# 目录示意:demo/src/Demo.java、demo/lib/sm2sm3-tool.jar javac -encoding UTF-8 -cp "lib/*" -d out src/Demo.java java -cp "out:lib/*" Demo这段命令里,-encoding UTF-8防止源码里的中文注释在编译时出问题;-cp "lib/*"让编译器一次找到 lib 下所有 jar;-d out把 class 输出到独立目录,避免污染源码目录。运行时的out:lib/*是“先找自己的类,再找依赖 jar”,如果当前环境用分号,就把冒号换掉。报ClassNotFoundException时先打印System.getProperty("java.class.path"),确认运行时的 classpath 和编译时一致,再查类名是否写错。
public class Demo { public static void main(String[] args) throws Exception { // 先只加载类,确认 jar 在 classpath 里可见 Class<?> clazz = Class.forName("your.package.Sm2Sm3Tool"); System.out.println(clazz.getName()); } }这个探针类只做类加载,不碰业务逻辑。类能加载出来,说明 jar 和包名对上了;加载失败就回到 jar 里核对包路径,注意包名大小写。很多人把your.package直接抄进代码却忘了替换,编译能过,运行才报错。类加载通过后再加方法调用,问题范围会小很多。
3.3 写一个探针类:反射列出方法,确认加密参数顺序
jar 没有文档时,反射比猜快。把候选类的方法名、参数类型、返回类型打印出来,再对照业务需求选方法。尤其是“加密字符串”和“加密文件”可能分别在不同类里,参数里可能带charset、mode、iv之类的配置对象。先列方法,再决定传什么,不要看到一个encrypt就往上套。
import java.lang.reflect.Method; public class MethodProbe { public static void main(String[] args) throws Exception { Class<?> clazz = Class.forName("your.package.Sm2Sm3Tool"); for (Method m : clazz.getDeclaredMethods()) { // 只打印公开方法,避免被私有实现干扰 if (java.lang.reflect.Modifier.isPublic(m.getModifiers())) { System.out.println(m.getName() + " -> " + m.getReturnType().getSimpleName()); for (Class<?> p : m.getParameterTypes()) { System.out.println(" param: " + p.getName()); } } } } }这段反射代码输出方法名、返回类型和参数类型。参数里出现byte[]往往说明它只处理原始字节,字符集要调用方自己转;出现String且方法名带Base64,多半已经做了编码。返回类型是void的方法可能把结果写进输出流或文件,要看参数里有没有OutputStream、Path。反射只用于探路,正式代码还是编译期调用,别把反射留在生产链路里。
提示:如果 jar 加了混淆,方法名可能是
a、b,这时优先看参数类型和类名,再用一组固定输入试跑,不要靠名字硬猜。
4. 字符与文件加解密落地:UTF-8、Base64、分块和临时文件的完整链路
4.1 字符加解密:统一 UTF-8、Base64 和密文格式
字符加解密的第一步不是调加密方法,而是把“字符串怎么变字节”定死。业务字段里可能有中文、表情、换行,平台默认编码不同就会得到不同字节。我的做法是:入口处统一getBytes(StandardCharsets.UTF_8),出口处统一new String(bytes, StandardCharsets.UTF_8),密文统一 Base64 字符串,落盘时用 UTF-8 写文本。这样命令行、Java 代码、接口传输三边的输入字节是一致的,出问题才能用同一组字节对照。
import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; public class StringRoundTrip { public static void main(String[] args) throws Exception { String text = "合同编号=HT-001;金额=100.00"; byte[] plain = text.getBytes(StandardCharsets.UTF_8); String pub = Files.readString(Path.of("keys/sm2_pub.hex"), StandardCharsets.UTF_8).trim(); String pri = Files.readString(Path.of("keys/sm2_pri.hex"), StandardCharsets.UTF_8).trim(); // 加密后直接得到 Base64 密文,避免中间再转 Hex String cipher = Sm2Util.encryptBase64(plain, pub); Files.writeString(Path.of("out/cipher.txt"), cipher, StandardCharsets.UTF_8); // 读回时先去空白,再解密,防止编辑器自动换行 String readBack = Files.readString(Path.of("out/cipher.txt"), StandardCharsets.UTF_8).trim(); byte[] decrypted = Sm2Util.decryptBase64(readBack, pri); String back = new String(decrypted, StandardCharsets.UTF_8); System.out.println(back.equals(text)); } }这段代码把字符加密、落盘、读回、解密串起来。参数上,trim()只去掉首尾空白,不能去掉密文中间的换行;如果密文可能被邮件或聊天工具折行,读回后要先做replaceAll("\\s+", "")再 Base64 解码。Sm2Util是示意类名,方法名按 jar 实际接口替换。输出比对用equals,不要用==;中文内容建议再打印一次字节长度,确认没有 BOM 或多余换行混进去。
字符场景还有一个容易忽略的点:加密结果的长度。SM2 密文包含 C1、C2、C3 三段,不同实现输出的顺序可能不同,密文长度也会随明文长度变化。如果数据库字段按“明文长度乘 2”设计,很可能存不下。落库前先拿最长业务字段跑一遍,确认字段类型和长度;接口传输时确认网关有没有对 Base64 里的+、/、=做二次转义。
4.2 文件加解密:分块、临时文件和完整性校验
文件加解密先看 jar 有没有现成的文件方法。有encryptFile(Path in, Path out, String key)这类接口就用它,外面包一层 SM3 校验;没有就自己用流式分块,绝不能Files.readAllBytes把整个文件读进内存。下面这段先做“加密前摘要、调用文件加密、解密、解密后摘要比对”的闭环,方法名按实际 jar 替换。临时文件放在独立目录,成功后原子替换,失败就保留现场便于排查。
import java.nio.file.Files; import java.nio.file.Path; public class FileRoundTrip { public static void main(String[] args) throws Exception { Path src = Path.of("data/input.bin"); Path enc = Path.of("out/input.bin.sm2"); Path dec = Path.of("out/input.bin.dec"); String pub = Files.readString(Path.of("keys/sm2_pub.hex")).trim(); String pri = Files.readString(Path.of("keys/sm2_pri.hex")).trim(); // 加密前先算 SM3,解密后用它判断文件内容是否一致 String before = Sm3Util.digestFileHex(src); Sm2FileUtil.encryptFile(src, enc, pub); // 按 jar 实际文件方法替换 Sm2FileUtil.decryptFile(enc, dec, pri); // 按 jar 实际文件方法替换 String after = Sm3Util.digestFileHex(dec); if (!before.equalsIgnoreCase(after)) { throw new IllegalStateException("SM3 mismatch: " + before + " / " + after); } System.out.println("round trip ok"); } }这段代码的关键不是加密方法,而是“摘要在前、比对在后”。参数上,digestFileHex要流式读文件,不能一次读全;encryptFile和decryptFile的路径要分开,别让输出覆盖输入;equalsIgnoreCase兼容大小写 Hex。如果 jar 只提供byte[]文件接口,就自己写 8KB 缓冲循环,边读边写,最后再算摘要。大文件还要看磁盘剩余空间,SM2 密文通常比明文大,临时文件可能占双份空间。
import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; public class ChunkCopyDemo { public static void main(String[] args) throws Exception { Path in = Path.of("data/large.bin"); Path out = Path.of("out/large.copy"); try (InputStream is = Files.newInputStream(in); OutputStream os = Files.newOutputStream(out)) { byte[] buf = new byte[8192]; int n; while ((n = is.read(buf)) > 0) { // 这里可以替换成逐块加密或逐块摘要更新 os.write(buf, 0, n); } } } }这段是分块读写的骨架,缓冲区 8KB 是折中值,太小系统调用多,太大内存占用高。真正做逐块 SM2 加密时,每块密文要带块序号和块长度,否则解密端不知道边界;逐块加密还会带来密文膨胀和随机数消耗,文件很大时要先算性能账。更稳的方案是“SM2 加密文件密钥,对称算法加密文件主体,SM3 校验密文或明文”,但资源包只给 SM2/SM3 时,就要把分块协议写清楚,别让后续维护的人靠猜。
4.3 命令行方式:把 jar 当离线工具用
交付现场经常没有 IDE,命令行能跑通就很值钱。先--help看子命令,再用固定输入跑一遍,最后写循环批量处理。批量脚本要处理文件名带空格、失败退出码和日志留存,不要一条命令跑完所有文件却不看返回码。下面这段以“jar 支持 sm2-encrypt 子命令”为前提,实际参数以帮助输出为准。
set -e mkdir -p out logs for f in ./inbox/*.txt; do base=$(basename "$f") # 单文件失败就退出,避免批量任务带着坏结果继续跑 java -jar sm2sm3-tool.jar sm2-encrypt \ --in "$f" \ --out "out/${base}.enc" \ --pub keys/sm2_pub.hex >> "logs/${base}.log" 2>&1 done这段脚本用set -e让单文件失败时停止,basename保留原文件名,输出统一进out/,日志按文件分开。参数上,--pub指向公钥文件,路径不要用相对当前目录的模糊写法;如果文件名可能以-开头,命令里加--分隔。批量跑完先抽一个文件解密比对,不要只看“脚本没报错”就认为全部成功。
注意:命令行工具的工作目录会影响相对路径。定时任务里先
cd到固定目录,或者全部用绝对路径,否则手动能跑、定时失败。
5. 避坑与排查:密钥、编码、大文件和 jar 依赖的 5 个血泪坑
5.1 解密报 invalid cipher text 或解出乱码
现象:加密端能出密文,解密端一跑就报密文非法,或者不报错但解出来是乱码。原因通常有三类:密文段顺序不一致,C1C2C3 和 C1C3C2 被混用;Base64 传输中被折行或转义,解码后字节已经变了;加密端对明文做了压缩或二次编码,解密端却按原始字节解。解决时先把密文读成字节打印头 16 个字节的 Hex,再和加密端输出的头 16 个字节比对;确认 Base64 解码后的长度一致,再去查段顺序。联调前把“段顺序、编码、是否压缩”写进对接约定,比事后猜快。
5.2 同一字符串 SM3 摘要两边不一致
现象:同一段中文,一边算出的 SM3 是a1...,另一边是b2...,肉眼看着字符串一模一样。原因多半是字符集不同,或者文本末尾多了换行、BOM、空格。解决时不要只打印字符串,打印getBytes(StandardCharsets.UTF_8).length和前 8 个字节的 Hex;文件摘要用二进制流读,不要用文本模式。双方约定“UTF-8 无 BOM、不自动追加换行、摘要输出小写 Hex”,再跑一组纯 ASCII 和一组中文测试向量。若只有中文不一致,优先查编码;若纯 ASCII 也不一致,查有没有加盐或拼接顺序不同。
5.3 本地能跑,服务器报 ClassNotFoundException
现象:开发机java -jar正常,放到服务器就报找不到类,或者NoClassDefFoundError。原因常见于依赖 jar 没一起上传、MANIFEST 里的Class-Path是相对路径、启动脚本的工作目录不同、运行环境里的 Java 版本不匹配。解决时先打印java.class.path,确认依赖 jar 是否在列表里;再用jar tf确认缺失的类到底在哪个 jar;最后检查启动脚本有没有cd到 jar 所在目录。用java -cp "lib/*:app.jar" 主类这种显式 classpath 比依赖 MANIFEST 更可控,尤其是在定时任务和容器里。
5.4 大文件加密 OOM 或速度极慢
现象:几百 MB 的文件一加密就内存溢出,或者 CPU 打满、进度条几乎不动。原因通常是一次性readAllBytes、逐块 SM2 加密导致密文膨胀和随机数频繁、临时文件写满磁盘。解决时改成 8KB 到 64KB 缓冲流式处理,输出先写临时文件,成功后再改名;如果 jar 只支持整段字节,就在外层限制文件大小并给出明确报错,不要静默失败。逐块 SM2 要记录块边界和块摘要,解密端按同样边界还原;能改成“SM2 封装对称密钥 + 对称算法加密文件”就优先改,SM3 只做完整性校验,别拿它当加密用。
5.5 签名验签通过但业务说验签失败
现象:自己用工具验签返回 true,业务方接过去一直 false。原因往往是签名原文拼接不一致:字段顺序、分隔符、时间戳、URL 编码、空值处理,任何一处不同都会改变原文。解决时把签名原文按字节打印 Hex,双方各跑同一组固定参数,先对齐原文再对齐签名值。签名值本身也要确认是 Base64 还是 Hex、有没有换行、大小写是否敏感。不要用“看起来一样”的字符串做判断,逐字节比对一次,后面能省很多沟通成本。
提示:避坑最有效的办法不是记更多错误码,而是保留一组最小固定输入和期望输出。每次换 jar、换环境、换密钥,先跑这组回环。
6. 进阶验证:固定测试向量和双跑校验怎么把国密工具用稳
固定测试向量不需要多复杂:一段纯 ASCII、一段中文、一个空文件、一个 1KB 二进制文件,再加一组固定的公钥私钥。每次换 jar 或换部署环境,先跑命令行,再跑 Java 调用,最后比对两边输出。命令行和代码双跑能快速区分“工具本身变了”还是“接入代码传参错了”。下面这段脚本把两边的 SM3 输出落盘后直接 diff,返回非零就停下来查。
set -e mkdir -p out/verify java -jar sm2sm3-tool.jar sm3 --in vectors/ascii.txt > out/verify/sm3.cli.txt java -cp "out:lib/*" VectorRunner > out/verify/sm3.java.txt diff -u out/verify/sm3.cli.txt out/verify/sm3.java.txt这段脚本的关键是set -e和diff:任何一步失败都不继续,输出不一致直接暴露。参数上,VectorRunner要读同一份vectors/ascii.txt,不要一边读文件一边读字符串;两边输出都做trim和小写统一,避免格式差异掩盖真实问题。SM2 的验证同理:命令行加密、代码解密,再反过来代码加密、命令行解密,四条路都通再接业务。
| 验证项 | 做法 | 通过标准 |
|---|---|---|
| SM3 字符摘要 | 同一字符串分别走命令行和 Java | Hex 完全一致 |
| SM3 文件摘要 | 同一文件流式读取计算 | 摘要一致且内存平稳 |
| SM2 字符回环 | 公钥加密、私钥解密 | 解密字节与原文一致 |
| SM2 文件回环 | 加密后解密再算 SM3 | 前后摘要一致 |
| 异常输入 | 空文件、错误密钥、截断密文 | 有明确报错,不静默成功 |
从那以后我每次交付国密加解密包,都先跑一遍固定向量和双跑比对,再谈接入业务;密钥、编码、密文格式三件事没写死之前,不往下写业务代码。希望帮到你。
本文还有配套的精品资源,点击获取