参考Java Card & STK Applet Development Guidelines文档。
遵从以下规则能够让你的程序更加标准化和减少出现问题的可能性。
1.尽可能使用API,而不是造轮子。
使用API可以减少重复代码,而且API可以提供的强大功能。
2.为了确保卡的安全性,PIN码以及秘钥不能存储在基本数组中。可以采取以下方法存储。
OwnerPIN pin; Pin = new OwnerPIN (tryLimitApplet, maxPINSizeApplet); Pin.update( pinApplet offset, length ) Key rsaPrivateKey; rsaPrivateKey = (RSAPPrivateKey) KeyBuilder.buildKey (KeyBuilder.TYPE_RSA_PRIVATE, KeyBuilder.LENGTH_RSA_512, False); rsaPrivateKey.setExponent(bufferExp, offsetExp, lengthExp); rsaPrivateKey.setModulus(bufferMod, offsetMod, lengthMod);
3.敏感的数据应该在会话开始初始化,会话结束后清除,(该规则适用于全局数组,秘钥更新和会话对象)。
注意:要重置一个key,需要使用clear()方法,而不是使用0或者其他值覆盖。因为使用clear()方法,秘钥的初始化状态重置为false。
sessionflags = JCSystem.makeTransientByteArray ((short)13,JCSystem.CLEAR_ON_RESET); sessionflags = JCSystem.makeTransientByteArray ((short)13,JCSystem.CLEAR_ON_DESELECT); sessionKey = (DESKey)KeyBuilder.buildKey( KeyBuilder.TYPE_DES_TRANSIENT_RESET, KeyBuilder.LENGTH_DES3_2KEY, false); sessionKey = (DESKey)KeyBuilder.buildKey( KeyBuilder.TYPE_DES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_DES3_2KEY, false);
蓝色的代码表示数据会在卡复位的时候清除,即卡断电或者重启的时候。而红色的代码表示数据会在该应用断开选择的时候清除数据。
4.敏感的数据应该存储在临时数据类型中,并且避免存储在APDU的buffer中。
sessionflags = JCSystem.makeTransientByteArray ((short)13,JCSystem.CLEAR_ON_DESELECT);
5.保护你的敏感数据,防止回滚攻击(Rollback attack)
回滚(Rollback)指的是程序或数据处理错误,将程序或数据恢复到上一次正确状态的行为。回滚包括程序回滚和数据回滚等类型。
当你的代码正在处于transaction(事务,程序执行单元)中突然断电,卡状态将会回滚到旧值,攻击者可以利用这点进行攻击。当处理敏感数据的时候,一定要引入计数管理器,以应对处理敏感数据时的回滚攻击。
规则:假如预防机制需要依靠计数器的话,并且该机制需要被管理,需要按照以下行为进行执行。
(1)检查计数器的值,如果被置为0的话,返回错误;
(2)原子操作(不可打断)递减计数器的值;
(3)验证这个代码不处于transaction(事务,程序执行单元);
(4)运行“try”函数;
(5)假如“try”运行成功,原子操作增加计数器的值。
6.所有初始化的buffer,一定要定义为静态数据。
初始化数组定义为静态数据有以下好处:
(1)节省执行时间,在初始化的过程中;
(2)节省代码空间;
(3)在初始化的过程中防止错误(transction 缓存满了)。
7.全局变量和局部变量
在javacard应用程序中有许多变量:实例变量(全局变量),在对象初始化的时候创建;局部变量,他只能在申明他们的函数或者快中使用。
一些基本的规则:
(1)当能够使用静态变量的时候,尽量不要使用全局变量。他可以减少类实例的冗余。
(2)尽量少使用全局变量。
(3)不可变的常量必须申明为static final。
(4)不要申明太多参数,在一个方法中。非常消耗内存(RAM)。
(5)局部变量的访问速度比全局变量快:
Working variables(像循环计数器,临时存储变量),一定要在方法中申明为局部变量而不是类中的全局变量,这样可以防止超过NVM的容量;
Working buffers应该尽可能申明为transient 数组(可以锁定在RAM区域),这样可以避免超过NVM容量限制。假如缺少RAM资源,一定要注意 Working buffers的更新频率(更新时间点以及更新次数)。
RAM空间应该需要时才使用,因为RAM是一个稀缺资源。
(6)方法参数分解,使用全局变量代替参数,并且传递一个对象而不是一个参数列表。
(7)最大化局部变量使用效率,尽量复用局部变量;
尽量减少类和对象的数量以及大小,可以有助于解决bug,增强程序可读性。
(8)禁止存储临时入口对象的引用。
在javacard标准中指定了一些JCRE的entry point objects(EPO)入口对象。在这些EPOS中,这些临时的EPO是禁止存储在类变量、全局变量或则数组阵列中。JCRE检测和禁止将这些对象的引用存储在程序中,作为应用防火墙的一部分。如果这么做的话系统会抛出安全异常。
一些主要的临时EPO:APDU, EnvelopeHandler, ProactiveHandler, EnvelopeResponseHandler, ProactiveResponseHandler 。
红色标注的代码有问题,因为它使用全局变量来存储EPO。
public class exampleD8 extends Applet implements ToolkitInterface { byte[] apduBuffer; byte[] envHandler; void process(APDU apdu){ byte[] buffer; apduBuffer = apdu.getBuffer(); buffer = apdu.getBuffer(); } void processToolkit(byte event){ byte[] handler; envHandler = EnvelopeHandler.getTheHandler(); handler = EnvelopeHandler.getTheHandler(); }
(9)方法的代码不要太复杂。
尽量保持方法的代码简单,最大不要超过300个字节。较复杂的方法最好拆分成几个较小的方法。方法的代码少可以提高可读性,并且更容易调试bug。
(10)确保每个类方法的数量不要超过256个。
每个类的方法数量限制在256个之内,更多的方法意味着更大的常量池。