********** ? 2017年3月15日留言 ——關于java卡Applet系列csdn博文 *************
貌似有不少人在看我寫的幾篇關于java卡applet的博文,也收到了一些評論指正博文錯誤,或者私信叫我發代碼文件過去。在此需要說明的是,java卡applet的這幾篇博文是自己在初學java卡applet的時候寫的,當時菜鳥一枚(雖然現在也還是菜鳥),未免會有些疏漏,僅供參考,希望大家在看的時候別太當權威,如果博文中有錯誤也請諒解。另外關于叫我發代碼的,因為有些代碼文件中的部分代碼并非是我寫的,怕牽涉到版權問題,所以就不能私發了,不過我在博文放的我的那個github地址里面的代碼可以去看看,另外因為版權問題其實我后期對博文很多地方的代碼做了刪改,所以現在看起來博文好像有些凌亂,幾個月前自己已經不做java卡applet這方面的開發了,所以也就沒再細心去修改好博文了,java卡applet在行業內已經有好幾年的歷史了,大家上網多找找,其實能找到很多更好的學習材料,當然,有不少是全英的,不過其實很多很好的材料就只有英文的,所以多去翻翻前輩們的精華吧!Best
wish~
********************** 新舊分割線 ?*****************************
很多時候我們并不需要自己去實現一個較為復雜的算法,而只需要知道怎么去調用現有的實現。API調用,在C/C++是用include+函數的形式,java其實也無非就是import+類(方法、變量)的形式,其他語言的也差不多,例如web里面的前端框架,所謂框架其實就是一堆別人已經寫好的代碼,你拿去用,然后繼續在上面填充自己的代碼,說到底其實就是代碼復用。而在java這里,因為以.class文件的形式封裝了實現,我們看到的接口:一堆.class文件里面方法的具體實現被隱藏了,只能看到個函數原型,所以調用javacard
API的難度在于你得去看別人寫的API注釋(全英,學好英語的作用有木有),去了解每個函數干什么的,然后你才知道要實現自己的功能需要用到它的哪些函數以及調用順序是怎樣的,一般在API(庫)的目錄下面都會有使用文檔(html形式的),用eclipse直接翻.class文件的話會有一堆html標簽符看著眼疼。
話說不多,上代碼:
Des.java(調用DES算法API的主要文件):
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)64, JCSystem.CLEAR_ON_DESELECT);
//給rand對象也分配空間,不然無法執行RandGenerator類的代碼!!
rand = new RandGenerator();
//****** 1 *******首先自動生成個密鑰
//產生64bit隨機數
temp = rand.GenrateSecureRand((short)64);
//Util.arrayFillNonAtomic(temp1, (short)16, (short)48, (byte)0x11);
//設置密鑰--拿隨機數當密鑰
((DESKey)myKey).setKey(temp, (short)0);
//****** 2 *******初始化加密密鑰和加密模式
DESEngine.init(myKey, Cipher.MODE_ENCRYPT);
}
public final void GetCipher(byte[] inBuf, short inOffset, short inLength, byte[] outBuf, short outOffset)
{
//****** 3 *******傳入密文進行加密并得到密文
//需要特別注意doFinal加密之后的結果估計有64位以上,所以outBuf在前面的定義時也要new出足夠的大小,否則又no precise...
DESEngine.doFinal(inBuf, inOffset, inLength, outBuf, outOffset);
}
}
(徒手生成密鑰太麻煩,所以我這里直接調用生成隨機數的API,拿隨機數來當密鑰)
接著就是生成隨機數的RandGenerator.java文件,調用了RandomData API:
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;
}
}
最后是測試Applet,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 = new Des();
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[] data = new byte[lc];
Util.arrayCopyNonAtomic(buf, ISO7816.OFFSET_CDATA, data, (short)0, lc);
//byte[] src = {'h','e','l','l','o',',','w','o','r','l','d'};//11 bytes
byte[] cipher = new byte[128];
byte ins = buf[ISO7816.OFFSET_INS];
switch (ins) {
case (byte) 0x00://INS == 0x00 表明要用DES加密
//****** 3 *******傳入密文進行加密并得到密文
des.GetCipher(data, (short)0, lc, cipher, (short)0);
Util.arrayCopyNonAtomic(cipher, (short)0, buf, (short)ISO7816.OFFSET_CDATA, lc);
apdu.setOutgoingAndSend((short)5, lc);
break;//一定要有break否則會繼續進入switch循環
case (byte) 0x01:
break;
default:
// good practice: If you don't know the INStruction, say so:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
}
然后用JCOP Shell工具執行測試。
測試腳本(des.jcsh):
/select 554433221100
/send 00000000041234567804
/send 00000000048765432104
/第一句表示選定該AID的Applet,后兩句都是輸入一串0x04長度的數據讓Applet去執行加密(注意十六進制和十進制數之間轉化,雖然這里0x04長度等于十進制的4個長度,但是0x20就千萬記得要轉換了)。
執行腳本看到結果:
Applet返回0x04長度的加密后的密文。
最后記錄幾個自己在寫代碼的過程中遇到的一些需要注意的事項。
1:因為涉及到了多個類,所以會有在另一個類(文件)中建立類的對象,但是千萬記得要給這個對象分配空間!類如果有有getInstance函數的就調用這個函數進行對象的實例化,如果沒有的就用new關鍵字來分配空間給對象!之前自己就出現了沒分配空間給對象的情況,結果測試時返回6F00,然后調試的時候一直是跳不進去Des.java的,因為根本沒分配空間給這個對象,也就無從談跳過去執行Des.java里面的代碼了。這個在面向對象編程尤其需要注意。
2:暫存對象(包含變量/數組)與永久對象。用JCSystem.makeTransientByteArray(第一個參數表示數組長度,第二個參數表示聲明周期(一般用CLEAR_ON_DESELECT也就是該AID的Applet被deselect的時候被回收))函數可以聲明一個固定長度的暫存數組。為什么要用到暫存對象呢?因為javacard畢竟那么小,它所擁有的存儲空間(無論是內存或者是ROM(包括那個啥EEPROM))都是很有限的,ROM一般是用來存卡片操作系統(COS,一般用匯編寫成)和虛擬機(必須下層有操作系統作為基礎,自己并不是操作系統)的,永久對象會存放在EEPROM空間當中,而暫存對象(數組)存放在RAM空間中,自然讀寫速度就快很多,所以對于需要更新次數頻繁的數據,并且暫存即可的,聲明為暫存對象可提高程序效率。同時,EEPROM中創建的對象,對這些對象的操作必須具有原子性(例如javacard的事務[transaction]處理機制),而RAM里面對象的處理自然不具有原子性。
3:閱讀API里面的函數時容易發現有些在前面帶“@deprecated”字眼,@override表示覆蓋父類的方法,而前面這個表示不建議使用的方法(即將被淘汰或怎樣的),一般這個方法都會被刪除線劃掉。
4:然后說一個跟des加密不相關的,是跟公鑰密碼系統如RSA算法相關的,des這些對稱密碼系統只需要一個密鑰,而公鑰系統需要公鑰私鑰,所以需要用一個KeyPair對象去存儲私鑰和公鑰,而不能用PrivateKey對象和PublicKey對象就算了,需要用KeyPair對象囊括住這兩個對象,后面會繼續寫RSA算法API調用的blog。
5:switch相當于一個循環,在每一個case后面都必須要加一個break關鍵字,否則執行完一次case之后會繼續跑去循環,下一次的話case的值是不確定的,所以會跳去執行default語句里面的代碼,所以如果執行的某個case里面沒有break,程序后面還會執行default里面的語句。
6:提到對象的初始化(分配空間),注意這個操作一般放在類的構造函數當中,如果是Applet主文件,就在install()的時候完成這些操作。
7:最后抽取幾個上面的關鍵函數出來提一提:
DESEngine = Cipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M1, false);
上面這句代碼是用來實例化DES引擎對象的,做一些分配空間和初始化之類的工作,替代了new去完成分配空間的事。
myKey = KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES, false);
然后這句代碼是用來創建未初始化的key對象,前面我們說到對象你在用它們之前必須給他們先分配空間!這里buildKey出來的只是分配好了空白空間的對象,后面需要自己用setKey函數來初始化密鑰。
((DESKey)myKey).setKey(temp, (short)0);
這句就是設置(初始化)密鑰了,temp在這里存的是前面提到的生成的隨機數,拿來當密鑰使。
密鑰設置好了,然后就是將這個密鑰扔到DES引擎對象里面去,用下面這個函數:
DESEngine.init(myKey, Cipher.MODE_ENCRYPT);
第二個參數表示的是設置模式,有加密和解密兩種。
最后就可以傳入數據進行加密得到密文了:
DESEngine.doFinal(inBuf, inOffset, inLength, outBuf, outOffset);
****** ? ?更新分隔線 ? *********
這個代碼挺不完善的,升級版的可以參考我另一篇博文: