java card的rsa运算_Javacard DES/AES/RSA/Hash/Sinature算法API使用示例

前面一篇DES算法API使用示例代码写得比较渣,特别是在部门里的老前辈帮我看了下代码风格之后深感如此。

本篇介绍本人写的一个国际算法(区别于国密算法SM2/SM3这些)API调用的示例applet:

话不多说,直接先上代码,后面再补充解释下,代码上也有我附带的较为详细的注释。

(1)Des API调用文件-Des.java:

package helloWorld;

import javacard.framework.JCSystem;

import javacard.security.DESKey;

import javacard.security.Key;

import javacard.security.KeyBuilder;

import javacardx.crypto.Cipher;

public class Des

{

private Cipher DESEngine;

private Key myKey;

private byte[] temp;

private RandGenerator rand;

public Des()

{

//必须先初始化(获得实例instance才能init否则报错)

DESEngine = Cipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M1, false);

//buildKey创建的是未初始化的key,密钥值需用setKey函数手动赋值

myKey = KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES, false);

//设置暂存变量temp

temp = JCSystem.makeTransientByteArray((short)100, JCSystem.CLEAR_ON_DESELECT);

//给rand对象也分配空间,不然无法执行RandGenerator类的代码!!

rand = new RandGenerator();

//****** 1 *******首先自动生成个密钥

//产生64bit随机数

temp = rand.GenrateSecureRand((short)100);

//Util.arrayFillNonAtomic(temp1, (short)16, (short)48, (byte)0x11);

//设置密钥--拿随机数当密钥.

//注意!DES密钥必须要是8字节的倍数长度,如果不是,下面这个setKey函数会截取64bits长度,剩余的抛弃掉!所以上面下来的100bits随机数只会取64bits

((DESKey)myKey).setKey(temp, (short)0);

}

public void init(boolean isEncryption)

{

//short b = myKey.getSize(); //可用debug查看该变量值

if(isEncryption)

//****** 2 *******初始化加密密钥和加密模式

DESEngine.init(myKey, Cipher.MODE_ENCRYPT);

else

DESEngine.init(myKey, Cipher.MODE_DECRYPT);

}

public void GetResult(byte[] inBuf, short inOffset, short inLength, byte[] outBuf, short outOffset)

{

//****** 3 *******传入密文/明文进行加密并得到明文/密文

//特别注意DES加密结果是8的倍数,所以outBuf开辟的空间至少要为8字节.并且DES解密只能处理8的倍数次方的密文输入.否则6F00

DESEngine.doFinal(inBuf, inOffset, inLength, outBuf, outOffset);

}

}

/* DES注意事项:

*

* 注意一:DES密钥必须要是8字节的倍数长度,如果不是,setKey函数会截取64bits长度,剩余的抛弃掉!所以上面下来的100bits随机数只会取64bits

*

* 注意二:DES加密结果是8的倍数,所以outBuf开辟的空间至少要为8字节

*

* 注意三:DES解密只能处理8的倍数次方的密文输入.否则又6F00.明文传入长度随意(>=0),函数也自动会有padding

* */

(2)Rsa.java:

package helloWorld;

import javacardx.crypto.Cipher;

import javacard.framework.ISO7816;

import javacard.framework.ISOException;

import javacard.security.KeyBuilder;

import javacard.security.KeyPair;

public class Rsa

{

private Cipher RSAEngine;

//非对称加密算法需要用密钥对的形式存储密钥(公钥私钥):囊括PublicKey和PrivateKey对象

//而不能用publicKey、privateKey分开来存[是因为公钥和私钥的生成的相关而不是独立的?]

private KeyPair keypair;

public Rsa()

{

//new一个密钥对对象

//第二个参数决定了密钥长度的同时,决定了生成密文的长度(因为密文长度=密钥长度[模数])为512比特,也就是64字节,转成十六进制表示为40

keypair = new KeyPair(KeyPair.ALG_RSA_CRT, KeyBuilder.LENGTH_RSA_512);//只支持这个构造函数,用KeyPair(PublicKey,PrivateKey)构造会异常

//调用函数自动生成随机的密钥(包括公钥和私钥)

keypair.genKeyPair();

try

{

//用Cipher.ALG_RSA_ISO14888会出错--6F00,ISO7816--接触式,14...--非接(触式)

RSAEngine = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);

}

catch(Exception e)

{

ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);

}

}

public void init(boolean isEncryption)

{

if(isEncryption)

RSAEngine.init(keypair.getPrivate(), Cipher.MODE_ENCRYPT);

else

RSAEngine.init(keypair.getPublic(), Cipher.MODE_DECRYPT);

}

public void GetResult(byte[] inBuf, short inOffset, short inLength, byte[] outBuf, short outOffset)

{

//****** 3 *******传入密文进行加密并得到密文

RSAEngine.doFinal(inBuf, inOffset, inLength, outBuf, outOffset);

}

}

/* RSA注意事项:

*

*

* 注意一:

* 为何不是所有传入的密文都能解密(6F00)?

* 并且只有用本次的密钥产生过的密文格式传入去解密才能no error

* 这是因为如果你拿什么密钥都能解密别人的密文,那就违背了密码算法的本意了呀!!!

*

*

* 注意二:

* RSA最终生成的密钥长度>=64字节且为64字节的倍数,若不足,则genKeyPair函数会自动补全到位

*

* 注意三:RSA算法生成的密文的长度 = 密钥的长度,所以这里注意给dofinal函数传入的输出缓冲区的大小不能太小

*

* 注意四:RSA明文传入加密,长度可随意(>=0),函数会自动padding。

* 但是传入解密的密文必须是 >= 密钥长度,且为密钥长度的倍数,最后解密出来的明文长度也是等于密钥长度

*

* */

(3)Aes.java:

/**

* AES

*/

package helloWorld;

import javacard.security.Key;

import javacard.security.KeyBuilder;

import javacard.security.AESKey;

import javacardx.crypto.Cipher;

import javacard.framework.JCSystem;

/**

* @author lv.lang

*

*/

public class Aes {

private Key myKey;

private Cipher AESEngine;

private byte[] temp;

private RandGenerator rand;

/**

*

*/

public Aes() {

rand = new RandGenerator();

temp = JCSystem.makeTransientByteArray((short)128, JCSystem.CLEAR_ON_DESELECT);

temp = rand.GenrateSecureRand((short)128);

//这里的算法参数只支持NOPAD,其他如ALG_AES_CBC_PKCS5都会报6A80,为什么?

AESEngine = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, true);

myKey = KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false);

((AESKey)myKey).setKey(temp, (short)0);

}

public void init(boolean isEncryption)

{

//short b = myKey.getSize(); //可用debug查看该变量值

if(isEncryption)

AESEngine.init(myKey, Cipher.MODE_ENCRYPT);

else

AESEngine.init(myKey, Cipher.MODE_DECRYPT);

}

public void GetResult(byte[] inBuf, short inOffset, short inLength, byte[] outBuf, short outOffset)

{

AESEngine.doFinal(inBuf, inOffset, inLength, outBuf, outOffset);

}

}

(4)Hash.java:

/**

* MessageDigest

*/

package helloWorld;

/**

* @author lv.lang

*

*/

import javacard.security.MessageDigest;

public class Hash {

private MessageDigest HashEngine;

public Hash() {

HashEngine = MessageDigest.getInstance(MessageDigest.ALG_SHA, false);

//HashEngine.getInitializedMessageDigestInstance(MessageDigest.ALG_SHA, false).

}

public void GetResult(byte[] inBuf, short inOffset, short inLength, byte[] outBuf, short outOffset)

{

//sha-1产生的摘要结果固定为160bit[20bytes]

//其他sha对应的摘要字节数可查看MessageDigest.ALG_弹出的注释窗口

HashEngine.doFinal(inBuf, inOffset, inLength, outBuf, outOffset);

}

}

/*

* HASH如sha、md5,只是将原文产生一段信息摘要,仅用来验证(只能自验?只能自验有啥用处)消息的完整性也就是检测原文是否被篡改。

* 也就是说hash的作用是保证任意一段原文对应唯一的hash值。

* 并且sha/md5这些都是带密钥的哈希,也就是说不同密钥下同个原文产生的hash又不同!

* 但是明显攻击者我自己用随便一段消息生成hash值,发出去看到的也是"读的通"的消息且hash正确。

* 所以做数字签名在hash基础上,还需要验证身份!那就是在hash值之后加上非对称密钥加解密!

*

*

*/

(5)RandGenerator.java:

package helloWorld;

import javacard.framework.JCSystem;

import javacard.security.RandomData;

public class RandGenerator

{

private byte[] temp;//随机数的值

private RandomData random;

private byte size;//随机数长度

//构造函数

public RandGenerator()

{

size = (byte)4;

temp = JCSystem.makeTransientByteArray((short)4, JCSystem.CLEAR_ON_DESELECT);

//类当中有getInstance的都要先调用这个函数获取对象实例才能使用其他方法,不然6F00

random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);

}

//产生length长度的随机数并返回

public final byte[] GenrateSecureRand(short length)

{

temp = new byte[length];

//生成4bit的随机数

random.generateData(temp, (short)0, (short)length);

return temp;

}

//返回随机数长度

public final byte GetRandSize()

{

return size;

}

}

(6)调用签名API文件的Sign.java:

/**

* Signarure

*

*/

package helloWorld;

/**

* @author lv.lang

*

*/

import javacard.framework.JCSystem;

import javacard.security.Key;

import javacard.security.KeyBuilder;

import javacard.security.Signature;

import javacard.security.HMACKey;

public class Sign {

private Signature signEngine;

private Key myKey;

private RandGenerator rand;

private byte[]temp;

public Sign() {

signEngine = Signature.getInstance(Signature.ALG_HMAC_SHA1, false);

myKey = KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, KeyBuilder.LENGTH_HMAC_SHA_1_BLOCK_64, false);

temp = JCSystem.makeTransientByteArray((short)64, JCSystem.CLEAR_ON_DESELECT);

rand = new RandGenerator();

temp = rand.GenrateSecureRand((short)100);

((HMACKey)myKey).setKey(temp, (short)0, (short)64);

}

public void init(boolean isSign)

{

if(isSign)

signEngine.init(myKey, Signature.MODE_SIGN);

else

signEngine.init(myKey, Signature.MODE_VERIFY);

}

public short sign(byte[] inBuff, short inOffset, short inLength, byte[] sigBuff, short sigOffset)

{

return signEngine.sign(inBuff, inOffset, inLength, sigBuff, sigOffset);

}

