Java Card开发指南(二)

标签(空格分隔): Java 智能卡


我们将从原理上理解Java Card开发中一些关键的技术信息。

名词解释

Applet:J2SE中也存在Applet这个概念。这里的Applet可以看做类似于Android中一个个APP。分为ROM Applet、事前发行和事后发行Applet。
AID:应用标识符。又区分为Package AID和Applet AID。相当于MAC地址的构造,前四个字节是IEEE分配的,后四个字节是厂商定义的内部序列号。Applet AID是Package AID的所有部分外加一些字节
CAP:Converted Applet。我们知道,J2SE中也存在Applet,这里沿袭了Applet的定义——小应用程序。由于智能卡的计算能力有限(据说是最小的计算平台),所以需要将二进制文件class进行削减,于是就构成了CAP
ATR:复位应答,卡-机交流的第一句话,包括协议之类的数据
DF:专用文件Dedicated File的缩写,DF的作用可以等同于计算机中的目录文件
MF:在一张卡片里(这里说的是卡片而不是某个应用)有且仅有一个特殊的DF,称为主文件MF,这个MF的FID默认为3F00,相当于计算机中的根目录,而且在任何时候MF都可以被选择
EF:基本信息文件Elementary File,也就是说通常情况下和应用相关的数据都会存放于EF中。
如果某个DF下没有子DF,只有若干EF,那么这个DF也被称作ADF,反之如果某个DF下除了有EF之外,还有子DF,那么这个父级的DF也被称作DDF。为了对文件进行访问,需要给文件分配一个特定的标识。无论是DF还是EF都会有对应的两个字节长ID标识,也就是所谓的FID。而DF还会有5-16个字节长的名字,也叫做AID。EF还会有一个5位长(范围从1到30)的短文件标识,就是SFI。

ADPU

CLA INS P1 P2 Lc 数据域
0x0 0xA4 0x4 0x0 AID的长度 AID 字节

APDU的全称是Application Protocol Data Unit,也就是在应用层实现的数据单元。其中读卡器发出的叫Command-APDU,卡回复的叫Response-APDU。
Command-APDU:

CLA INS P1 P2 Lc 数据域
0x0 0xA4 0x4 0x0 AID的长度 AID 字节
Le 数据域 SW1 SW2
0x00 Null 0x90 0x00

Response-APDU:

Le 数据域 SW1 SW2
0x00 Null 0x90 0x00

在卡-机通信开始之前,还有个请求和回复的过程。这时候我们可以收到一个ATR如:
ATR = 0x3b 0xf0 0x11 0x00 0xff 0x00
| TS | T0 | TA1,TB1,TC1,TD1 |T1,T2,...,T15 | TCK |
| :----: | :----: | :----: | :----: | :----: | :----: |
| 起始* | 格式*,决定接口和历史 | 接口(由T0决定:b5-TA1,b8-TD1) | 历史 | 校验 |
| 0x3B/0x3F | 0xF0 | 0x11 0x00 0xFF 0x00 | Null | Null |

Applet构成简介

我们以最简单的demo为例:

package hellojavacard;

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

public class Appletcard extends Applet {

    private Appletcard() {
    }

    public static void install(byte bArray[], short bOffset, byte bLength) throws ISOException {
        new Appletcard().register(bArray, (short) (bOffset + 1), bArray[bOffset]);
    }

    public void process(APDU apdu) {
        if (selectingApplet()) {
            return;
        }

        byte[] buf = apdu.getBuffer();
        switch (buf[ISO7816.OFFSET_INS]) {
        case (byte) 0x00:
            break;
        default:
            ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
        }
    }
}

注册Applet

这一步是通过register()函数实现的,register()函数有两种形式,一种是不带参数的,一种是带参数的,这些参数在Applet的安装期间就进行了传递:

register(byte[] bArray,short bOffset,byte bLength)
#byte[] bArray - 包含安装参数的数组
#short bOffset - 安装参数在 bArray 中的起始位移
#byte Length - bArray 中的参数数据的字节数

这些初始化值是Applet开发者定义的,通常钱包应用可以包括记录数目,钱包ID,卡初始余额等。

选择Applet

CLA INS P1 P2 Lc 数据域
0x0 0xA4 0x4 0x0 AID的长度 AID 字节

在未被选择之前,Applet是处于挂起状态的。这一步是通过selectingApplet()实现的,SELECT APDU 命令是在 Java 卡平台上被标准化的唯一 APDU 命令。注意,正常情况下Applet只能通过成功的SELECT APDU命令执行,但有些卡可以通过配置缺省Applet的方式省去这一步骤。
C-APDU:

CLA INS P1 P2 Lc 数据域
0x0 0xA4 0x4 0x0 AID的长度 AID 字节
Le 数据域 SW1 SW2
0x00 Null 0x90 0x00

R-APDU:

Le 数据域 SW1 SW2
0x00 Null 0x90 0x00

与之对应的反向操作为deselect(),相当于进程阻塞操作block。在复位和掉电的情况下,取消选择不执行deselect()。

获取APDU

为了处理APDU,我们先要将其获取并存储至字节数组buffer。APDU buffer 是一个字节数组,其长度可通过方法 apdu_buffer.length确定。

public void process(APDU apdu){
//索取APDU 
buffer byte[] apdu_buffer = apdu.getBuffer();
}

在调用一个 applet 的 process 方法时,在APDU buffer 中只有命令的前 5 个字节是可用的――前 4
个字节是APDU 头〔CLA,INS,P1,P2〕而第 5 个字节(P3)是一个附加的长度域。P3 的含义是隐含的, 由命令的 case 决定:

  • 对于 case1,P3=0;
  • 对于 case2,P3=Le,表示输出的响应数据的长度;
  • 对于 case3 和 case4,P3=Lc,表示输入的命令数据的长度。

返回状态字

  • 正常状态:返回状态字0x9000。(SW1:0x90;SW2:0x0)
  • 在 命 令 处 理 过 程 中 的 任 何 地 方 , applet 终 止 操 作 并 通 过 调 用 静 态 方 法
    ISOException.throwIt(reason) 抛出 ISOException 例外。
  • 如果由底层 Java 卡系统检测到错误, JCRE 的行为是不确定的。例如, JCRE 可能抛出一个例
    外 APDUException 或 TransactionException。

智能卡安全

从上面的APDU交互过程我们可以看出,交互的过程完全通过明文传输。这就好比TCP/IP协议实现了端-端的传输,踢进了临门一脚。对于智能卡传输过程中的安全性,我们就要借助应用层协议(如SSL/TLS)。智能卡的传输过程也是一样,我们需要对一些敏感的信息进行加密(比如卡交易金额),同时对消息的可靠性也有认证的需求(还比如卡交易金额)。另外,加密可以作为一个身份认证的标示(比如SIM卡与注册网络),这样可以将其视作一个令牌(token)。
javacard.security 包

类或接口 描述
Key 所有密钥的基接口
SecretKey 用于对称算法的密钥的接口
DESKey 代表 DES 或双密钥 3DES 或 3 密钥 3DES 的 8/16/24 字节密钥
PrivateKey 关于用于非对称算法的私钥的基接口
PublicKey 关于用于非对称算法的公钥的基接口
RSAPrivateKey 用于利用模/指数形式的 RSA 算法签名数据
RSAPrivateCrtKey 用于利用中国余数定理形式的 RSA 算法签名数据
RSAPublicKey 用于验证利用 RSA 算法签名数据时的签名
DSAKey 关于 DSA 算法私钥和公钥实现的基接口
DSAPrivateKey 用于利用 DSA 算法签名数据
DSAPublicKey 用于验证利用 DSA 算法的签名
KeyBuilder 生成一个密钥对象的 Factory 类
MessageDigest 关于 hash 算法的抽象基类
Signature 关于签名算法的抽象基类
RandomData 关于随机数生成的抽象基类
CryptoException 代表一个密码技术相关的例外
类或接口 描述
Cipher 提供关于加密和解密的密码技术 cipher 的功能。类 cipher 是一个抽象基类
KeyEncryption 能使密钥的实现访问加密的密钥数据

javacardx.crypto 包

类或接口 描述
Cipher 提供关于加密和解密的密码技术 cipher 的功能。类 cipher 是一个抽象基类
KeyEncryption 能使密钥的实现访问加密的密钥数据

下面将一些应用进行举例:

计算消息摘要

