尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Oracle 11g数据库内嵌SM4国密算法:从Java源码到SQL函数的完整实践

Oracle 11g数据库内嵌SM4国密算法:从Java源码到SQL函数的完整实践
📅 发布时间:2026/7/30 5:27:14

1. 项目概述与核心价值

最近在做一个老系统的安全加固项目,客户现场还在跑着Oracle 11g,数据安全合规要求上来了,明文存储敏感信息肯定是不行了。国密算法SM4是硬性要求,但总不能把数据全捞出来在应用层加密再存回去吧?那工程量太大了,而且历史数据迁移、实时加解密查询都是大麻烦。琢磨了一下,最理想的方案是把SM4算法直接“塞”进数据库里,让加密解密逻辑在数据库内部完成,这样应用层的SQL几乎不用大改,性能也更有保障。这个“Oracle 11g 数据库内嵌SM4算法”的想法,就是从实际需求里逼出来的。

简单说,这个实践的目标就是:在Oracle 11g数据库中,创建出原生的、可以被SQL直接调用的SM4加密解密函数。最终效果是,你可以在SQL语句里像用UPPER()、SUBSTR()这些内置函数一样,直接写SM4_ENCRYPT(‘明文’, ‘密钥’)或者SM4_DECRYPT(‘密文’, ‘密钥’)。这对于处理存量系统中的数据脱敏、字段级加密查询、以及满足合规要求,意义重大。尤其适合那些应用层改造困难、但又必须快速满足数据安全审计的老系统。

整个链路涉及几个关键环节:首先是找到可靠、标准的SM4算法Java实现;然后通过Oracle的JVM扩展机制,把Java代码“搬”进数据库;接着在PL/SQL层做一层包装,把Java方法暴露成SQL函数;最后还要考虑密钥管理、性能优化和实际调用中的各种坑。别看最终就是一个SQL函数,背后是一整套从源码到运行时环境的打通。下面,我就把这次从Java源码到SQL调用的完整实践过程,包括踩过的雷和总结的经验,详细拆解一遍。

2. 核心思路与方案选型背后的考量

为什么选择在数据库内嵌,而不是在应用层做?这得从几个维度权衡。应用层加密固然灵活,但意味着所有相关查询的SQL都要改,特别是那些模糊查询、范围查询,一旦加密就失效了,改造起来伤筋动骨。而数据库内嵌函数,相当于把加解密能力下沉为数据服务,对应用透明,原有查询逻辑(尤其是精确查询)可以最大程度保留。对于Oracle 11g这种老版本,虽然官方不直接支持SM4,但其内嵌的JVM(通常称为Oracle JVM或Aurora JVM)为我们提供了用Java扩展数据库功能的可能。

方案选型上,核心是利用Oracle的loadjava工具和CREATE JAVA SOURCE语句,将Java类加载到数据库的Java命名空间中。然后,通过编写PL/SQL包装函数,调用这些Java类的静态方法,最终使用CREATE FUNCTION将其创建为数据库函数。这条路线的优势在于,Java生态丰富,有大量经过验证的加密库实现(如Bouncy Castle),我们可以直接引入或参考其源码。同时,PL/SQL作为Oracle的“胶水语言”,调用Java非常成熟稳定。

这里有个关键决策点:是直接使用现成的JAR包,还是使用纯Java源码?对于生产环境,尤其是金融、政务等对组件来源有严格要求的场景,我强烈建议使用可审计的Java源码。直接loadjava一个来路不明的bcprov-jdk15on-xxx.jar,你很难说清楚里面有没有后门。因此,我们的实践基于一个从权威开源项目(如Bouncy Castle)中抽取、并稍作适配的纯净SM4算法Java源码文件。这样,从源码到二进制,都在可控范围内。

另一个考量是密钥管理。把密钥硬编码在Java源码或PL/SQL函数里是绝对的大忌。我们的方案是,将密钥作为函数的一个必传参数。加解密时由应用层传入,这样密钥不落库,符合安全规范。当然,这要求应用层妥善管理密钥。如果场景更复杂,可能需要结合Oracle的透明数据加密(TDE)或使用专门的密钥管理服务(KMS),但那又是另一个话题了。

3. 环境准备与SM4算法Java源码剖析

3.1 数据库环境确认与权限准备

首先,确保你的Oracle 11g(建议是11.2.0.4及以上版本)实例已经安装了JVM组件。可以连接上数据库,用以下SQL查询:

SELECT comp_name, version, status FROM dba_registry WHERE comp_id = ‘JAVAVM’;

如果STATUS是VALID,那就没问题。接下来,你需要一个具有足够权限的数据库用户来执行后续操作。通常需要CREATE PROCEDURE、CREATE JAVA以及JAVAUSERPRIV等权限。为了省事,我直接用有DBA权限的账号测试,但在生产环境,请遵循最小权限原则进行授权。

3.2 SM4算法Java源码获取与简化

SM4是一种分组密码算法,分组长度和密钥长度均为128位。我们需要其ECB和CBC模式下的加解密实现。Bouncy Castle库的源码是极佳的参考。但为了减少依赖和复杂度,我从一个精简的、无外部依赖的SM4实现开始。这个实现通常包含以下几个核心类:

  1. SM4_Context: 算法上下文,用于存储轮密钥等信息。
  2. SM4: 核心算法类,包含密钥扩展、加密解密轮函数。
  3. SM4Util: 工具类,提供对外的encrypt_ECB、decrypt_ECB、encrypt_CBC、decrypt_CBC等静态方法,处理字节数组与16进制字符串的转换。

这里有一个至关重要的实操心得:Oracle数据库内嵌的JVM版本通常较老(可能是JDK 1.5或1.6),且功能受限。因此,你找的Java源码必须使用纯Java SE API,不能依赖任何第三方库(如Apache Commons Codec),也不能使用高版本JDK的特性(如java.util.Base64)。对于Base64编码,要使用sun.misc.BASE64Encoder/Decoder(虽然不推荐,但在Oracle JVM里可用)或者自己实现一个简单的。我通常选择将加密后的字节数组直接转为十六进制字符串返回,这样最通用。

下面是一个极度精简的SM4Util类示例,它引用了核心的SM4类(代码已省略):

// SM4Util.java import java.util.Arrays; public class SM4Util { // 算法名称 public static final String ALGORITHM_NAME = “SM4”; // ECB模式加密,返回16进制字符串 public static String encrypt_ECB(String plainText, String secretKey) throws Exception { if (secretKey == null || secretKey.length() != 32) { // SM4密钥为128位,16字节,16进制表示是32字符 throw new IllegalArgumentException(“密钥长度必须为32位十六进制字符串 (128位)”); } byte[] keyData = hexStringToBytes(secretKey); byte[] srcData = plainText.getBytes(“UTF-8”); // 这里调用核心SM4类的ECB加密方法,返回加密后的字节数组 byte[] encrypted = SM4.encrypt_ECB_Padding(srcData, keyData); return bytesToHexString(encrypted); } // ECB模式解密 public static String decrypt_ECB(String cipherTextHex, String secretKey) throws Exception { byte[] keyData = hexStringToBytes(secretKey); byte[] srcData = hexStringToBytes(cipherTextHex); byte[] decrypted = SM4.decrypt_ECB_Padding(srcData, keyData); return new String(decrypted, “UTF-8”); } // 十六进制字符串转字节数组 private static byte[] hexStringToBytes(String hexString) { // ... 实现略 } // 字节数组转十六进制字符串 private static String bytesToHexString(byte[] bytes) { // ... 实现略 } }

注意: 在实际操作中,你需要一个完整的、可编译的SM4.java和SM4Util.java。务必确保其中的SM4类及其方法(如encrypt_ECB_Padding)都是public static的,这样才能被PL/SQL顺利调用。

4. 将Java源码加载至Oracle数据库

有了纯净的Java源码,下一步就是把它“注入”到Oracle里。有两种主流方式:loadjava命令行工具和CREATE JAVA SOURCE语句。我更喜欢用CREATE JAVA SOURCE,因为一切操作都在SQL*Plus或你喜欢的数据库客户端里完成,便于脚本化部署。

4.1 使用CREATE JAVA SOURCE加载

首先,将你的Java源码(比如SM4Util.java)内容读取出来,然后构造一个SQL。由于Java源码中包含大量单引号和换行符,直接拼SQL很痛苦。我的技巧是使用编程语言(如Python)或文本处理工具,将源码内容进行转义,并生成一个完整的SQL脚本文件。

假设我们有两个文件:SM4.java和SM4Util.java。你需要为每个文件执行一次CREATE JAVA SOURCE。下面以SM4Util.java为例,展示生成的SQL脚本样式:

— 首先,如果存在同名的旧Java类,先删除它(谨慎操作) — drop java source “SM4Util”; CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED “SM4Util” AS import java.util.Arrays; public class SM4Util { // … 这里是完整的SM4Util.java文件内容 … } /

关键点在于NAMED “SM4Util”,这个名字就是将来在数据库中引用这个Java类的名称。执行这段SQL,如果没有语法错误,Oracle就会在数据库内部编译这个Java类并将其存储起来。你可以通过USER_JAVA_CLASSES或ALL_JAVA_CLASSES视图查看加载的Java类及其状态。

4.2 编译状态检查与问题排查

执行完CREATE JAVA SOURCE后,务必检查编译状态:

SELECT dbms_java.longname(object_name) as long_name, status FROM user_objects WHERE object_type = ‘JAVA SOURCE’ OR object_type = ‘JAVA CLASS’;

如果STATUS是VALID,恭喜你,成功了。如果是INVALID,那就需要查看错误信息。使用以下语句获取详细的编译错误:

SELECT text FROM user_errors WHERE name = ‘SM4Util’ AND type = ‘JAVA SOURCE’ ORDER BY sequence;

常见问题1:字符集问题。如果Java源码文件是UTF-8 with BOM格式,或者包含中文字符,可能在编译时出现诡异错误。建议将源码文件保存为ASCII或UTF-8 without BOM格式,并且暂时去掉所有中文注释。

常见问题2:使用了不支持的API。这是最常踩的坑。Oracle数据库内的JVM是“沙箱”环境,很多java.lang、java.io、java.net包中的类和方法是被禁止使用的。如果你在错误信息中看到“权限不足”或“找不到类”,很可能就是这个问题。我们的加密算法实现必须只使用最基础的数学运算和数组操作,避免任何文件、网络IO。

实操心得: 在将源码加载进数据库前,最好先用对应版本的JDK(如JDK 1.6)在外部编译测试一遍,确保语法和基础API没问题。这样可以提前排除大部分环境问题。

5. 创建PL/SQL包装函数

Java类成功加载并编译后,它就像数据库里的一个静态库。我们需要用PL/SQL写一个“外壳”来调用它,并把这个“外壳”暴露成SQL函数。

5.1 编写PL/SQL调用规范

PL/SQL调用Java方法,需要使用AS LANGUAGE JAVA的语法。我们需要为每一个想要暴露的Java静态方法,编写一个对应的PL/SQL函数或过程。

以ECB加密函数为例:

CREATE OR REPLACE FUNCTION sm4_encrypt_ecb( p_plain_text IN VARCHAR2, p_secret_key IN VARCHAR2 ) RETURN VARCHAR2 AS LANGUAGE JAVA NAME ‘SM4Util.encrypt_ECB(java.lang.String, java.lang.String) return java.lang.String’; /
  • sm4_encrypt_ecb: 这是我们最终在SQL中使用的函数名。
  • p_plain_text,p_secret_key: PL/SQL函数的参数。
  • AS LANGUAGE JAVA: 声明这是一个Java调用。
  • NAME ‘SM4Util.encrypt_ECB(…)’: 这是最关键的部分,指定了要调用的Java类和方法。必须使用完全限定的Java类型。参数是java.lang.String,返回也是java.lang.String。这里的方法签名必须与你Java源码中的方法签名完全一致,包括大小写。

同理,创建ECB解密函数:

CREATE OR REPLACE FUNCTION sm4_decrypt_ecb( p_cipher_text IN VARCHAR2, p_secret_key IN VARCHAR2 ) RETURN VARCHAR2 AS LANGUAGE JAVA NAME ‘SM4Util.decrypt_ECB(java.lang.String, java.lang.String) return java.lang.String’; /

如果需要CBC模式,还需要初始化向量(IV)参数,可以创建sm4_encrypt_cbc和sm4_decrypt_cbc函数,对应的Java方法签名也需要调整。

5.2 函数授权与测试

创建成功后,这些函数属于当前用户。如果需要让其他数据库用户使用,需要授予EXECUTE权限:

GRANT EXECUTE ON sm4_encrypt_ecb TO other_user; GRANT EXECUTE ON sm4_decrypt_ecb TO other_user;

现在,激动人心的测试时刻到了。打开你的SQL客户端,直接调用:

— 测试密钥:32位十六进制字符串,对应128位密钥 SELECT sm4_encrypt_ecb(‘Hello, SM4 in Oracle!’, ‘0123456789ABCDEFFEDCBA9876543210’) AS encrypted FROM dual;

如果一切顺利,你会得到一串长长的十六进制字符串。然后,用解密函数验证:

SELECT sm4_decrypt_ecb(‘上一步返回的密文’, ‘0123456789ABCDEFFEDCBA9876543210’) AS decrypted FROM dual;

应该能正确还原出‘Hello, SM4 in Oracle!’。

6. 性能优化与生产环境注意事项

功能跑通只是第一步,要上生产,还得过性能和稳定性的关。

6.1 性能考量与优化

在数据库内执行Java代码是有开销的,主要在于Java调用上下文(JVM)的切换。对于单行或少量数据的加解密,这个开销可以接受。但如果你要对一个百万级别的表进行全表更新加密,在SQL中逐行调用这个函数,性能会是灾难性的。

优化策略1:批量处理。尽量避免在WHERE条件或SELECT列表中对大量数据逐行调用。对于数据迁移或初始化加密,建议使用PL/SQL存储过程,在过程内进行循环批量处理,减少SQL和Java上下文切换的次数。

优化策略2:减少密钥转换开销。我们的示例中,每次调用都传入十六进制密钥字符串,Java内部需要做一次十六进制到字节数组的转换。如果一段业务逻辑中密钥不变,可以考虑在PL/SQL端或Java端缓存转换后的密钥对象。但要注意,Oracle JVM中Java静态变量的生命周期和状态管理比较复杂,不建议在Java类中使用静态变量缓存密钥,因为多个会话可能互相干扰。更安全的方式是在PL/SQL包装层做些文章,或者接受这点转换开销。

优化策略3:连接池与会话。确保你的应用连接池配置合理。每个数据库会话首次调用Java函数时,会有加载类的开销。保持适度的会话复用可以平摊这个成本。

6.2 密钥安全管理

再次强调,密钥绝不能硬编码在代码或函数定义中。我们的设计是要求调用者传入。在生产中,密钥应该来自安全的配置中心或硬件加密机(HSM)。应用层从安全位置获取密钥,然后作为参数传递给数据库函数。

对于必须存储在数据库中的密钥(不推荐),可以考虑使用Oracle的DBMS_CRYPTO包(如果可用)或其他主密钥对其进行加密后存储。但最佳实践始终是“密钥不出安全域”。

6.3 异常处理与日志

我们的Java代码和PL/SQL函数都应该有良好的异常处理。PL/SQL函数可以增加EXCEPTION处理块,将Java抛出的异常转换为更易理解的用户自定义错误。

CREATE OR REPLACE FUNCTION sm4_encrypt_ecb_safe( p_plain_text IN VARCHAR2, p_secret_key IN VARCHAR2 ) RETURN VARCHAR2 AS l_result VARCHAR2(4000); BEGIN l_result := sm4_encrypt_ecb(p_plain_text, p_secret_key); RETURN l_result; EXCEPTION WHEN OTHERS THEN — 这里可以记录日志,例如使用自治事务写入自定义日志表 — RAISE_APPLICATION_ERROR(-20001, ‘SM4加密失败: ‘ || SQLERRM); RETURN NULL; — 或者返回一个特定错误标识 END; /

注意: 在Oracle JVM中,System.out.println的输出默认是看不到的。调试时可以使用dbms_java.set_output来重定向Java的System.out到DBMS_OUTPUT,但这在生产环境不实用。生产环境的日志最好通过PL/SQL层,写入自定义的日志表中。

7. 常见问题排查与实战技巧实录

在实际部署和测试过程中,我遇到了不少问题,这里整理成速查表,希望能帮你避坑。

问题现象可能原因排查与解决步骤
ORA-29549: 类 XXXX 已加载,但具有不同的校验和重复加载了同名的Java类,但内容有细微差别。1. 执行DROP JAVA SOURCE “SM4Util”;删除旧的类定义。
2. 重新执行CREATE JAVA SOURCE。注意:如果该类已被其他对象依赖,可能需要先删除依赖。
ORA-29531: 在类 XXXX 中找不到方法 YYYYPL/SQL包装函数中NAME子句指定的方法签名与Java类中的实际签名不匹配。1. 仔细核对Java方法名、参数类型(全限定名)、返回类型。
2. 特别注意String要写java.lang.String,byte[]要写[B。
3. 使用javap -s命令(在外部JDK环境下)查看编译后类的实际签名,确保完全一致。
调用函数返回乱码或ORA-XXXXX错误1. 字符集不统一。
2. Java代码中编解码方式与数据库字符集冲突。
3. 密钥或数据格式错误。
1. 确保数据库、客户端、Java代码(getBytes(“UTF-8”))使用统一的字符集,推荐UTF-8。
2. 在Java源码中,对所有字符串与字节数组的转换显式指定”UTF-8”。
3. 检查传入的密钥是否为32位十六进制字符串,密文是否为完整的十六进制字符串。
函数调用性能极慢1. 在SQL中逐行调用。
2. 会话首次调用加载类开销。
3. Java代码中存在低效循环或算法。
1. 改为批量处理模式(PL/SQL循环)。
2. 考虑在应用层缓存少量频繁访问的加密结果。
3. 审查Java算法实现,确保没有不必要的对象创建(如循环内new对象)。
ORA-29540: 类 oracle/xxxx 未找到Java代码中引用了Oracle JVM不支持的类或包。1. 这是最棘手的问题。彻底检查Java源码,移除所有非java.lang,java.math(部分)等基础包之外的引用。
2. 特别是避免使用java.util.logging,java.io.File等。
3. 用最基础的数组和算法操作重写相关逻辑。
加解密结果与外部工具(如OpenSSL)不一致1. 工作模式不同(ECB vs CBC)。
2. 填充模式不同(PKCS5Padding vs NoPadding)。
3. 数据格式不同(Hex vs Base64)。
1. 确认双方使用的模式、填充、初始向量(IV)完全一致。
2. SM4通常使用PKCS5Padding(或PKCS7Padding,两者在块加密中等价)。
3. 统一输入输出格式,建议都使用十六进制字符串进行比对测试。

独家避坑技巧:

  1. 先外后内调试法: 务必先在数据库外部的标准JVM环境中,用单元测试完整验证你的SM4 Java代码的正确性(加解密对称、与标准向量测试用例匹配)。确保算法本身100%正确,再引入Oracle环境变量。
  2. 最小化加载法: 第一次部署时,不要一次性加载所有Java类。可以先创建一个最简单的HelloWorld类,测试loadjava和调用流程是否通畅。然后再逐步替换为复杂的加密类。
  3. 版本控制与回滚: 将CREATE JAVA SOURCE和CREATE FUNCTION的SQL脚本纳入版本控制。每次更新前,备份旧的函数定义。一旦新版本有问题,可以快速回滚。
  4. 权限隔离: 生产环境上,创建一个专用的数据库用户(如CRYPTO_USER)来存放这些Java类和加密函数。只授予其他业务用户必要的EXECUTE权限。这样便于管理和安全审计。

8. 扩展应用场景与进阶思考

当基础的SM4加密解密函数稳定运行后,就可以基于此构建更强大的数据安全能力了。

场景一:透明字段加密对于存量表的关键字段(如身份证号、手机号),可以创建带触发器的视图或直接更新表。例如,为users表的id_card字段加密:

— 创建存储密文的新列 ALTER TABLE users ADD (id_card_encrypted VARCHAR2(100)); — 使用更新语句加密存量数据(注意分批提交,避免undo爆炸) UPDATE users SET id_card_encrypted = sm4_encrypt_ecb(id_card, ‘你的密钥’) WHERE id_card IS NOT NULL; — 创建视图,对外提供解密后的数据 CREATE VIEW v_users AS SELECT id, name, sm4_decrypt_ecb(id_card_encrypted, ‘你的密钥’) AS id_card FROM users;

当然,密钥需要安全地从应用传入,不能像示例这样硬编码。

场景二:基于密文的精确查询如果业务需要根据加密字段进行精确查询(如根据加密的手机号找用户),由于SM4是确定性加密(ECB模式或CBC模式使用固定IV),相同的明文和密钥总是产生相同的密文。因此,应用层可以先加密查询条件,再到数据库里匹配密文。

— 应用层代码:String encryptedPhone = sm4Encrypt(“13800138000”, key); — 然后执行SQL: SELECT * FROM users WHERE phone_encrypted = :encryptedPhone;

场景三:结合索引优化如果加密字段的查询非常频繁,可以在密文字段上建立普通索引。但请注意,这会将密文模式暴露给有权限查看索引的人。需要权衡安全与性能。

进阶思考:向更高版本迁移Oracle 12c及以上版本,对国密算法的支持可能会更好,甚至有原生的DBMS_CRYPTO支持。如果未来升级,我们的这套方案可以平滑迁移。届时,只需要将sm4_encrypt_ecb等函数的实现,从调用自定义Java类,改为调用原生PL/SQL加密包(如果Oracle提供了的话),上层的业务SQL几乎无需改动。这体现了将加解密逻辑抽象为数据库函数带来的另一个好处:逻辑与实现解耦。

最后,我想说的是,在Oracle 11g这样的老版本上实现国密算法,确实需要一些“折腾”。但这个过程让你对数据库的扩展能力、Java与数据库的交互、以及密码学的实际应用有了更深的理解。这套方案不仅解决了眼前的合规问题,更提供了一种在传统架构中嵌入现代安全能力的思路。当你看到一条简单的SQL语句SELECT sm4_decrypt_ecb(secret_data, :key) FROM sensitive_table成功执行并返回明文时,那种把复杂技术封装成简单服务带来的成就感,才是技术人最大的乐趣。

相关新闻

  • 2026年7月物料设计与制作/平面设计与制作客户信赖公司_北京旺泉文化传播有限公司 - 行业平台推荐
  • C#的基本语法入门,看这一篇就够了!
  • SSM框架开发高校教学质量评价系统实战解析

最新新闻

  • 【Go语言入门学习笔记】Part19.速率控制、原子计数器、互斥锁(Mutex)、状态协程
  • 51单片机按键计数与数码管显示:从硬件搭建到软件优化的嵌入式入门实战
  • Keil RTE实战指南:STM32开发中的软件组件管理与配置
  • Qt 6.5安卓开发环境配置全攻略:从JDK、SDK到APK打包
  • KMS智能激活实战指南:三步永久激活Windows和Office的完整方案
  • 企业级AI应用落地:从Claude合作看工程化实践与个人开发指南

日新闻

  • 终极TeamSpeak3音乐机器人搭建指南:5分钟实现语音聊天室音频播放
  • 广州海珠区内搬家攻略,平价靠谱搬家服务商推荐,专业打包搬运省心避坑全流程指南 - 厚道搬家
  • 大语言模型入门指南:从零到精通掌握AI核心技术的5大步骤

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号