public boolean verify(byte[] inBuff, short inOffset, short inLength, byte[] sigBuff, short sigOffset, short sigLength)

{

return signEngine.verify(inBuff, inOffset, inLength, sigBuff, sigOffset, sigLength);

}

}

(7)主文件Hello.java:

package helloWorld;

//import Hello;

import javacard.framework.APDU;

import javacard.framework.Applet;

import javacard.framework.ISO7816;

import javacard.framework.ISOException;

import javacard.framework.Util;

public class Hello extends Applet {

//下面这些都是未分配空间的实例化!需要后面自己使用new关键字或者用getInstance函数分配空间!

private Des des;

private Aes aes;

private Rsa rsa;

private Sign mySign;

private Hash hmac;

byte[] result; //存储加密或解密后的结果

public Hello(){

//所有new的都应该尽量在构造时完成,否则每次发送一句APDU命令它都会new一遍空间出来,导致空间浪费

rsa = new Rsa();

aes = new Aes();

mySign = new Sign();

des = new Des();

hmac = new Hash();

result = new byte[64];//此处开辟空间的大小(S)按:DES的空间S >= 明文/密文长度, RSA的S >= 密钥字节数(512比特长度的密钥为64字节)

}

public static void install(byte[] bArray, short bOffset, byte bLength) {

// GP-compliant JavaCard applet registration

new Hello().register(bArray, (short) (bOffset + 1), bArray[bOffset]);

}

public void process(APDU apdu) {

// Good practice: Return 9000 on SELECT

if (selectingApplet()) {

return;

}

//将缓冲区与数组buf建立映射绑定

byte[] buf = apdu.getBuffer();

short lc = apdu.setIncomingAndReceive();//读取data并返回data长度lc

byte ins = buf[ISO7816.OFFSET_INS];

byte p1 = buf[ISO7816.OFFSET_P1];//p1用于判断是加密还是解密

short signLen = (short)0;

switch (ins) {

case (byte) 0x00://INS == 0x00 表明要用DES加密

if(p1 == (byte)0x00)

des.init(true); //p1 == 00 表示加密,否则表示解密

else

des.init(false);

//****** 3 *******传入明文/密文进行DES加密并得到密文/明文

des.GetResult(buf, (short)ISO7816.OFFSET_CDATA, lc, result, (short)0);

Util.arrayCopyNonAtomic(result, (short)0, buf, (short)ISO7816.OFFSET_CDATA, lc);

apdu.setOutgoingAndSend((short)5, lc);

break;//一定要有break否则会继续进入switch循环

case (byte) 0x01://INS == 0x01 表示要用RSA算法

if(p1 == (byte)0x00)

rsa.init(true);

else

rsa.init(false);

rsa.GetResult(buf, (short)ISO7816.OFFSET_CDATA, lc, result, (short)0);

Util.arrayCopyNonAtomic(result, (short)0, buf, (short)ISO7816.OFFSET_CDATA, (short)64);

apdu.setOutgoingAndSend((short)5, (short)64);

break;

case (byte)0x02://Hash-SHA

hmac.GetResult(buf, (short)ISO7816.OFFSET_CDATA, lc, result, (short)0);

Util.arrayCopyNonAtomic(result, (short)0, buf, (short)ISO7816.OFFSET_CDATA, (short)64);

apdu.setOutgoingAndSend((short)ISO7816.OFFSET_CDATA, (short)64);

break;

case (byte) 0x03://AES

if(p1 == (byte)0x00)

aes.init(true); //p1 == 00 表示加密,否则表示解密

else

aes.init(false);

//****** 3 *******传入明文/密文进行DES加密并得到密文/明文

aes.GetResult(buf, (short)ISO7816.OFFSET_CDATA, lc, result, (short)0);

Util.arrayCopyNonAtomic(result, (short)0, buf, (short)ISO7816.OFFSET_CDATA, lc);

apdu.setOutgoingAndSend((short)5, lc);

break;

case (byte)0x04://signature

if(p1 == (byte)0x00) //p1 == 00 表示做签

{

mySign.init(true);

signLen = mySign.sign(buf, (short)ISO7816.OFFSET_CDATA, lc, result, (short)0);

}

else //表示验签

{

mySign.init(false);

mySign.verify(buf, (short)ISO7816.OFFSET_CDATA, lc, result, (short)0, signLen);

}

default:

// good practice: If you don't know the INStruction, say so:

ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);

}

}

}

/* 主文件注意事项:

*

* 注意一:所有new的都应该尽量在构造时完成,否则每次发送一句APDU命令它都会new一遍空间出来,导致空间浪费

*

* 注意二:加密/解密得到的result数组开辟空间的大小(S)按:

* DES的空间S >= 明文/密文长度, RSA的S >= 密钥字节数(512比特长度的密钥为64字节)

*

* 注意三:switch-case的每个case之后一定要有break否则会继续进入switch循环

*

*

* */

1、DES和AES均作为对称密钥算法,使用大同小异,只需要修改下小范围的参数,流程都是密钥生成->设置密钥->初始化引擎->传入数据进行加解密获得结果,这里我都是用Javacard随机数生成的API来产生一个随机数的字节数组来充当密钥。

