Java 卡小程序中的 RSA 加密 returns "ILLEGAL_USE"

RSA Cryptography returns "ILLEGAL_USE" in Java Card applet

我编写了以下小程序来在我的 NXP JCOP 卡中执行 RSA 加密操作:

package testPack;

import javacard.framework.*;
import javacard.security.CryptoException;
import javacard.security.KeyBuilder;
import javacard.security.KeyPair;
import javacard.security.RSAPrivateKey;
import javacard.security.RSAPublicKey;
import javacardx.crypto.Cipher;

public class Test extends Applet {

    RSAPrivateKey myRSAPriKey;
    RSAPublicKey myRSAPubKey;
    Cipher myCipher;
    KeyPair myKeyPair;
    byte[] input;
    byte[] result;

    private static final byte INS_GEN_KEYPAIR = (byte) 0x10;
    private static final byte INS_INIT_CIPHER_ENC = (byte) 0x20;
    private static final byte INS_ENC = 0x21;
    private static final byte INS_INIT_CIPHER_DEC = (byte) 0x30;
    private static final byte INS_DEC = (byte) 0x31;

    private static final byte P1_CHAIN_APDU = (byte) 0x00;
    private static final byte P1_LAST_APDU = (byte) 0x01;

    public static void install(byte[] bArray, short bOffset, byte bLength) {
        new Test();
    }

    protected Test() {
        myRSAPriKey = (RSAPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PRIVATE, KeyBuilder.LENGTH_RSA_2048, false);
        myRSAPubKey = (RSAPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC, KeyBuilder.LENGTH_RSA_2048, false);
        myKeyPair = new KeyPair(myRSAPubKey, myRSAPriKey);
        myCipher = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);
        input = JCSystem.makeTransientByteArray((short) 256, JCSystem.CLEAR_ON_RESET);
        result = JCSystem.makeTransientByteArray((short) 256, JCSystem.CLEAR_ON_RESET);
        register();
    }

    public void process(APDU apdu) {
        if (selectingApplet()) {
            return;
        }

        byte[] buff = apdu.getBuffer();
        byte ins = buff[ISO7816.OFFSET_INS];
        byte p1 = buff[ISO7816.OFFSET_P1];
        short lc = (short) (buff[ISO7816.OFFSET_LC] & 0x00FF);
        short dataOffset = ISO7816.OFFSET_CDATA;

        switch (ins) {
            case INS_GEN_KEYPAIR:
                myKeyPair.genKeyPair();
                break;

            case INS_INIT_CIPHER_ENC:
                myCipher.init(myRSAPubKey, Cipher.MODE_ENCRYPT);
                break;
            case INS_ENC:
                apdu.setIncomingAndReceive();
                if (p1 == P1_CHAIN_APDU) {
                    Util.arrayCopyNonAtomic(buff, dataOffset, input, (short) 0x00, lc);
                } else if (p1 == P1_LAST_APDU) {
                    Util.arrayCopyNonAtomic(buff, dataOffset, input, (short) 128, lc);
                    try {
                        myCipher.doFinal(input, (short) 0x00, (short) 256, result, (short) 0x00);
                    } catch (CryptoException e) {
                        short reason = e.getReason();
                        ISOException.throwIt((short) ((short) 0x6B00 | reason));
                    }
                    apdu.setOutgoing();
                    apdu.setOutgoingLength((short) 256);
                    apdu.sendBytesLong(result, (short) 0x00, (short) 256);
                }
                break;
            case INS_INIT_CIPHER_DEC:
                myCipher.init(myRSAPriKey, Cipher.MODE_DECRYPT);
                break;
            case INS_DEC:
                apdu.setIncomingAndReceive();
                if (p1 == P1_CHAIN_APDU) {
                    Util.arrayCopyNonAtomic(buff, dataOffset, input, (short) 0x00, lc);
                } else if (p1 == P1_LAST_APDU) {
                    Util.arrayCopyNonAtomic(buff, dataOffset, input, (short) 128, lc);
                    try {
                        myCipher.doFinal(input, (short) 0x00, (short) 256, result, (short) 0x00);
                    } catch (CryptoException e) {
                        short reason = e.getReason();
                        ISOException.throwIt((short) ((short) 0x6B00 | reason));
                    }
                    apdu.setOutgoing();
                    apdu.setOutgoingLength((short) 256);
                    apdu.sendBytesLong(result, (short) 0x00, (short) 256);
                }
                break;

        }

    }
}

问题是我在 doFinal() 方法上收到 0x0005 CryptoException 原因代码:

Select Applet begin...
Select Applet successful.
Send: 00 10 00 00 00
Recv: 90 00

Send: 00 20 00 00 00
Recv: 90 00

Send: 00 21 00 00 80 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00
Recv: 90 00

Send: 00 21 01 00 80 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00
Recv: 6B 05

问题:

  1. 如您所知,原因码0x0005表示ILLEGAL_USE。但是为什么?
  2. 如何在密码对象上使用 update() 方法从我的小程序中删除瞬态字节数组?

答案 1:Cipher.ALG_RSA_PKCS1 文档中所述:

This algorithm is only suitable for messages of limited length. The total number of input bytes processed during encryption may not be more than k-11, where k is the RSA key's modulus size in bytes.

您尝试加密的邮件不遵循此规则,因为您正在加密 256 字节的邮件。由于模数大小为 256 字节,因此您可以加密的最大消息为 245 字节 (k-11)。您必须考虑将添加到消息中的额外填充字节。

答案 2: 您不能同时删除输入和输出缓冲区,因为您需要一个来存储部分结果。

case INS_INIT_CIPHER_ENC:
                myCipher.init(myRSAPubKey, Cipher.MODE_ENCRYPT);
                cipher_result_len = (short) 0x00;
                break;
case INS_ENC:
                apdu.setIncomingAndReceive();
                if (p1 == P1_CHAIN_APDU) {
                    cipher_result_len += myCipher.update(buff, dataOffset, lc, result, cipher_result_len); 
                } else if (p1 == P1_LAST_APDU) {
                    
                    try {
                        cipher_result_len += myCipher.doFinal(buff, dataOffset, lc, result, cipher_result_len);
                    } catch (CryptoException e) {
                        short reason = e.getReason();
                        ISOException.throwIt((short) ((short) 0x6B00 | reason));
                    }
                    apdu.setOutgoing();
                    apdu.setOutgoingLength(cipher_result_len);
                    apdu.sendBytesLong(result, (short) 0x00, cipher_result_len);
                }
                break;

cipher_result_len 是短数据,必须存储在临时缓冲区中。