Public static MessageDigest GetInstance(byte algorithm,boolean externalAccess);
#第一个参数可以为ALG_SHA、ALG_MD5,和 ALG_RIPEMD160
#第二个参数设置的是Applet计算的MD结果可否被外部访问

这里以SHA-1为例,对三个字节数组m1、m2、m3计算摘要:

Public class myApp extends Applet 
{ 
Private MessageDigest sha;
Public MyApplet()
    { 
    sha = MessageDigest.getInstance(MessageDigest.ALG_SHA,false);
    //把字节数组 m1 中的整个数据馈入消息摘要计算 
    sha.update(m1,(short)0,(short)(m1.length));
    //再从字节数组 m2 中位移 0 起馈入 8 字节数据 
    sha.update(m2,(short)0,(short)8);
    //将字节数组 m3 中的全部数据作为最后一批发送给消息摘要计算,并将 hash 值保存于字节数组 digest 中位移 0 开始的地方 
    sha.doFinal(m3,(short)0,(short)(m3.length),digest,(short)0);
    } 
}

产生密钥

类 KeyBuilder 定义了一个供你选择的选择参数集,使你选择密钥的类型和密钥的长度。例如,为了建 立一个长度为 64 字节(64*8 = 512 位)的 RSA 私钥,你调用 buildKey 方法如下:

    RSAPrivateKey rsa_private_key;
    rsa_private_key = (RSAPrivateKey)KeyBuilder.buildkey(KeyBuilder.TYPE_RSA_PRIVATE, KeyBuilder.LENGTH_RSA_512,false);#这里进行了强制类型转换是便于调用RSAPrivateKey接口中的方法setModulus和setExponent

注意,这里生成的密钥对象未被初始化,还需要设置模数Modulus(r)和指数Exponent(e)。

计算和验证签名

签名就是生成摘要和产生密钥并进行加密的复合过程。所以我们要先构造签名实例:

Signature signature;
Signature = Signature.GetInstance(Signature.ALG_DSA_SHA,false);
#第一个参数支持主流的签名算法
#第二个参数设置的是Applet计算的Signature结果可否被外部访问

因为签名要使用一个密钥,我们需要初始化这个 Signature 对象。为此,我们需要调用两个 init 方法
之一:

public void init(Key theKey,byte theMode);
public void init(Key theKey,byte theMode,byte[] bArray,short bOff,short bLen);
#第二个init方法环允许你在字节数组bArray中规定算法初始化参数。如初始向量IV

在一个非对称算法中,签名和验证不是使用同一个密钥。所以,你应在第二个参数 theMode 中指
出如何使用密钥。有两种模式,如类 Signature 中所定义:

  • MODE_SIGN--指出签名模式
  • MODE_VERIFY--指出验证模式

下面的代码计算来自数组 s1, s2,和 s3 的数据的签名:

Public class myApp extends Applet 
{ 
Private Signature signature;
Public MyApplet()
    { 
    Signature = Signature.GetInstance(Signature.ALG_DSA_SHA,false);
    Signnature.init(Key theKey,byte theMode);
    //或Signnature.init(Key theKey,byte theMode,byte[] bArray,short bOff,short bLen);
    //输入字节数组 s1 中的数据
    Signature.update(s1,(short)0,(short)(s1.length));
    //输入字节数组 s2 中的数据
    Signature.update(s2,(short)0,(short)(s2.length));
    //作为最后一批数据输入字节数组 s3 中的数据并生成签名,放到数组 sig_buffer 中,从位移 0 起
    Signature.sign(s3,(short)0,(short)(s3.length),sig_buffer,(short)0);
    } 
}

下面的例子表明如何验证上一例子中计算出来的签名:

Public class myApp extends Applet 
{ 
Private Signature signature;
Public MyApplet()
    { 
    Signature = Signature.GetInstance(Signature.ALG_DSA_SHA,false);
    Signnature.init(Key theKey,byte theMode);
    //或Signnature.init(Key theKey,byte theMode,byte[] bArray,short bOff,short bLen);
    //输入字节数组 s1 中的数据
    Signature.update(s1,(short)0,(short)(s1.length));
    //输入字节数组 s2 中的数据
    Signature.update(s2,(short)0,(short)(s2.length));
    //作为最后一批数据输入字节数组 s3 中的数据并生成签名,放到数组 sig_buffer 中,从位移 0 起
    Signature.sign(s3,(short)0,(short)(s3.length),sig_buffer,(short)0);
    //输入数组 s3 中的最后一批数据并用计算出来的签名验证放在 sig_buffer 中的签名
    If(Signature.verify(s2,(short)0,(short)(s2.length),sig_buffer,sig_offset,sig_length) != true){ISOException.throwIt(SW_WRONG_SIGNATURE);}
    } 
}

