DES 加密小程序 returns 接收任何命令时出错

DES Crypto applet returns errror on reception of any command

在下面您会看到一个简单的 Java 卡片小程序,它被编写为使用不同的 DES3DES 算法加密和解密数据。

这些是支持的命令:

  1. 00 C0 00 00 | KeyLength | KeyValue : 设置DES/3DES键。 (8 byte for DES, 16/24 bytes for 2Key/3Key 3DES algorithm)

  2. 00 C1 XX YY | DataLength | DataValue : 对于 DES Encryption/Decryption

  3. 00 C2 XX YY | DataLength | DataValue : 对于 2Key 3DES Encryption/Decryption

  4. 00 C3 XX YY | DataLength | DataValue : 对于 3Key 3DES Encryption/Decryption

XX = 0x00 : DES_CBC_ISO9797_M1

XX = 0x01 : DES_CBC_ISO9797_M2

XX = 0x02 : DES_CBC_NOPAD

XX = 0x03 : DES_CBC_PKCS5

XX = 0x04 : DES_ECB_ISO9797_M1

XX = 0x05 : DES_ECB_ISO9797_M2

XX = 0x06 : DES_ECB_NOPAD

XX = 0x07 : DES_ECB_PKCS5

YY = 0x00 : Encrypt

YY = 0x01 : Decrypt

程序:

package cryptoPack;

import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.DESKey;
import javacard.security.KeyBuilder;
import javacardx.crypto.Cipher;

public class CryptoDES extends Applet {

    // Array for the encryption/decryption key
    private byte[] TheDES_Key = { (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
            (byte) 0x00 };

    // Defining required Keys
    DESKey MyDES1Key = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES,
            KeyBuilder.LENGTH_DES, false);
    DESKey MyDES2Key = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES,
            KeyBuilder.LENGTH_DES3_2KEY, false);
    DESKey MyDES3Key = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES,
            KeyBuilder.LENGTH_DES3_3KEY, false);

    // Defining required cipher
    Cipher MyCipher;

    // Defining switch case variables for supported instructions
    final byte SetKey = (byte) 0xC0;
    final byte OneKeyDES = (byte) 0xC1;
    final byte TwoKeyDES = (byte) 0xC2;
    final byte ThreeKeyDES = (byte) 0xC3;

    // Defining switch case variables for cipher algorithms
    final byte DES_CBC_ISO9797_M1 = (byte) 0x00;
    final byte DES_CBC_ISO9797_M2 = (byte) 0x01;
    final byte DES_CBC_NOPAD = (byte) 0x02;
    final byte DES_CBC_PKCS5 = (byte) 0x03;
    final byte DES_ECB_ISO9797_M1 = (byte) 0x04;
    final byte DES_ECB_ISO9797_M2 = (byte) 0x05;
    final byte DES_ECB_NOPAD = (byte) 0x06;
    final byte DES_ECB_PKCS5 = (byte) 0x07;

    // Defining Proprietary Status Words
    final short KeyInNotSetGood = 0x6440;

    // A flag to be sure that the configured key has the same length that the
    // algorithm needs.
    byte ConfiguredKeyLength = 0;

    private CryptoDES() {

    }

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

    public void process(APDU apdu) throws ISOException {

        // Assigning 0 to "ConfiguredKeyLength" to force the user to use ...
        // ... "SetKey" command, after applet selection.
        if (selectingApplet()) {
            ConfiguredKeyLength = 0;
            return;
        }

        byte[] buffer = apdu.getBuffer();

        // Checking the CLA field in the APDU command.
        if (buffer[ISO7816.OFFSET_CLA] != 0) {
            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
        }

        // Checking the P1 and P2 fields in the APDU command.
        if (buffer[ISO7816.OFFSET_P1] > 7 || buffer[ISO7816.OFFSET_P2] > 1) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        // Analyzing the command.
        try {

            switch (buffer[ISO7816.OFFSET_INS]) {

            case SetKey:
                SetCryptoKeyAndInitCipher(apdu);
                break;

            case OneKeyDES:
                OneKeyDESCrypto(apdu);
                DoEncryptDecrypt(apdu);
                break;

            case TwoKeyDES:
                TwoKeyDESCrypto(apdu);
                DoEncryptDecrypt(apdu);
                break;

            case (byte) ThreeKeyDES:
                ThreeKeyDESCrypto(apdu);
                DoEncryptDecrypt(apdu);
                break;

            default:
                ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);

            }

        } catch (Exception e) {
            if (e instanceof CryptoException) {
                ISOException.throwIt(((CryptoException) e).getReason());
            }
            ISOException.throwIt(ISO7816.SW_UNKNOWN);

        }

    }

    public void SetCryptoKeyAndInitCipher(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        // Key must has a length of 8, 16 or 24 bytes
        if (buffer[ISO7816.OFFSET_LC] == 8 || buffer[ISO7816.OFFSET_LC] == 16
                || buffer[ISO7816.OFFSET_LC] == 24) {
            Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, TheDES_Key,
                    (short) 0, ISO7816.OFFSET_LC);

            ConfiguredKeyLength = buffer[ISO7816.OFFSET_LC];

        } else {
            ISOException.throwIt(ISO7816.SW_DATA_INVALID);
        }

        switch (buffer[ISO7816.OFFSET_P1]) {
        case DES_CBC_ISO9797_M1:
            MyCipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M1, false);
            break;
        case DES_CBC_ISO9797_M2:
            MyCipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M2, false);
            break;
        case DES_CBC_NOPAD:
            MyCipher.getInstance(Cipher.ALG_DES_CBC_NOPAD, false);
            break;
        case DES_CBC_PKCS5:
            MyCipher.getInstance(Cipher.ALG_DES_CBC_PKCS5, false);
            break;
        case DES_ECB_ISO9797_M1:
            MyCipher.getInstance(Cipher.ALG_DES_ECB_ISO9797_M1, false);
            break;
        case DES_ECB_ISO9797_M2:
            MyCipher.getInstance(Cipher.ALG_DES_ECB_ISO9797_M2, false);
            break;
        case DES_ECB_NOPAD:
            MyCipher.getInstance(Cipher.ALG_DES_ECB_NOPAD, false);
            break;
        case DES_ECB_PKCS5:
            MyCipher.getInstance(Cipher.ALG_DES_ECB_PKCS5, false);
            break;

        }

    }

    public void OneKeyDESCrypto(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        // Check to see if the configured key is the required key for this ...
        // ... algorithm or not
        if (ConfiguredKeyLength != 8) {
            ISOException.throwIt(KeyInNotSetGood);
        }
        MyDES1Key.setKey(TheDES_Key, (short) 0);

        if (buffer[ISO7816.OFFSET_P2] == 1) {
            MyCipher.init(MyDES1Key, Cipher.MODE_ENCRYPT);
        } else {
            MyCipher.init(MyDES1Key, Cipher.MODE_DECRYPT);

        }

    }

    public void TwoKeyDESCrypto(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        // Check to see if the configured key is the required key for this ...
        // ... algorithm or not

        if (ConfiguredKeyLength != 16) {
            ISOException.throwIt(KeyInNotSetGood);
        }
        MyDES2Key.setKey(TheDES_Key, (short) 0);

        if (buffer[ISO7816.OFFSET_P2] == 1) {
            MyCipher.init(MyDES2Key, Cipher.MODE_ENCRYPT);
        } else {
            MyCipher.init(MyDES2Key, Cipher.MODE_DECRYPT);

        }

    }

    public void ThreeKeyDESCrypto(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        // Check to see if the configured key is the required key for this ...
        // ... algorithm or not
        if (ConfiguredKeyLength != 24) {
            ISOException.throwIt(KeyInNotSetGood);
        }

        MyDES3Key.setKey(TheDES_Key, (short) 0);

        if (buffer[ISO7816.OFFSET_P2] == 1) {
            MyCipher.init(MyDES3Key, Cipher.MODE_ENCRYPT);
        } else {
            MyCipher.init(MyDES3Key, Cipher.MODE_DECRYPT);

        }

    }

    public void DoEncryptDecrypt(APDU apdu) {
        byte[] buffer = apdu.getBuffer();

        byte[] CipheredData = JCSystem.makeTransientByteArray((short) 32,
                JCSystem.CLEAR_ON_DESELECT);

        short datalen = apdu.setIncomingAndReceive();
        if ((datalen % 8) != 0) {
            ISOException.throwIt(ISO7816.SW_DATA_INVALID);
        }

        MyCipher.doFinal(buffer, (short) 0, datalen, CipheredData, (short) 0);
        Util.arrayCopyNonAtomic(CipheredData, (short) 0, buffer, (short) 0,
                datalen);
        apdu.setOutgoingAndSend((short) 0, datalen);
    }

}

