Код: Выделить всё
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
/**
* @author Daniel Andrade
*/
public class TripleDES {
/**
* Encrypt using 3DES: DESede/CBC/NoPadding.
*
* @param myIV Initialization vector
* @param myKey Secret key (24 Bytes)
* @param myMsg Message to encrypt
* @return The encrypted message, or null*/
public static byte[] encrypt(byte[] myIV, byte[] myKey, byte[] myMsg) {
byte[] cipherText = null;
try {
IvParameterSpec iv = new IvParameterSpec(myIV);
DESedeKeySpec desKey = new DESedeKeySpec(myKey);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey key = keyFactory.generateSecret(desKey);
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
cipherText = cipher.doFinal(myMsg);
} catch (Exception e) {
//TODO: multicatch only Java 1.7+
e.printStackTrace();
return null;
}
return cipherText;
}
// ciphertext inside msg at offset and with length length
public static byte[] decrypt(byte[] myKey, byte[] myMsg, int offset, int length) {
return decrypt(new byte[8], myKey, myMsg, offset, length);
}
/**
* Decrypt using 3DES: DESede/CBC/NoPadding.
*
* @param myIV The initialization vector
* @param myKey Secret key (24 Bytes)
* @param myMsg Message to decrypt
* @return
*/
public static byte[] decrypt(byte[] myIV, byte[] myKey, byte[] myMsg) {
return decrypt(myIV, myKey, myMsg, 0, myMsg.length);
}
public static byte[] decrypt(byte[] myIV, byte[] myKey, byte[] myMsg, int offset, int length) {
byte[] plainText = null;
try {
IvParameterSpec iv = new IvParameterSpec(myIV);
DESedeKeySpec desKey = new DESedeKeySpec(myKey);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey key = keyFactory.generateSecret(desKey);
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, iv);
//plainText = cipher.doFinal(myMsg);
plainText = cipher.doFinal(myMsg, offset, length);
} catch (Exception e) {
//TODO: multicatch only Java 1.7+
e.printStackTrace();
}
return plainText;
}
}
< /code>
Ключ не случайный: < /p>
protected void fillRandom(byte[] randA) {
if (randA.length == 8) {
byte[] fixed8 = new byte[] {
0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08
};
System.arraycopy(fixed8, 0, randA, 0, 8);
} else if (randA.length == 16) {
byte[] fixed16 = new byte[] {
0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C,
0x0D, 0x0E, 0x0F, 0x10
};
System.arraycopy(fixed16, 0, randA, 0, 16);
} else {
throw new IllegalArgumentException("Unsupported RndA length: " + randA.length);
}
}
< /code>
и вот тесты: < /p>
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.util.Arrays;
public class DESFireChangeKeyTest {
public static void main(String[] args) throws Exception {
byte[] newKey = new byte[16]; // 16 bytes of 00
byte[] sessionKey = new byte[] {
0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08
}; // ← Same session key used in real app
System.out.println("Plaintext (newKey + version):");
printHex(newKey);
byte[] plainWithVersion = new byte[17];
System.arraycopy(newKey, 0, plainWithVersion, 0, 16);
plainWithVersion[16] = 0x00; // Key version = 0x00
byte[] crc = crc16(plainWithVersion);
System.out.println("CRC16:");
printHex(crc);
byte[] fullPlaintext = new byte[24];
System.arraycopy(plainWithVersion, 0, fullPlaintext, 0, 17);
System.arraycopy(crc, 0, fullPlaintext, 17, 2);
System.out.println("Full Plaintext + Padding (before encryption):");
printHex(fullPlaintext);
byte[] sessionKey24 = new byte[24];
System.arraycopy(sessionKey, 0, sessionKey24, 0, 8);
System.arraycopy(sessionKey, 0, sessionKey24, 8, 8);
System.arraycopy(sessionKey, 0, sessionKey24, 16, 8);
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey key = keyFactory.generateSecret(new DESedeKeySpec(sessionKey24));
IvParameterSpec iv = new IvParameterSpec(new byte[8]);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext = cipher.doFinal(fullPlaintext);
System.out.println("Ciphertext:");
printHex(ciphertext);
byte[] apdu = new byte[5 + 1 + ciphertext.length + 1];
apdu[0] = (byte) 0x90;
apdu[1] = (byte) 0xC4;
apdu[2] = 0x00;
apdu[3] = 0x00;
apdu[4] = (byte) (1 + ciphertext.length);
apdu[5] = (byte) 0x80; // Key number 0x80 (AES master key)
System.arraycopy(ciphertext, 0, apdu, 6, ciphertext.length);
apdu[apdu.length - 1] = 0x00;
System.out.println("Final ChangeKey APDU:");
printHex(apdu);
}
private static void printHex(byte[] data) {
for (byte b : data) {
System.out.printf("%02X ", b);
}
System.out.println();
}
private static byte[] crc16(byte[] data) {
int crc = 0x6363;
for (byte b : data) {
crc ^= (b & 0xFF);
for (int i = 0; i < 8; i++) {
if ((crc & 0x0001) != 0) {
crc = (crc >> 1) ^ 0x8408;
} else {
crc >>= 1;
}
}
}
return new byte[] {
(byte)(crc & 0xFF),
(byte)((crc >> 8) & 0xFF)
};
}
}
< /code>
Plaintext (newKey + version):
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
CRC16:
75 45
Full Plaintext + Padding (before encryption):
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 75 45 00 00 00 00 00
Ciphertext:
B0 73 DC 3F B2 09 53 6D 84 1B E1 EB DE 3B 2A 1B E0 3E F7 4B 29 98 0E 7F
Final ChangeKey APDU:
90 C4 00 00 19 80 B0 73 DC 3F B2 09 53 6D 84 1B E1 EB DE 3B 2A 1B E0 3E F7 4B 29 98 0E 7F 00
< /code>
Compare that to the app's ciphertext:
CA D9 4D AE BF 0E 87 B7 CA D9 4D AE BF 0E 87 B7 67 36 C6 8B 69 93 77 E8
< /code>
As you can see, the ciphertext from the standalone Java program does not match what I obtain from the Android app. Specifically, the second encrypted block in the standalone version is all zeros, clearly indicating that CBC chaining isn't working correctly, whereas the Android app correctly produces a ciphertext with no zero-block issues.
My Question:
Why is the ciphertext generated by the standalone Java code different from the ciphertext generated by the original Android app code, even though I'm using the same parameters (key, IV, plaintext)?
What is the correct way to exactly match the Android app's encryption behavior in standalone Java?
Thank you!
Подробнее здесь: https://stackoverflow.com/questions/795 ... pp-using-i
Мобильная версия