PAYATHON 2026

TapLinx 调用 DESFire EV3 changeKey() 报 Integrity Error SW2=30

排障赵师傅

结论

代码创建的是 KeyType.TWO_KEY_THREEDES 应用,与描述中的 AES 应用不一致。创建应用、认证和更改密钥时,使用的密钥类型需要一致。

更改应用主密钥 key 0,应按以下顺序操作:

选择目标应用
使用该应用当前的 key 0 完成认证
调用 changeKey() 更改 key 0
使用新的 key 0 重新认证

PICC 主密钥认证成功,不能代替目标应用的 key 0 认证。题目中的执行记录没有明确显示“选择应用后,使用旧应用主密钥认证成功”这一步,需要先单独验证。

仅凭这段代码和异常栈,还无法确定 Integrity Error 的唯一原因。实际认证方式、密钥数据和调用 changeKey() 时的会话状态都需要检查。

SW2=30 表示什么

先确认日志使用的数值进制。如果 TapLinx 按十进制输出,30 就是十六进制的 0x1E,对应 DESFire 的 INTEGRITY_ERROR,不能把它直接当作 0x30。采用 ISO 包装的原始响应通常为:

91 1E

完整性错误表示命令处理中的完整性校验失败。ChangeKey 的处理涉及认证建立的会话、加密数据和校验内容,需要检查:

  • 是否已在当前应用中正确完成认证。
  • 认证算法、认证模式与应用密钥类型是否匹配。
  • 认证后是否重新选择应用或重新连接卡片,导致会话失效。
  • 传入的密钥数据格式是否符合当前 TapLinx 接口要求。

缺少认证、密钥错误或权限不足,也可能在不同阶段返回其他错误码。仅凭 Integrity Error,不能断定是“旧密钥错误”或“少了一次认证”。

应用主密钥与 PICC 主密钥需要分别认证

PICC 层与应用层各有自己的主密钥。即使编号都是 0,它们也属于不同的上下文。

例如:

选择 PICC
使用 PICC key 0 认证
创建应用

选择目标应用
使用应用 key 0 认证
更改应用 key 0

selectApplication() 成功只表示选中了应用,不会自动完成应用密钥认证,也不能沿用此前 PICC 层的认证权限。

创建参数:

.setAppMasterKeyChangeable(true)

允许更改应用主密钥,但更改前仍需认证。文件管理和目录访问的认证设置,也不能代替更改主密钥所需的认证。

按照 2K3DES 配置更改 key 0

如果保留当前创建参数,就应始终按 TWO_KEY_THREEDES 处理该应用。

对于采用标准初始密钥、尚未更改密钥的新应用,2K3DES 初始密钥通常为 16 字节全零。如果卡片经过个性化,或默认应用密钥配置已被修改,则应使用实际初始密钥,不能假定为全零。

示例中的 desfire 和 AID 沿用现有代码。authenticateKey0 是调用方提供的认证函数,不是 TapLinx API;它必须对当前应用的 key 0 执行 2K3DES 认证,并在失败时抛出异常。

fun change2K3DesApplicationMasterKey(
    oldKey: ByteArray,
    newKey: ByteArray,
    authenticateKey0: (ByteArray) -> Unit
) {
    // 此处按 16 字节的 2K3DES 原始密钥表示。
    require(oldKey.size == 16)
    require(newKey.size == 16)

    desfire.selectApplication(AID)

    // 使用当前应用的旧 key 0 建立认证会话。
    authenticateKey0(oldKey.copyOf())

    desfire.changeKey(
        0,
        KeyType.TWO_KEY_THREEDES,
        oldKey.copyOf(),
        newKey.copyOf(),
        0x00
    )

    // 更改当前认证密钥后,重新建立认证会话。
    desfire.selectApplication(AID)
    authenticateKey0(newKey.copyOf())
}

认证回调应使用当前 TapLinx 版本支持的 authenticate() 重载、认证模式和密钥对象格式。题目未提供 SDK 版本和认证代码,因此无法给出保证能编译的认证调用。

接入时需要确认:

  1. 认证的密钥编号是应用 key 0。
  2. 密钥对象按 2K3DES 构造。
  3. 认证模式与所用 DESFire 命令流程匹配。
  4. 认证失败后立即停止,不再调用 changeKey()。
  5. 从认证成功到调用 changeKey() 之间,不重新选择应用或重建连接。

如果 SDK 的密钥容器要求特定的 3DES 展开格式,应按该版本文档构造容器。不要把底层加密库的格式要求直接套到 changeKey() 的字节数组参数上。

changeKey() 参数应如何理解

题目中的调用形式为:

desfire.changeKey(
    keyNo,
    KeyType.TWO_KEY_THREEDES,
    oldKey,
    newKey,
    0x00
)

各参数在本场景中的要求如下:

参数本场景中的要求
keyNo更改应用主密钥时为 0
密钥类型与当前应用及目标密钥类型一致
oldKey当前应用 key 0 的实际旧密钥
newKey要写入的新密钥,长度和编码符合接口要求
0x00按该重载定义传入的密钥版本值,不是认证模式或密钥编号

更改当前认证密钥和更改其他密钥时,底层 ChangeKey 报文的构造有所不同。使用 TapLinx 高层接口时,这些差异应由 SDK 处理。不要自行对 oldKey、newKey 做异或,也不要提前拼接 CRC、填充或加密报文。

更改当前用于认证的 key 0 后,后续操作需要使用新密钥重新认证,不能继续依赖旧认证会话。

AES 与 TWO_KEY_THREEDES 的差异会导致失败吗

如果实际代码混用了这两种算法,就会导致失败。这行配置创建的是 2K3DES 应用:

.setKeyTypeOfApplicationKeys(KeyType.TWO_KEY_THREEDES)

随后若按 AES 方式认证,即使双方都使用 16 字节全零密钥,也无法匹配。AES-128 与 2K3DES 的原始密钥长度都可以是 16 字节,但长度相同不代表算法相同。

如果目标是 AES 应用,以下配置需要同时统一:

  • 创建应用时的 AES 密钥类型配置。
  • AES 密钥对象与对应认证方式。
  • changeKey() 的密钥类型及密钥数据。

AES 枚举的具体名称以当前 TapLinx 版本为准。仅修改日志中的 KEY_TYPE 变量,或只修改 changeKey() 的第二个参数,都不会自动将已有的 2K3DES 应用转换为 AES 应用。

仍然失败时,如何缩小范围

先单独验证“选择应用后,使用旧 key 0 认证”。认证成功后,再执行一次 changeKey(),并记录实际 AID、密钥编号、密钥类型、认证模式和原始响应状态字。认证未成功时,不执行密钥更改;日志中也不要输出密钥本身。

如果仍报完整性错误,优先检查认证后是否重新选卡、重新选择应用,或发生了其他会话变化,再核对 TapLinx 的具体版本及对应接口文档。

异常栈中的 DESFireEV2.changeKey() 只能说明调用经过了该实现路径,不能据此认定 IDESFireEV1 不支持当前操作,也不能直接认定存在 EV3 兼容性缺陷。

如果更改命令期间断连或响应丢失,卡片上的密钥状态可能已经改变。重新连接后,应分别验证已知的旧密钥和新密钥,确认实际状态,再决定下一步操作。formatPICC() 会清除卡片应用和数据,不应作为密钥更改失败后的常规重试步骤。

备注:内容仅供参考。