Java Card DES生成器小程序输出不同于在线工具输出 [英] Java Card DES generator applet output is different from online-tools output
问题描述
以下小程序用于对APDU数据字段执行DES加密/解密:
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.CryptoException;
import javacard.security.DESKey;
import javacard.security.KeyBuilder;
import javacardx.crypto.Cipher;
public class CryptoDES extends Applet {
//加密/解密密钥的数组
private byte [] TheDES_Key = {(byte)0x00,(byte) 0x00,(字节)0x00,
(字节)0x00,(字节)0x00,(字节)0x00,(字节)0x00,(字节)0x00, (byte)0x00,(byte)0x00,(byte)0x00,
(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,
)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,
(byte)0x00};
//定义所需的密钥
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);
byte ConfiguredKeyLength;
//定义所需的密码
Cipher MyCipher;
//为支持的指令定义开关情况变量= APDU命令中的INS
最后字节SetKey =(字节)0xC0;
final byte OneKeyDES =(byte)0xC1;
final byte TwoKeyDES =(byte)0xC2;
final byte ThreeKeyDES =(byte)0xC3;
//定义加密算法的交换机变量= APDU命令中的P1
最后字节DES_CBC_ISO9797_M1 =(字节)0x00;
final byte DES_CBC_ISO9797_M2 =(byte)0x01;
最后字节DES_CBC_NOPAD =(字节)0x02;
最后字节DES_CBC_PKCS5 =(字节)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;
//定义专有状态字
final short KeyInNotSetGood = 0x6440;
//一个标志,以确保配置的密钥与
//算法需要的长度相同。
private CryptoDES(){
}
public static void install(byte bArray [],short bOffset,byte bLength)
throws ISOException {
new CryptoDES()。register();
}
public void process(APDU apdu)throws ISOException {
//分配0到ConfiguredKeyLength以强制用户使用...
// ...SetKey命令,在小程序选择后。
if(selectionApplet()){
ConfiguredKeyLength = 0;
return;
}
byte [] buffer = apdu.getBuffer();
//检查APDU命令中的CLA字段。
if(buffer [ISO7816.OFFSET_CLA]!= 0){
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
//检查APDU命令中的P1和P2字段。
if(buffer [ISO7816.OFFSET_P1]> 7 || buffer [ISO7816.OFFSET_P2]> 1){
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
//分析命令。
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;
默认值:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
} catch(CryptoException e){
ISOException.throwIt((CryptoException)e).getReason
}
}
public void SetCryptoKeyAndInitCipher(APDU apdu)
throws ISOException {
byte [] buffer = apdu.getBuffer ;
//密钥长度必须为8,16或24字节
if(buffer [ISO7816.OFFSET_LC] == 8 || buffer [ISO7816.OFFSET_LC] == 16
||缓冲区[ISO7816.OFFSET_LC] == 24){
Util.arrayCopyNonAtomic(buffer,ISO7816.OFFSET_CDATA,TheDES_Key,
(short)0,buffer [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 = Cipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M1,false);
break;
case DES_CBC_ISO9797_M2:
MyCipher = Cipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M2,false);
break;
case DES_CBC_NOPAD:
MyCipher = Cipher.getInstance(Cipher.ALG_DES_CBC_NOPAD,false);
break;
case DES_CBC_PKCS5:
MyCipher = Cipher.getInstance(Cipher.ALG_DES_CBC_PKCS5,false);
break;
case DES_ECB_ISO9797_M1:
MyCipher = Cipher.getInstance(Cipher.ALG_DES_ECB_ISO9797_M1,false);
break;
case DES_ECB_ISO9797_M2:
MyCipher = Cipher.getInstance(Cipher.ALG_DES_ECB_ISO9797_M2,false);
break;
case DES_ECB_NOPAD:
MyCipher = Cipher.getInstance(Cipher.ALG_DES_ECB_NOPAD,false);
break;
case DES_ECB_PKCS5:
MyCipher = Cipher.getInstance(Cipher.ALG_DES_ECB_PKCS5,false);
break;
}
}
public void OneKeyDESCrypto(APDU apdu)
throws ISOException {
byte [] buffer = apdu .getBuffer();
//检查配置的密钥是否为此所需的密钥...
// ...算法或不是
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)
抛出ISOException {
byte [] buffer = apdu .getBuffer();
//检查配置的密钥是否为此所需的密钥...
// ...算法或不是
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();
//检查配置的密钥是否为此所需的密钥...
// ...算法或不是
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);
}
}
问题是它的输出不等于在线工具。例如,我使用此网站计算的加密值0x3030303030303030
,其中输入:
,其中键= 1122334455667788
https://i.stack.imgur.com/5JOjf.jpgalt =enter image description here>
现在我用我的applet重复上面的加密:
OpenSC :: opensc-tool.exe -s 00a404000b0102030405060708090000 -s 00c0000008112233
$由于我不知道
4455667788 -s 00c10401083030303030303030
使用带卡的读卡器:ACS CCID USB读卡器0
发送:00 A4 04 00 0B 01 02 03 04 05 06 07 08 09 00 00接收到的
(SW1 = 0x90,SW2 = 0x00)
发送:00 C0 00 00 08 11 22 33 44 55 66 77 88
接收(SW1 = 0x90,SW2 = 0x00)
发送:00 C1 04 01 08 30 30 30 30 30 30 30 30
Received(SW1 = 0x90,SW2 = 0x00):
8E 43 CF B8 91 02 01 38 .C ..... 8
OpenSC :: opensc-tool.exe -s 00a404000b0102030405060708090000 -s 00c0000008112233
4455667788 -s 00c10501083030303030303030
使用卡片阅读器:ACS CCID USB读卡器0
发送:00 A4 04 00 0B 01 02 03 04 05 06 07 08 09 00 00
接收(SW1 = 0x90,SW2 = 0x00)
发送:00 C0 00 00 08 11 22 33 44 55 66 77 88接收到
(SW1 = 0x90,SW2 = 0x00)
发送:00 C1 05 01 08 30 30 30 30 30 30 30 30
收到(SW1 = 0x90,SW2 = 0x00):
A6 DE 1C D9 1B A9 EE D0 ...... ..
OpenSC :: opensc-tool.exe -s 00a404000b0102030405060708090000 -s 00c0000008112233
4455667788 -s 00c10601083030303030303030
使用读卡器:ACS CCID USB读卡器0
发送:00 A4 04 00 0B 01 02 03 04 05 06 07 08 09 00 00
收到(SW1 = 0x90,SW2 = 0x00)
发送:00 C0 00 00 08 11 22 33 44 55 66 77 88
接收(SW1 = 0x90,SW2 = 0x00)
发送:00 C1 06 01 08 30 30 30 30 30 30 30 30接收(SW1 = 0x90,SW2 = 0x00)
0B FC BF EE 82 F4 8B 19 ........
OpenSC :: opensc-tool.exe -s 00a404000b0102030405060708090000 -s 00c0000008112233
4455667788 -s 00c10701083030303030303030
使用读卡器卡:ACS CCID USB读卡器0
发送:00 A4 04 00 0B 01 02 03 04 05 06 07 08 09 00 00接收到的
(SW1 = 0x90,SW2 = 0x00 )
发送:00 C0 00 00 08 11 22 33 44 55 66 77 88
接收(SW1 = 0x90,SW2 = 0x00)
发送:00 C1 07 01 08 30 30 30 30 30 30 30 30
已接收(SW1 = 0x90,SW2 = 0x00):
AA 6E 4D 79 E5 0C B1 51 .nMy ... Q
DES_ECB_PKCS5
,DES_ECB_NOPAD $ c>中的哪一个,因此p $ p>
$ b $ c>,DES_ECB_ISO9797_M2
或DES_ECB_ISO9797_M1
正在使用的在线工具,我做了所有的加密,卡。但输出与在线工具不同。
- 发生了什么问题?
- 如何找出上述哪一种密码在线工具使用?
解决方案
在线工具使用的是DES_ECB_PKCS5,DES_ECB_NOPAD,
DES_ECB_ISO9797_M2或DES_ECB_ISO9797_M1中的哪一个
这是问题。 blockmode,padding等东西都很重要(还有这么多超过4种组合)。并且在在线工具的情况下,字符集东西(一般的输入解释)也是一个问题。
不知道详细的在线工具,你不能得到任何正确的。
不要使用它,并搜索标准化测试载体。
除此之外,为什么你使用DES!停止。
不再安全了。The below applet is written to do a DES encryption/Decryption on the APDU data field :
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.CryptoException; 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); byte ConfiguredKeyLength; // Defining required cipher Cipher MyCipher; // Defining switch case variables for supported instructions = INS in APDU command 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 = P1 in APDU command 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. 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 (CryptoException e) { ISOException.throwIt(((CryptoException) e).getReason()); } } 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, buffer[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 = Cipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M1, false); break; case DES_CBC_ISO9797_M2: MyCipher = Cipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M2, false); break; case DES_CBC_NOPAD: MyCipher = Cipher.getInstance(Cipher.ALG_DES_CBC_NOPAD, false); break; case DES_CBC_PKCS5: MyCipher = Cipher.getInstance(Cipher.ALG_DES_CBC_PKCS5, false); break; case DES_ECB_ISO9797_M1: MyCipher = Cipher.getInstance(Cipher.ALG_DES_ECB_ISO9797_M1, false); break; case DES_ECB_ISO9797_M2: MyCipher = Cipher.getInstance(Cipher.ALG_DES_ECB_ISO9797_M2, false); break; case DES_ECB_NOPAD: MyCipher = Cipher.getInstance(Cipher.ALG_DES_ECB_NOPAD, false); break; case DES_ECB_PKCS5: MyCipher = Cipher.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); } }
The problem is that its output is not equal with online tools. for example I use this website to calculate encrypted value of
0x3030303030303030
with a key =1122334455667788
, and this is output :Now I repeat the above encryption with my applet:
OpenSC:: opensc-tool.exe -s 00a404000b0102030405060708090000 -s 00c0000008112233 4455667788 -s 00c10401083030303030303030 Using reader with a card: ACS CCID USB Reader 0 Sending: 00 A4 04 00 0B 01 02 03 04 05 06 07 08 09 00 00 Received (SW1=0x90, SW2=0x00) Sending: 00 C0 00 00 08 11 22 33 44 55 66 77 88 Received (SW1=0x90, SW2=0x00) Sending: 00 C1 04 01 08 30 30 30 30 30 30 30 30 Received (SW1=0x90, SW2=0x00): 8E 43 CF B8 91 02 01 38 .C.....8 OpenSC:: opensc-tool.exe -s 00a404000b0102030405060708090000 -s 00c0000008112233 4455667788 -s 00c10501083030303030303030 Using reader with a card: ACS CCID USB Reader 0 Sending: 00 A4 04 00 0B 01 02 03 04 05 06 07 08 09 00 00 Received (SW1=0x90, SW2=0x00) Sending: 00 C0 00 00 08 11 22 33 44 55 66 77 88 Received (SW1=0x90, SW2=0x00) Sending: 00 C1 05 01 08 30 30 30 30 30 30 30 30 Received (SW1=0x90, SW2=0x00): A6 DE 1C D9 1B A9 EE D0 ........ OpenSC:: opensc-tool.exe -s 00a404000b0102030405060708090000 -s 00c0000008112233 4455667788 -s 00c10601083030303030303030 Using reader with a card: ACS CCID USB Reader 0 Sending: 00 A4 04 00 0B 01 02 03 04 05 06 07 08 09 00 00 Received (SW1=0x90, SW2=0x00) Sending: 00 C0 00 00 08 11 22 33 44 55 66 77 88 Received (SW1=0x90, SW2=0x00) Sending: 00 C1 06 01 08 30 30 30 30 30 30 30 30 Received (SW1=0x90, SW2=0x00): 0B FC BF EE 82 F4 8B 19 ........ OpenSC:: opensc-tool.exe -s 00a404000b0102030405060708090000 -s 00c0000008112233 4455667788 -s 00c10701083030303030303030 Using reader with a card: ACS CCID USB Reader 0 Sending: 00 A4 04 00 0B 01 02 03 04 05 06 07 08 09 00 00 Received (SW1=0x90, SW2=0x00) Sending: 00 C0 00 00 08 11 22 33 44 55 66 77 88 Received (SW1=0x90, SW2=0x00) Sending: 00 C1 07 01 08 30 30 30 30 30 30 30 30 Received (SW1=0x90, SW2=0x00): AA 6E 4D 79 E5 0C B1 51 .nMy...Q
As I didn't know which one of
DES_ECB_PKCS5
,DES_ECB_NOPAD
,DES_ECB_ISO9797_M2
orDES_ECB_ISO9797_M1
being used by the online tool, I did the encryption with all of them in my card. but the output is different from the online tool.
- What's wrong?
- How I can find out which one of the above ciphers, does the online tool use?
解决方案As I didn't know which one of DES_ECB_PKCS5, DES_ECB_NOPAD, DES_ECB_ISO9797_M2 or DES_ECB_ISO9797_M1 being used by the online tool
That´s the problem. Blockmode, padding and other things are very important (and there are so much more than 4 combinations). And in case of online tools, charset stuff (input interpretation in general) is also a problem.
Without knowing the online tool in detail, you won´t get anything right with it.
Just don´t use it and search for standardized test vectors.Other than that, why you´re using DES!? Stop that.
It´s not secure anymore.这篇关于Java Card DES生成器小程序输出不同于在线工具输出的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!