Java 卡片小程序、安全数据传输和安全通道

Java Card applets, secure data transmission and Secure Channel

我想以一种方式编写我的小程序,使其 APDU 命令和状态字在我的卡和我的 reader 之间的传输通道中不清晰。我的意思是我不想将 APDU 命令和响应以纯文本形式发送给第三方。

我想我有两个选择:

  1. 在卡上选择我的小程序后,对于所有其他命令,对APDU命令的数据部分进行加密功能并在卡上对其进行解密,然后进行分析他们。请注意,我无法使用此方法加密整个命令,因为结果可能与另一个 SELECT APDU 命令冲突,并且卡的 SD 将其错误地识别为 SELECT 命令。是吗?

其示意图:

  1. 使用 SD 安全通道:据我所知,安全通道意味着:整个 APDU 命令 responses 以加密形式传输(即它们在源 (Security Domain/Card reader) 中加密并在目标 (Secutity Domain/Card Reader) 中解密。是这样吗?据我所知,SD 在此机制中执行加密方法角色,我的小程序和 SD 之间的通信是简单的(下图),对吗?

其示意图:

还有其他方法吗?

看来第一个解决方案不够好,因为:

  1. 我必须自己实现! :)
  2. 我们无法对第三方隐藏命令和响应的所有部分。(我们只能隐藏数据)

我说得对吗?

现在,假设我想确保我的小程序仅适用于使用安全通道传输的 APDU 命令。我想我又有两个选择:

  1. 将卡片置于SECURED状态。由于用户无法在此状态下使用纯文本 APDU 命令与卡通信(对吗?),因此他必须使用安全通道将命令发送到我的小程序。正确的?如果不正确,有没有办法强制 SD 仅与安全通道一起工作?

  2. 将卡保持在任何生命周期(例如OP_READY),但是,在接收到任何 APDU 命令时,检查 CLA段看是不是安全传输的! (这可能吗?来自安全通道的 APDU 命令的 CLA 部分和其他部分有什么区别吗?我说的对吗?)

还有其他方法吗?

最后是主要问题:

如何使用 SD 与我的小程序进行安全通信?因为我认为我必须使用 GlobalPlatform 类(是吗?),所以我看了一下它的 API-s。我在名为 org.globalplatform.GPSystem 的包中找到了一个名为 getSecureChannel 的方法。我走对路了吗?我必须使用这种方法吗?

我知道回答这个问题可能太长了,但我相信它不仅为我而且为其他未来的观众澄清了很多问题。

感谢任何人在这个问题上为我提供任何帮助。

还有一个示例小程序更有价值。

我按顺序回答:

  1. 是的,对于 ISO/IEC 7816-4,只有数据部分是加密的。 header 仅受身份验证标记保护。
  2. 不,全球平台安全通道也只是(可选)加密数据。完整性超过 header 和命令数据。
  3. 不,安全状态仅适用于全球平台,您必须使用卡上的 GP API 自行编程。 GP API 具有执行身份验证、请求安全通道和检索当前状态的访问方法。
  4. 正确,CLA 字节决定 APDU 是否加密(不是 如何 它是加密的)。如果 CLA 的第一位为零,那么您的安全通道必须符合 ISO/IEC 7816-4.

不用担心通过小程序进行安全通道通信。如果您在小程序中使用 Global Platform API,则非常简单。

您无需考虑很多问题,只需尝试编写一个安全通道小程序,它会根据命令数据中定义的安全级别处理您的小程序。

参考 GP 安全通道 API: http://www.win.tue.nl/pinpasjc/docs/apis/gp22/

并且您应该将卡保持在 SECURED 状态。

这是安全通道 scp02 的示例小程序:

package secureChannel;

import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;

import org.globalplatform.GPSystem;
import org.globalplatform.SecureChannel;

public class Scp02 extends Applet
{
    final static byte INIT_UPDATE       = (byte) 0x50;

    final static byte EXT_AUTHENTICATE  = (byte) 0x82;

    final static byte STORE_DATA        = (byte) 0xE2;

    public static void install(byte[] bArray, short sOffset, byte bLength)
    {
        new Scp02().register(bArray, sOffset, bLength);
    }

    public void process(APDU apdu) throws ISOException
    { 
        SecureChannel sc = GPSystem.getSecureChannel();

        byte[] buffer = apdu.getBuffer();

        short inlength = 0;

        switch (ISO7816.OFFSET_INS)
        {
            case INIT_UPDATE:
            case EXT_AUTHENTICATE:
                inlength = apdu.setIncomingAndReceive();
                sc.processSecurity(apdu);
            break;

            case STORE_DATA:
                //Receive command data
                inlength = apdu.setIncomingAndReceive();
                inlength = sc.unwrap(buffer, (short) 0, inlength);

                apdu.setOutgoingAndSend((short)0, inlength);

                //Process data
                break;
        }
    }
}

为了 Google 搜索,来自 Anurag Bajpai 的代码如果不稍加修改就无法工作,因为如 GP 安全通道 API 中所述,小程序应输出最终响应数据:

If response data is present, this data will be placed in the APDU buffer at offset ISO7816.OFFSET_CDATA. The return value indicates the length and the applet is responsible for outputting this data if necessary.

因此,更正后的代码是:

package secureChannel;

import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import org.globalplatform.GPSystem;
import org.globalplatform.SecureChannel;


public class Scp02 extends Applet
{
    final static byte INIT_UPDATE       = (byte) 0x50;

    final static byte EXT_AUTHENTICATE  = (byte) 0x82;

    final static byte STORE_DATA        = (byte) 0xE2;

    public static void install(byte[] bArray, short sOffset, byte bLength)
    {
        new Scp02().register(bArray, sOffset, bLength);
    }

    public void process(APDU apdu) throws ISOException
    { 
        SecureChannel sc = GPSystem.getSecureChannel();

        byte[] buffer = apdu.getBuffer();

        short inlength = 0;

        switch (ISO7816.OFFSET_INS)
        {
            case INIT_UPDATE:
            case EXT_AUTHENTICATE:
                inlength = apdu.setIncomingAndReceive();
                short respLen = sc.processSecurity(apdu);
                apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, respLen);
            break;

            case STORE_DATA:
                //Receive command data
                inlength = apdu.setIncomingAndReceive();
                inlength = sc.unwrap(buffer, (short) 0, inlength);

                apdu.setOutgoingAndSend((short)0, inlength);

                //Process data
            break;
    }
}

}

请注意,在调用 sc.processSecurity(apdu) 之前调用 apdu.setIncomingAndReceive() 是不正确的,因为 processSecurity()“负责接收已识别命令的数据字段”。