Javacard DES/AES/RSA/Hash/Sinature算法API使用示例

来源:互联网 发布:手机网络挣钱 编辑:程序博客网 时间:2024/05/30 05:40

前面一篇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);//设置暂存变量temptemp = 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);elseDESEngine.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的倍数次方的密文输入.否则6F00DESEngine.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字节,转成十六进制表示为40keypair = 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);elseRSAEngine.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);elseAESEngine.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的都要先调用这个函数获取对象实例才能使用其他方法,不然6F00random = 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);elsesignEngine.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 registrationnew Hello().register(bArray, (short) (bOffset + 1), bArray[bOffset]);}public void process(APDU apdu) {// Good practice: Return 9000 on SELECTif (selectingApplet()) {return;}//将缓冲区与数组buf建立映射绑定byte[] buf = apdu.getBuffer();short lc = apdu.setIncomingAndReceive();//读取data并返回data长度lcbyte 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 表示加密,否则表示解密elsedes.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);elsersa.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-SHAhmac.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://AESif(p1 == (byte)0x00)aes.init(true); //p1 == 00 表示加密,否则表示解密elseaes.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://signatureif(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测试:


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

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


AES加解密:


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

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


3、MessageDigest(Hash)的测试:


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


4、RSA算法测试:


(后面两个[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);elsesignEngine.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的都要先调用这个函数获取对象实例才能使用其他方法,不然6F00random = 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字节,转成十六进制表示为40keypair = 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);elseRSAEngine.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);//设置暂存变量temptemp = 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);elseDESEngine.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的倍数次方的密文输入.否则6F00DESEngine.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);elseAESEngine.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 表示加密,否则表示解密elsedes.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();//读取dataif(buf[ISO7816.OFFSET_P1] == (byte)0x00)rsa.init(true);elsersa.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();//读取datahmac.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();//读取dataif(buf[ISO7816.OFFSET_P1] == (byte)0x00)aes.init(true); //p1 == 00 表示加密,否则表示解密elseaes.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();//读取datashort 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;elsebuf[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 registrationnew Hello().register(bArray, (short) (bOffset + 1), bArray[bOffset]);}public void process(APDU apdu) {// Good practice: Return 9000 on SELECTif (selectingApplet()) {return;}/*//将缓冲区与数组buf建立映射绑定byte[] buf = apdu.getBuffer();short lc = apdu.setIncomingAndReceive();//读取data并返回data长度lcbyte 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-SHAhandleSHA(apdu);break;case (byte) 0x03://AEShandleAES(apdu);break;case (byte)0x04://signaturehandleSIGN(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定义的长度问题,例如在建立暂存数组或者复制数组的函数里面:



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


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

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

https://github.com/Victor-Lv/Algorithm

0 0
原创粉丝点击