数据加密和解密

下面的例子以 CBC 模式 DES 算法建立一个 Cipher 对象。输入数据无 padding。该 Cipher 对象以一个
用于加密的 DES 密钥初始化:

Public class myApp extends Applet 
{ 
Private Cipher cipher;
Public MyApplet()
    { 
    Cipher = Cipher.getInstance(Cipher.ALG_DES_CBC_NO_PAD,false);
    Cipher.init(des_key,Cipher.MODE_ENCRYPT);
    //或Signnature.init(Key theKey,byte theMode,byte[] bArray,short bOff,short bLen);
    //输入字节数组 s1 中的数据
    Signature.update(s1,(short)0,(short)(s1.length));
    //输入字节数组 s2 中的数据
    Signature.update(s2,(short)0,(short)(s2.length));
    //作为最后一批数据输入字节数组 s3 中的数据并生成签名,放到数组 sig_buffer 中,从位移 0 起
    Signature.sign(s3,(short)0,(short)(s3.length),sig_buffer,(short)0);
    //输入数组 s3 中的最后一批数据并用计算出来的签名验证放在 sig_buffer 中的签名
    If(Signature.verify(s2,(short)0,(short)(s2.length),sig_buffer,sig_offset,sig_length) != true){ISOException.throwIt(SW_WRONG_SIGNATURE);}
    } 
}

接着,为了加密数据,利用 update 方法和 doFinal 方法:

public short update(byte[] inBuf,short inOffset,short inLength,byte[] outBuff,short outOffset);
public short doFinal(byte[] inBuf,short inOffset,short inLength,byte[] outBuff,short outOffset);

随机数据的生成

随机数是密码技术过程( procedures)经常需要的。为了建立一个随机数发生器,你要调用类javacard.security.randomData中的方法getInstance并指出一种算法。算法选择参数可为RandomData.ALG_PSEDO_RANDOM,它表示伪随机数生成算法实用程序;或者为RandomData.ALG_SECURE_RANDOM,它指的是密码技术上安全性很强的随机数生成算法。
为了获得一个随机数,调用generatedata方法如下:

RandomData random_data = randomdata.getInstance(RandomData.ALG_SECURE_RANDOM);
//种子提供于字节数组 seed 中
random_data.setSeed(seed,seed_offset,seed_length);
//把一个随机数写入字节数组 random_num 中
random_data.generatedata(random_num, random_num_offset, random_num_ length);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,100评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,862评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,993评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,309评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,303评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,421评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,830评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,501评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,689评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,506评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,564评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,286评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,826评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,875评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,114评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,705评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,269评论 2 341

推荐阅读更多精彩内容

  • 标签(空格分隔): Java 智能卡 以下将要进行的是基于Java Card的智能卡开发学习,第一次我们先来熟悉开...
    dacf13a4e779阅读 10,551评论 4 0
  • 在刚刚结束的2017年12月31日和新一年的1月1日,这个临界点儿,票圈儿不断蹦出小伙伴们的年终总结或者新年愿望清...
    地耳阅读 372评论 0 0
  • 《我的飘客之路》 作者:黎根大叔 导读:翻了翻旧日记,我觉得是时候把我的一些个人的旅行经过写出来了。没有跌宕起伏的...
    黎根大叔阅读 351评论 0 0
  • 今天雾霾散去,终于见到了久违的阳光,心情顿时愉悦起来。下午上班时,我特意步行去单位,想好好体会一下,有阳光、...
    b7a019f3cbb9阅读 422评论 2 1
  • 2014年11月15日 21:05浏览(125) 为父庆90寿辰的决议确定之后,接下来的就是各忙各的事情。 一日,...
    镇南方良金阅读 290评论 0 2