2、RSA作为非对称密钥算法,与前面对称密钥算法流程差不多,只是这里因为它有公钥和私钥,需要用一个KeyPair对象来存储密钥(包含公钥和私钥进去),这里我用KeyPair类API提供的一个自动生成密钥对的函数来生成密钥,也可以自己对公钥和私钥一个个来手动初始化。

3、然后就是消息摘要MessageDigest类APi的使用,这个类是提供来做hash的,也就是对消息产生摘要,注意这里只是生成消息摘要,并不能作为数字签名,因为它只进行了消息篡改与否的验证,并未达到“发送方身份验证/不可否认性”的功能。

4、随机数接口使用,上一篇有提到,这个也没啥好说的。

5、Signature数字签名API,这个略麻烦,流程是:生成密钥->设置密钥->初始化引擎->产生签名或验签,在使用JCOP测试的时候发现buildKey时好些算法模式参数都不被支持(install时返回6A80),对于签名API的使用示例自己还在调试中,所以后面也没测试结果截图放出来。后面有机会再补上签名API的详细介绍。

使用JCOP Shell工具进行测试:

DES测试:

de272ca8ad93a9820cd5797cf1acd10b.png

(首先传入8个字节的数据进行DES加密,然后拿加密后的密文再充当数据传进去做DES解密)

需要特别注意的是DES密钥长度规范、DES加密时开辟的outBuffer的空间(8)、以及传入的密文长度,在Des.java代码文件中都有详细提到。

AES加解密:

1144a65f9707c6c763b09e3485d684b8.png

(同样是先加密,然后再拿生成的密文扔进去解密,这里我还弄了个“输入密文长度不合规范导致返回6F00异常”的示例)

同样AES需要特别注意outBuffer开辟空间的大小,以及传入密文的长度规范,这里AES我用的是16字节的密钥,并且算法模式是No Padding的(因为Padding的那几种模式在我的JCOP工具中在初始化阶段就报这个算法模式参数的错误!),所以传入密文至少要是16个字节才能解密。哦,这里我走得匆忙忘了测试看AES密文传入是否非得是16字节倍数长度了(估计是的)。

3、MessageDigest(Hash)的测试:

b9e1bd6a8025aae577fe0862802613af.png

(注意sha-1哈希算法生成的摘要长度固定为20个字节,即便你输入的原文长度很短,它也会给你做Padding)

4、RSA算法测试:

50e53cf1089c2f0f8d90803681a6cc64.png

(后面两个[INS == 01]的才是RSA的测试样例,前面两个是DES的。先进行RSA的加密,将密文[64字节])

RSA传入的明文长度也和对称密钥的一样,随意。但是传入密文时,长度有严格要求,看上面代码文件我写的注释。

最后再次强调一次,对于java这种面向对象编程的语言,因为操作往往都是针对对象的,而对象操作就有一个很重要的使命:空间的管理。所以也像前面博客强调的,必须要给对象分配空间程序才能去执行对象里面的代码!同时由于Javacard的特性,卡内存储空间很小,所以new出来的对象尽量重复使用,并且为了避免每次process applet的时候都new一遍对象,应在install或者构造函数时就完成分配空间/实例化对象这些工作,这样才能避免每次发送一次apdu命令都产生一次对象,除非代码判断下对象是否为null,若不为空则先=null,把前面的对象给毙掉再new。这个是前辈们给我看了代码风格后的教训。

/*********************          分隔线                  ************************************/

2016-7-27日找“师傅”帮我看了下工程代码风格,发现自己写的代码还有很多问题。

(1)首先,对于INS值判断的case里面的执行过程,应该封装成一个函数,如下面的代码。

(2)其次,很多中间变量可以省掉,比如p1,p2就不必重新再定义一遍了,直接用buf[ISO7816....],因为Javacard应用开发必须注意资源的节省!

(3)然后,Des.java这些辅助类在实际应用开发中没必要这样写,而是应该直接在主applet文件里面直接建立对象和相关函数,避免文件的链接的同时减小了代码占用的空间,要知道卡片里面的空间是非常宝贵的,所以才需要对java文件进行压缩再压缩得到cap包,最后把压缩到精致的字节码文件烧到卡片上去执行。

(4)然后顺带解决了几个关键问题,首先,之前发现使用MessageDigest产生的摘要每次都是随机变化的不符合实际需求,后面师傅帮我测试了下,才发现时因为自己测试时输入的消息太短了!只要输入的消息足够长,那么产生的摘要就是固定的,所以才能进行验证。因为太短的话会对消息进行padding再做摘要,而这个padding的内容应该是随机变化的,然后padding的内容和原消息交叉混合下,就导致前后摘要看起来毫无关联地随机性变化。

(5)还有个问题就是AES或者Sign里面选用的算法模式,发现很多算法模式在使用JCOP工具调试时,在install过程中报6A80(错误的参数)错,师傅说估计是因为JCOP使用的算法库不支持该算法模式,即便工程没报错(工程使用的API jar包里有该模式选项)。

(6)然后还有个比较大的问题是,自己还需要去做算法模式的遍历!而不是仅仅测试通过一两种模式就狂欢了。比如sha,就得遍历sha1,sha256……所有模式。写成一个遍历了所有算法组合的applet工程,下次有实际任务说要去测试卡片里面所有的算法库,才能快速用到这个先前开发好的applet去测试,不然每次都要写一个applet对于这样一个很基础简单的小测试来说多花时间。

所以针对上述问题,下面的代码做了些改善。后面需要自己继续完善的地方还有:中间变量的继续压缩,多个文件整合到同个文件,以及算法所有组合的遍历……

最后顺带记录下今天问到关于一个问题的解答:

在javacard中,new出来的对象时存放在EEPROM空间中永久保存的,那么如果重复使用呢?每次跑程序的时候不是会重新new或者需要新创建的句柄去和旧的对象空间关联起来么?

回答:其实后面的问题并不是自己想象中的那样,要清楚在applet的生命周期中,install仅仅只执行了一遍!也就是说,在安装applet环节中new出来的对象被永久使用了,后面不会再执行到这部分的代码去new对象了,因为后面就相当于一个无限循环(从process函数里面开始执行代码),install那些代码只在最开始时执行了一遍。所以并不存在说new了多个对象的问题。

2016-7-28代码改善版本:

(1)Sign.java

/**

* Signarure

*

*/

package helloWorld;

/**

* @author lv.lang

*

*/

import javacard.security.Signature;

public class Sign {

private Signature signEngine;

//private Key myKey;

//private RandGenerator rand;

//private byte[]temp;

private Rsa rsa;

public Sign() {

rsa = new Rsa();

//算法模式参数选用不适会导致6A80,为啥? //ALG_RSA_MD5_PKCS1模式决定了最后生成的签名长度=RSA密钥长度(如64字节)

signEngine = Signature.getInstance(Signature.ALG_RSA_MD5_PKCS1, false);

//myKey = KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, KeyBuilder.LENGTH_HMAC_SHA_1_BLOCK_64, false);

//temp = JCSystem.makeTransientByteArray((short)64, JCSystem.CLEAR_ON_DESELECT);

//rand = new RandGenerator();

//temp = rand.GenrateSecureRand((short)100);

//((HMACKey)myKey).setKey(temp, (short)0, (short)64);

}

public void init(boolean isSign)

{

if(isSign)

signEngine.init(rsa.GetPrivateKey(), Signature.MODE_SIGN);

//signEngine.init(myKey, Signature.MODE_SIGN);

else

signEngine.init(rsa.GetPublicKey(), Signature.MODE_VERIFY);

//signEngine.init(myKey, Signature.MODE_VERIFY);

}

//做签

public short sign(byte[] inBuff, short inOffset, short inLength, byte[] sigBuff, short sigOffset)

{

return signEngine.sign(inBuff, inOffset, inLength, sigBuff, sigOffset);

}

//验签

public boolean verify(byte[] inBuff, short inOffset, short inLength, byte[] sigBuff, short sigOffset, short sigLength)

{

return signEngine.verify(inBuff, inOffset, inLength, sigBuff, sigOffset, sigLength);

}

}

(2)RandGenerator.java

package helloWorld;

import javacard.framework.JCSystem;

import javacard.security.RandomData;

public class RandGenerator

{

private byte[] temp;//随机数的值

private RandomData random;

private byte size;//随机数长度

//构造函数

public RandGenerator()

{

size = (byte)4;

temp = JCSystem.makeTransientByteArray((short)4, JCSystem.CLEAR_ON_DESELECT);

//类当中有getInstance的都要先调用这个函数获取对象实例才能使用其他方法,不然6F00

random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);

}

//产生length长度的随机数并返回

public final byte[] GenrateSecureRand(short length)

{

temp = new byte[length];

//生成4bit的随机数

random.generateData(temp, (short)0, (short)length);

return temp;

}

//返回随机数长度

public final byte GetRandSize()

{

return size;

}

}

(3)Hash.java

/**

* MessageDigest

*/

package helloWorld;

/**

* @author lv.lang

*

*/

import javacard.security.MessageDigest;

public class Hash {

private MessageDigest HashEngine;

public Hash() {

HashEngine = MessageDigest.getInstance(MessageDigest.ALG_SHA, false);

//HashEngine.getInitializedMessageDigestInstance(MessageDigest.ALG_SHA, false).

}

public void GetResult(byte[] inBuf, short inOffset, short inLength, byte[] outBuf, short outOffset)

{

//sha-1产生的摘要结果固定为160bit[20bytes]

//其他sha对应的摘要字节数可查看MessageDigest.ALG_弹出的注释窗口

HashEngine.doFinal(inBuf, inOffset, inLength, outBuf, outOffset);

}

}

/*

* 1.sha-1产生的摘要结果固定为160bit[20bytes],并且即便你输入的原文过短,它也会帮你做padding

*

* 2.因为padding的数是随机的,所以如果输入过短,会导致自动padding,然后经过摘要算法的混啊混啊,最后得到的摘要是随机变化的!

* 所以要保证输入的消息足够长,才能得到固定的摘要。而且实际应用中,传入的一般都是文件级别的数据,并不会有这么短的数据!

*/

(4)Rsa.java

package helloWorld;

import javacardx.crypto.Cipher;

import javacard.framework.ISO7816;

import javacard.framework.ISOException;

import javacard.security.KeyBuilder;

import javacard.security.KeyPair;

import javacard.security.Key;

public class Rsa