运行-时间输出:

OSC: opensc-tool.exe -s 00a404000b0102030405060708090000 -s 00c10000081122334455667788 -s 00c20000081011121314151617

Using reader with a card: ACS CCID USB Reader 0

//Selecting the applet
Sending: 00 A4 04 00 0B 01 02 03 04 05 06 07 08 09 00 00
Received (SW1=0x90, SW2=0x00)

//Assign 1122334455667788 as the crypto key
Sending: 00 C1 00 00 08 11 22 33 44 55 66 77 88
Received (SW1=0x6F, SW2=0x00)

//Request to "encrypt" "1011121314151617" with "DES_CBC_ISO9797_M1"
Sending: 00 C2 00 00 08 10 11 12 13 14 15 16 17
Received (SW1=0x6F, SW2=0x00)

问题:

  1. 专业的程序员如何编写上述程序才能使其更高效、更安全? (任何改进:变量的声明和定义范围,变量类型等)
  2. 为什么小程序 returns 在接收其支持的任何 APDU 命令时出错?

更新:

2.1: 我的 IDE (Eclipse), warnThe static method getInstance(byte,boolean) from the type Cipher should be accessed in a static 呀什么意思?我为什么要?

2.2:在上面的程序中,我假设输入数据的长度小于 32 字节,并且也是 8 字节的倍数。如何使长度可变?一种解决方案是使用 new 关键字,但我认为这是最糟糕的解决方案。有什么建议吗?

Q1

Whosebug 并不意味着要成为同行代码审查的网站。尽管如此,还是有一些明显的观察结果:

  • 遵循 Java 代码约定:myCipher 代替 MyCipherKEY_IN_NOT_SET_GOOD 代替 KeyInNotSetGood 等(参见 https://google-styleguide.googlecode.com/svn/trunk/javaguide.html) .这些规则可以大大提高你代码的可读性。
  • 切勿以非静态方式使用静态方法(使用 Cipher.getInstance(...) 而不是 myCipher.getInstance(...))。 (static 标记 - https://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html)
  • 用 try-catch 块包围 process 方法的全部内容,并处理您捕获的所有异常。然后根据异常的类型和原因设置状态字。否则你只会得到 6F00,几乎没有任何信息。
  • ConfiguredKeyLength 必须存储在 RAM 中。你的方式很快就会毁坏卡(每个 SELECT 重写 EEPROM 单元)。

Q2

首先,有一个很常见的错误:

Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, TheDES_Key,
                    (short) 0, ISO7816.OFFSET_LC);

而不是

 Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, TheDES_Key,
                    (short) 0, buffer[ISO7816.OFFSET_LC]);

(顺便说一下,一周前我在 Whosebug 告诉你的人是你吗?)

第二个问题:你不打电话

apdu.setIncomingAndReceive();

在接触 APDU 缓冲区的数据部分之前。这会引起很多麻烦。

第三个问题:你创建Cipher实例的方式不对。写:

MyCipher = Cipher.getInstance(Cipher.ALG_DES_CBC_NOPAD, false);

而不是

MyCipher.getInstance(Cipher.ALG_DES_CBC_NOPAD, false);

这就是为什么你得到 NullPointerException - MyCipher 始终保持 null.