{

private Cipher RSAEngine;

//非对称加密算法需要用密钥对的形式存储密钥(公钥私钥):囊括PublicKey和PrivateKey对象

//而不能用publicKey、privateKey分开来存[是因为公钥和私钥的生成的相关而不是独立的?]

private KeyPair keypair;

public Rsa()

{

//new一个密钥对对象

//第二个参数决定了密钥长度的同时,决定了生成密文的长度(因为密文长度=密钥长度[模数])为512比特,也就是64字节,转成十六进制表示为40

keypair = new KeyPair(KeyPair.ALG_RSA_CRT, KeyBuilder.LENGTH_RSA_512);//只支持这个构造函数,用KeyPair(PublicKey,PrivateKey)构造会异常

//调用函数自动生成随机的密钥(包括公钥和私钥)

keypair.genKeyPair();

try

{

//用Cipher.ALG_RSA_ISO14888会出错--6F00,ISO7816--接触式,14...--非接(触式)

RSAEngine = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);

}

catch(Exception e)

{

ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);

}

}

public void init(boolean isEncryption)

{

if(isEncryption)

RSAEngine.init(keypair.getPrivate(), Cipher.MODE_ENCRYPT);

else

RSAEngine.init(keypair.getPublic(), Cipher.MODE_DECRYPT);

}

public void GetResult(byte[] inBuf, short inOffset, short inLength, byte[] outBuf, short outOffset)

{

//****** 3 *******传入密文进行加密并得到密文

RSAEngine.doFinal(inBuf, inOffset, inLength, outBuf, outOffset);

}

public Key GetPrivateKey()

{

return keypair.getPrivate();

}

public Key GetPublicKey()

{

return keypair.getPublic();

}

}

/* RSA注意事项:

*

*

* 注意一:

* 为何不是所有传入的密文都能解密(6F00)?

* 并且只有用本次的密钥产生过的密文格式传入去解密才能no error

* 这是因为如果你拿什么密钥都能解密别人的密文,那就违背了密码算法的本意了呀!!!

*

*

* 注意二:

* RSA最终生成的密钥长度>=64bits且为64bits的倍数,若不足,则genKeyPair函数会自动补全到位

*

* 注意三:RSA算法生成的密文的长度 = 密钥的长度,所以这里注意给dofinal函数传入的输出缓冲区的大小不能太小

*

* 注意四:RSA明文传入加密,长度可随意(>=0),函数会自动padding。

* 但是传入解密的密文必须是 >= 密钥长度(如64字节),且为密钥长度的倍数,最后解密出来的明文长度也是等于密钥长度

*

* */

(5)Des.java

package helloWorld;

import javacard.framework.JCSystem;

import javacard.security.DESKey;

import javacard.security.Key;

import javacard.security.KeyBuilder;

import javacardx.crypto.Cipher;

public class Des

{

private Cipher DESEngine;

private Key myKey;

private byte[] temp;

private RandGenerator rand;

public Des()

{

//必须先初始化(获得实例instance才能init否则报错)

DESEngine = Cipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M1, false);

//buildKey创建的是未初始化的key,密钥值需用setKey函数手动赋值

myKey = KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES, false);

//设置暂存变量temp

temp = JCSystem.makeTransientByteArray((short)100, JCSystem.CLEAR_ON_DESELECT);

//给rand对象也分配空间,不然无法执行RandGenerator类的代码!!

rand = new RandGenerator();

//****** 1 *******首先自动生成个密钥

//产生64bit随机数

temp = rand.GenrateSecureRand((short)100);

//Util.arrayFillNonAtomic(temp1, (short)16, (short)48, (byte)0x11);

//设置密钥--拿随机数当密钥.

//注意!DES密钥必须要是8字节的倍数长度,如果不是,下面这个setKey函数会截取64bits长度,剩余的抛弃掉!所以上面下来的100bits随机数只会取64bits

((DESKey)myKey).setKey(temp, (short)0);

}

public void init(boolean isEncryption)

{

//short b = myKey.getSize(); //可用debug查看该变量值

if(isEncryption)

//****** 2 *******初始化加密密钥和加密模式

DESEngine.init(myKey, Cipher.MODE_ENCRYPT);

else

DESEngine.init(myKey, Cipher.MODE_DECRYPT);

}

public void GetResult(byte[] inBuf, short inOffset, short inLength, byte[] outBuf, short outOffset)

{

//****** 3 *******传入密文/明文进行加密并得到明文/密文

//特别注意DES加密结果是8的倍数,所以outBuf开辟的空间至少要为8字节.并且DES解密只能处理8的倍数次方的密文输入.否则6F00

DESEngine.doFinal(inBuf, inOffset, inLength, outBuf, outOffset);

}

}

/* DES注意事项:

*

* 注意一:DES密钥必须要是8字节的倍数长度,如果不是,setKey函数会截取64bits长度,剩余的抛弃掉!所以上面下来的100bits随机数只会取64bits

*

* 注意二:DES加密结果是8的倍数,所以outBuf开辟的空间至少要为8字节

*

* 注意三:注意算法(DES/AES/RSA等都是)并不会对密文进行padding,所以密文传入长度需要>=密钥长度(如8字节)且为密钥长度的倍数,否则均会报6F00.

* 明文传入长度随意(>=0),函数也自动会有padding

* */

(6)Aes.java

/**

* AES

*/

package helloWorld;

import javacard.security.Key;

import javacard.security.KeyBuilder;

import javacard.security.AESKey;

import javacardx.crypto.Cipher;

import javacard.framework.JCSystem;

/**

* @author lv.lang

*

*/

public class Aes {

private Key myKey;

private Cipher AESEngine;

private byte[] temp;

private RandGenerator rand;

/**

*

*/

public Aes() {

rand = new RandGenerator();

temp = JCSystem.makeTransientByteArray((short)128, JCSystem.CLEAR_ON_DESELECT);

temp = rand.GenrateSecureRand((short)128);

//这里的算法参数只支持NOPAD,其他如ALG_AES_CBC_PKCS5都会报6A80,估计是因为JCOP工具不支持

AESEngine = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, true);

myKey = KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false);

((AESKey)myKey).setKey(temp, (short)0);

}

public void init(boolean isEncryption)

{

/**

* 代码改进三:Cipher.MODE_ENCRYPT这类参数的确定用p2确定,为了省空间和效率牺牲代码可观性

*/

//short b = myKey.getSize(); //可用debug查看该变量值

if(isEncryption)

AESEngine.init(myKey, Cipher.MODE_ENCRYPT);

else

AESEngine.init(myKey, Cipher.MODE_DECRYPT);

}

public void GetResult(byte[] inBuf, short inOffset, short inLength, byte[] outBuf, short outOffset)

{

AESEngine.doFinal(inBuf, inOffset, inLength, outBuf, outOffset);

}

}

/*注意事项:

* 1.如果getInstance中的算法模式选用了NOPAD的话,明文传入也必须达到>=密钥长度(如16字节)且为密钥长度的倍数

*

* 2.密文传入当然也和DES一样,密文不会给你padding的,所以传入长度需要>=密钥长度(如16字节)且为密钥长度的倍数

*

* 3.为啥getInstance中的算法模式很多选项选用了都会报0A80呢?不支持这种模式的算法吗?

*

*/

(7)Hello.java

package helloWorld;

//import Hello;

import javacard.framework.APDU;

import javacard.framework.Applet;

import javacard.framework.ISO7816;

import javacard.framework.ISOException;

import javacard.framework.Util;

public class Hello extends Applet {

//下面这些都是未分配空间的实例化!需要后面自己使用new关键字或者用getInstance函数分配空间!

private Des des;

private Aes aes;

private Rsa rsa;

private Sign mySign;

private Hash hmac;

byte[] result; //存储加密或解密后的结果

public Hello(){

//所有new的都应该尽量在构造时完成,否则每次发送一句APDU命令它都会new一遍空间出来,导致空间浪费

rsa = new Rsa();

aes = new Aes();

mySign = new Sign();

des = new Des();

hmac = new Hash();

result = new byte[64];//此处开辟空间的大小(S)按:DES的空间S >= 明文/密文长度, RSA的S >= 密钥字节数(512比特长度的密钥为64字节)

}

/**

* 代码风格改进一:

* 把case里面对每种INS的处理封装成函数放构造函数和install及process中间

* */

private void handleDES(APDU apdu) throws ISOException

{

byte[] buf = apdu.getBuffer();

apdu.setIncomingAndReceive();//读取data,必不可少

/**

* 代码风格改进二:直接通过buf传递p1,p2,lc等这些参数,从而减少中间变量的使用

* */

if(buf[ISO7816.OFFSET_P1] == (byte)0x00)

des.init(true); //p1 == 00 表示加密,否则表示解密

else

des.init(false);

//****** 3 *******传入明文/密文进行DES加密并得到密文/明文

des.GetResult(buf, (short)ISO7816.OFFSET_CDATA, (short)buf[ISO7816.OFFSET_LC], result, (short)0);

Util.arrayCopyNonAtomic(result, (short)0, buf, (short)ISO7816.OFFSET_CDATA, (short)buf[ISO7816.OFFSET_LC]);

apdu.setOutgoingAndSend((short)5, (short)buf[ISO7816.OFFSET_LC]);

}

private void handleRSA(APDU apdu) throws ISOException

{

byte[] buf = apdu.getBuffer();

apdu.setIncomingAndReceive();//读取data

if(buf[ISO7816.OFFSET_P1] == (byte)0x00)

rsa.init(true);

else

rsa.init(false);

rsa.GetResult(buf, (short)ISO7816.OFFSET_CDATA, (short)buf[ISO7816.OFFSET_LC], result, (short)0);

Util.arrayCopyNonAtomic(result, (short)0, buf, (short)ISO7816.OFFSET_CDATA, (short)64);

apdu.setOutgoingAndSend((short)5, (short)64);

}

private void handleSHA(APDU apdu) throws ISOException

{

byte[] buf = apdu.getBuffer();

apdu.setIncomingAndReceive();//读取data

hmac.GetResult(buf, (short)ISO7816.OFFSET_CDATA, (short)buf[ISO7816.OFFSET_LC], result, (short)0);

Util.arrayCopyNonAtomic(result, (short)0, buf, (short)ISO7816.OFFSET_CDATA, (short)64);

apdu.setOutgoingAndSend((short)ISO7816.OFFSET_CDATA, (short)64);

}

private void handleAES(APDU apdu) throws ISOException

{

byte[] buf = apdu.getBuffer();

apdu.setIncomingAndReceive();//读取data

if(buf[ISO7816.OFFSET_P1] == (byte)0x00)

aes.init(true); //p1 == 00 表示加密,否则表示解密

else

aes.init(false);

//****** 3 *******传入明文/密文进行DES加密并得到密文/明文

aes.GetResult(buf, (short)ISO7816.OFFSET_CDATA, (short)buf[ISO7816.OFFSET_LC], result, (short)0);

Util.arrayCopyNonAtomic(result, (short)0, buf, (short)ISO7816.OFFSET_CDATA, (short)buf[ISO7816.OFFSET_LC]);

apdu.setOutgoingAndSend((short)5, (short)buf[ISO7816.OFFSET_LC]);

}

private void handleSIGN(APDU apdu) throws ISOException

{

byte[] buf = apdu.getBuffer();

apdu.setIncomingAndReceive();//读取data

short signLen = (short)0;

if(buf[ISO7816.OFFSET_P1] == (byte)0x00) //p1 == 00 表示做签

{

mySign.init(true);

signLen = mySign.sign(buf, (short)ISO7816.OFFSET_CDATA, (short)buf[ISO7816.OFFSET_LC], result, (short)0);

Util.arrayCopyNonAtomic(result, (short)0, buf, (short)ISO7816.OFFSET_CDATA, signLen);

apdu.setOutgoingAndSend((short)ISO7816.OFFSET_CDATA, signLen);

}

else //表示验签

{

mySign.init(false);

//需要apdu同时输入Message以及签名,所以用p2参数来切分开二者

//short temp = (short)(lc - p2); //仅用作调试时观察值的变化

boolean verifyIsPass = mySign.verify(buf, (short)ISO7816.OFFSET_CDATA, (short)buf[ISO7816.OFFSET_P2], buf, (short)(ISO7816.OFFSET_CDATA+buf[ISO7816.OFFSET_P2]), (short)(buf[ISO7816.OFFSET_LC] - buf[ISO7816.OFFSET_P2]));

if(verifyIsPass)

buf[ISO7816.OFFSET_CDATA] = (byte)0x00;

else

buf[ISO7816.OFFSET_CDATA] = (byte)0x01;

apdu.setOutgoingAndSend((short)ISO7816.OFFSET_CDATA, (short)1);

}

}

public static void install(byte[] bArray, short bOffset, byte bLength) {

// GP-compliant JavaCard applet registration

new Hello().register(bArray, (short) (bOffset + 1), bArray[bOffset]);

}

public void process(APDU apdu) {

// Good practice: Return 9000 on SELECT

if (selectingApplet()) {

return;

}

/*

//将缓冲区与数组buf建立映射绑定

byte[] buf = apdu.getBuffer();

short lc = apdu.setIncomingAndReceive();//读取data并返回data长度lc

byte ins = buf[ISO7816.OFFSET_INS];

byte p1 = buf[ISO7816.OFFSET_P1];//p1用于判断是加密还是解密

short signLen = (short)0;

byte p2 = buf[ISO7816.OFFSET_P2];*/

byte[] buf = apdu.getBuffer();

switch (buf[ISO7816.OFFSET_INS]) {

case (byte) 0x00://INS == 0x00 表明要用DES加密

handleDES(apdu);

break;//一定要有break否则会继续进入switch循环

case (byte) 0x01://INS == 0x01 表示要用RSA算法

handleRSA(apdu);

break;

case (byte)0x02://Hash-SHA

handleSHA(apdu);

break;

case (byte) 0x03://AES

handleAES(apdu);

break;

case (byte)0x04://signature

handleSIGN(apdu);

break;

default:

// good practice: If you don't know the INStruction, say so:

ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);

}

}

}

/**主文件注意事项:

*

* 注意一:所有new的都应该尽量在构造时完成,否则每次发送一句APDU命令它都会new一遍空间出来,导致空间浪费

*

* 注意二:加密/解密得到的result数组开辟空间的大小(S)按:

* DES的空间S >= 明文/密文长度, RSA的S >= 密钥字节数(512比特长度的密钥为64字节)

*

* 注意三:switch-case的每个case之后一定要有break否则会继续进入switch循环

*

* 注意三:验签时因为需要同时传入原始消息以及签名,所以一段apdu命令需要包含了“消息+签名”

* 所以注意lc的值等于lenOf(Message)+lenOf(sign)。

* 同时,为了切分开这两种数据,我额外用到了p2参数。

* 还有要注意的是p2和lc的值传进去的都是十六进制表达,Applet在收到apdu之后会把它们自动转成十进制表示(debug即可观察到),

* 所以p2表达的是Message字节长度的十六进制表达,lc表达的是data部分的字节长度的十六进制表达,并不需要画蛇添足去转换成十进制再send

*

* 代码风格改进三:实际项目开发时需要将Des.java等这些辅助文件全部集成到主Applet文件中

*

* */

*************   分隔线  *****************

隔了几天发现自己的代码有一个比较严重的错误,就是对于short定义的长度问题,例如在建立暂存数组或者复制数组的函数里面:

2d0527ea2809b683be71c553f6ac3ea5.png

都有一个short length的参数,这里的short length自己一度以为表示有多少个bit,然后需要再根据bits计算bytes,但是这两天才发现它表示的是有length个字节!而不是length个bit。例如JCSystem.makeTransientBooleanArray(64,...)这样表示的是建立一个64字节的字节数组,而不是64bits的数组!上面的代码我没直接修改这个错误, 让它成为前车之鉴吧,后面会继续发布更新版的博文和代码。

*******   2016-9-7更新 *********

最新版的代码做了些小优化,已放到我的github上,地址:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值