Rétroingénierie d'un applet de Javacard
Write-up du challenge Suspicious SMS de hackropole (FCSC édition 2021)
Le
Dans ce premier writeup que je fais sur les challenges hackropole, je vous propose de résoudre un challenge basé sur l'analyse d'un applet Java pour smartcard. À noter que je n'ai bien sûr pas lu de solution pour ce challenge afin de ne pas être spoilé. Par ailleurs je n'en ai pas trouvé sur Internet lors de mes recherches (20/12/2025).
Description du challenge
En analysant le trafic entre mon téléphone et le réseau, j’ai détecté un SMS de classe 0. Ce SMS contient des instructions pour installer un applet Java Card sur ma carte SIM. Je vous ai extrait le contenu de l’applet (
jc_dump.bin). Cela ressemble à du Java Card 3.0.1. Notre expert étant absent, nous ne pouvons pas le solliciter. Une fois cet applet installé, j’ai découvert que des APDUs chiffrés étaient échangés entre la carte SIM et le réseau opérateur. J’ai détecté cette réponse APDU, mais je n’ai pas pu capter le début des échanges. Son contenu semble chiffré.< C6 5F 56 69 08 F8 A0 4A 4D CE 35 C0 1A 4B B2 AB 29 D4 1C FC EA 3D FF 7E 97 E3 42 F6 4F 60 27 14 9C C7 83 4A 04 F9 D7 C2 DE 8F 35 0E 96 77 09 6F 81 EA D0 CD 09 FB BE 74 58 D7 FE 45 2D 9D A4 43 11 87 63 31 24 EF 65 3D 6E 55 DF 54 34 AC E0 A5 90 00Pouvez-vous retrouver le secret caché ?
Afin d'apporter un peu plus de contexte sur le challenge, je vous propose de présenter en détail ce qu'est une smartcard dans une première partie, puis d'aborder dans un second temps les SMS et enfin la résolution du challenge.
Commençons par expliquer ce qu'est une smartcard.
Les smartcards
Les smartcards (cartes à puce) sont des cartes intégrant un microcircuit électronique capable de stocker et de traiter des données de manière sécurisée. À l'inverse des cartes "classiques", qui se contentent de délivrer les données qu'elles stockent de manière passive en réponse à un stimulus, une smart card est capable d'effectuer des calculs et de traiter l'information de manière autonome, pouvant garantir l'authentification du lecteur avant de lui délivrer des informations.
On en retrouve un peu partout dans le quotidien, comme dans : - les cartes SIMs ; - les cartes bancaires ; - les cartes vitales ; - les nouvelles cartes d'identité française ; - certains badges d'accès
.png)
La norme ISO/IEC-7816
Les grandes caractéristiques des smartcards sont définies dans la norme ISO/IEC 7816. Cette norme est divisée en 4 parties :
- ISO/IEC 7816-1 : Impose des caractéristiques physiques
- ISO/IEC 7816-2 : Définis les dimensions et les emplacements des zones de contacts avec le lecteur. Elles sont au nombre de 8 :
- VCC (C1) : Alimentation électrique.
- RST (C2) : Signal de Reset.
- CLK (C3) : Horloge (pour synchroniser les calculs).
- I/O (C7) : Entrée/Sortie des données (le fil par lequel transitent les informations).
- GND (C5) : La masse.

- ISO/IEC 7816-3 : Définie l'interface électrique, notamment les tensions d'alimentations sous forme de classe (5V, 3V, 1.8V) et les protocoles de transmission.
- ISO/IEC 7816-4 : Définis la partie logicielle de la carte et son organisation (les différentes commandes et leurs réponses). L'ensemble des commandes est appelé APDU (Application Protocol Data Unit). Les commandes envoyées sont appelées C-APDU et celles reçues R-APDU.
⚠️ A noté que rien n'oblige un constructeur à suivre cette norme, mais c'est généralement le cas.
Composition matérielle
On peut considérer les smartcard comme des ordinateurs à part entière, car elles disposent : - d'un CPU ; - de mémoire vive (RAM) ; - de mémoire morte (ROM/Flash) ; - d'un système d'exploitation
Système d'exploitation
Il existe trois systèmes d'exploitation principaux pour les smartcard :
- Javacard ;
- .NET for smartcards, similaire au Javacard, mais en C# ;
- MultOS.
Java Card
Java Card est un système d'exploitation pour les smartcard qui fournit un environnement d'exécution pour du code Java, sous la forme de bytecode (code Java compilé), spécifiquement destiné aux applications pour carte à puce. L'organisation du système est décrite par l'image ci-dessous.

Un applet est une application portable de petite taille, écrite en JavaCard qui s'exécute à l'intérieur de la puce sous le contrôle du Java Card Runtime Environment (JRCE). Un applet est dit réactif : il reste en sommeil jusqu'à ce qu'il reçoive une commande (APDU) du monde extérieur. Chaque applet est isolé les uns des autres.
Les applets sont développés en Java, puis compilés en .class, puis transformés en fichier CAP (Converted APplet) afin de satisfaire les contraintes de mémoire avant d'être chargés dans la carte.

Une fois installé sur la plateforme et sélectionné, le bytecode de l'applet est exécuté par la machine virtuelle embarquée JCVM.
Les cartes SIMs
Cette vidéo de LiveOverflow permet de résumer ce qui a été dit au-dessus et propose une étude de cas sur les cartes SIMs.
SMS
SMS, pour Short Message Service est le service responsable de l'envoi et de la réception de messages textuels (appelé eux aussi SMS).
Le challenge mentionne la réception d'un SMS de classe 0. Il existe en effet plusieurs classes de SMS, décrites dans le tableau ci-dessous :
| Classe | Nom | Comportement |
|---|---|---|
| Classe 0 | Flash SMS | S'affiche immédiatement en plein écran. Non stocké par défaut (sauf action manuelle). |
| Classe 1 | Standard | Stocké dans la mémoire du téléphone (ME). C'est le SMS quotidien. |
| Classe 2 | SIM-specific | Destiné spécifiquement à être stocké sur la carte SIM/USIM. |
| Classe 3 | TE-specific | Transmis à un équipement externe connecté (ordinateur, automate, terminal). |
Analyse d'un applet pour Javacard
Maintenant que nous avons compris ce qu'est une smartcard, plus précisément une javacard, et comment elle fonctionne, nous pouvons retourner au challenge.
Le challenge nous fournit un fichier binaire jc_dump.bin extrait d'une smartcard.
$ file jc_dump.bin
jc_dump.bin: data
Ce fichier contient deux chaines de caractères identifiables, n'apportant que peu d'informations sur le type de fichier et la nature de son contenu.
$ strings jc_dump.bin
ANSSI FCSC
ANSSI FCSC2021
%AA)
%AA)
;pN(
Il a une taille relativement petite, 1060 octets.
$ ls -la jc_dump.bin
-rwxrwxrwx 1 ohohoh ohohoh 1060 Dec 26 00:35 jc_dump.bin
Jusqu'à là, rien de très utile. En revanche, la description du challenge précise Java Card 3.0.1, ce qui indique que le dump est extrait d'une smartcard Java, dont le format logiciel est CAP (comme vu précédemment).
Format CAP
Intéressons-nous à la structure du format CAP. Une documentation plutôt complète est fournie ici.
Pour résumé, un fichier CAP est composé de plusieurs blocs.
- le premier octet de chaque bloc identifie le type du bloc. On en trouve 12 types au total, décrit dans le tableau ci-dessous.
| Component Type | Value |
|---|---|
| COMPONENT_Header | 1 |
| COMPONENT_Directory | 2 |
| COMPONENT_Applet | 3 |
| COMPONENT_Import | 4 |
| COMPONENT_ConstantPool | 5 |
| COMPONENT_Class | 6 |
| COMPONENT_Method | 7 |
| COMPONENT_StaticField | 8 |
| COMPONENT_ReferenceLocation | 9 |
| COMPONENT_Export | 10 |
| COMPONENT_Descriptor | 11 |
| COMPONENT_Debug | 12 |
- le deux octets suivant la taille du bloc (celle-ci est donc codée sur deux octets)
- Le reste des octets constitue les données du bloc.
Si on applique cette règle sur notre fichier, on remarque que ça match bien.

Le fichier commence par un octet valant 01, un bloc de type "entête", d'une taille de 0014, soit 20 octets.
Vérifions si le contenu du bloc d'entête est valide (et essayons d'en extraire des informations qui pourraient s'avérer utiles).
Analyse du bloc d'entête
D'après la documentation, le bloc d'entête à cette structure :
header_component {
u1 tag
u2 size
u4 magic
u1 minor_version
u1 major_version
u1 flags
package_info package
package_name_info package_name
}
Si on prend le premier bloc du fichier :

- Tag et Size sont respectivement le type de bloc (01, entête) et sa taille (0014, 20 octets).
- Magic est une constante qui vaut
0xDECAFFED. - Les deux octets suivants (
0102) comprennent les deux champs minor_version (1), major_version (2), codé chacun sur un octet. - Flags, sur un octet, vaut
04ce qui indique ACC_APPLET. - Package
00010a??? - ANSSI FCSC est le Package_name.
On a donc bien la confirmation que le fichier contient des blocs respectant le format CAP.
Extraction des blocs
On peut maintenant extraire le contenu de chaque bloc et son contenu avec le script ci-dessous.
CAP_BLOCK_TYPE = {
1: "COMPONENT_Header",
2: "COMPONENT_Directory",
3: "COMPONENT_Applet",
4: "COMPONENT_Import",
5: "COMPONENT_ConstantPool",
6: "COMPONENT_Class",
7: "COMPONENT_Method",
8: "COMPONENT_StaticField",
9: "COMPONENT_ReferenceLocation",
10: "COMPONENT_Export",
11: "COMPONENT_Descriptor",
12: "COMPONENT_Debug"
}
import binascii
with open("jc_dump.bin","rb") as file:
data = file.read()
pos = 0
while pos < len(data):
block_type = data[pos]
block_len = int.from_bytes(data[pos+1:pos+3])
print("%s (%i) " % (CAP_BLOCK_TYPE[block_type], block_len))
print(binascii.hexlify(data[pos+3:pos+3+block_len]).decode())
pos += 3 + block_len
Analyse du bloc method
Le bloc qui nous intéresse le plus est le bloc method, qui contient les instructions (le bytecode) du programme. Grâce au script du paragraphe précédent, on peut obtenir son contenu facilement.
COMPONENT_Method (573)
02015e000c016c000b015e800c01b0000c0644188c00031e29040329051010900b3d031050383d041019383d0510fe383d0610ae383d07104d383d08102b383d100610aa383d1007107b383d10081091383d10091023383d100a10ac383d100b1085383d100c1044383d100d1068383d100e106f383d100f10663828061010900b3d0310ca383d041049383d051099383d06103b383d071073383d0810fc383d1006106e383d1007107a383d10081064383d10091062383d100a107f383d100b10e5383d100c1080383d100d10d6383d100e10a8383d100f105738280718100f110080038d0005940000028700ad001506038e03000204181017048d00048701ad01ad00051507031507928b00061f10096c1d160404191e254141290416040419160425414129045904010429051605600f18191e0441191e258b00077006188b00087a05308f00093d181d1e8c000a3b7a0110047806431064900b2804ad01191e1f1504038b000d3b704e280515058b000e2906160673003700010005001900310029002100111166618d000f70201166628d000f70181166638d000f70101166648d000f70081166658d000f700a28051166668d000f1504770524188b001060037a198b00112d1a032510806a08116e008d000f1a052561071a06256008116a868d000f1a042575004d0001000100091a07251100ff5332198b001229041f65101f1604433219088b0013290470f1181a081a07251100ff538c001428051505031a031505928d00153b19031505928b00167008116d008d000f7a
D'après la documentation, le bloc est organisé comme ceci :
method_component {
u1 tag
u2 size
u1 handler_count
exception_handler_info exception_handlers[handler_count]
method_info methods[]
}
Comme handler_count vaut 2, la liste exception_handlers est composée de deux structures exception_handler_info faisant 8 octets chacune (015e000c016c000b et 015e800c01b0000c).
exception_handler_info {
u2 start_offset
u2 bitfield {
bit[1] stop_bit
bit[15] active_length
}
u2 handler_offset
u2 catch_type_index
}
- start_offset indique la structure protégée par l'exception ;
- active_length sa taille ;
- handler_offset l'adresse du handler (l'endroit où jump le programme lors d'un catch) ;
- catch_type_index lndex dans le composant Constant Pool de la classe de l'exception.
La suite est la liste method_info.
0644188c00031e29040329051010900b3d031050383d041019383d0510fe383d0610ae383d07104d383d08102b383d100610aa383d1007107b383d10081091383d10091023383d100a10ac383d100b1085383d100c1044383d100d1068383d100e106f383d100f10663828061010900b3d0310ca383d041049383d051099383d06103b383d071073383d0810fc383d1006106e383d1007107a383d10081064383d10091062383d100a107f383d100b10e5383d100c1080383d100d10d6383d100e10a8383d100f105738280718100f110080038d0005940000028700ad001506038e03000204181017048d00048701ad01ad00051507031507928b00061f10096c1d160404191e254141290416040419160425414129045904010429051605600f18191e0441191e258b00077006188b00087a05308f00093d181d1e8c000a3b7a0110047806431064900b2804ad01191e1f1504038b000d3b704e280515058b000e2906160673003700010005001900310029002100111166618d000f70201166628d000f70181166638d000f70101166648d000f70081166658d000f700a28051166668d000f1504770524188b001060037a198b00112d1a032510806a08116e008d000f1a052561071a06256008116a868d000f1a042575004d0001000100091a07251100ff5332198b001229041f65101f1604433219088b0013290470f1181a081a07251100ff538c001428051505031a031505928d00153b19031505928b00167008116d008d000f7a
Celle-ci a le format suivant :
method_info {
method_header_info method_header
u1 bytecodes[]
}
method_header a un des deux formats.
method_header_info {
u1 bitfield {
bit[4] flags
bit[4] max_stack
}
u1 bitfield {
bit[4] nargs
bit[4] max_locals
}
}
extended_method_header_info {
u1 bitfield {
bit[4] flags
bit[4] padding
}
u1 max_stack
u1 nargs
u1 max_locals
}
On pourrait, en extrayant chaque structure de chaque bloc, obtenir assez d'information pour reconstituer le bytecode Java dans son intégralité. Il serait alors possible de l'analyser séparément, en utilisant un outil comme radare2par exemple. C'était ma première idée, mais c'est devenu très vite compliqué (certains blocs contiennent des structures en cascade).
J'ai opté pour une autre solution, convertir le fichier cap dans un format plus facilement analysable.
Conversion en classe Java
On apprend via quelque recherche qu'il est possible de convertir les fichiers CAP en classe Java, bien plus aisément analysable. Dans cet objectif, j'ai téléchargé le SDK Javacard depuis le site officiel d'oracle ici.
Le SDK contient un utilitaire très intéressant, un script bat nommé normalizer.bat qui permet de parser automatiquement le contenu d'un fichier .cap et de générer un fichier .class.
⚠️ L'utilitaire normalizer.bat ne semble pas présent dans toutes les versions des SDK Javacard. Pour éviter tout problème, je vous recommande d'installer spécifiquement la version
java_card_kit-classic-3_0_5-u4-win32-bin-do-b_109-03_jun_2020.msi.
Pour faire fonctionner le script, il faut une version SDK de java compatible. J'ai choisi la version 8 disponible ici.
Une fois que tout est installé, on peut lancer l'utilitaire normalizer.bat de la manière suivante :
cd "C:\Program Files (x86)\Oracle\Java Card Development Kit 3.0.5u4\bin"
set JAVA_HOME=C:\Program Files (x86)\Java\jdk1.8.0_202\
normalizer.bat normalize -i CAP_FILE.cap
Cependant, si l'on essaie directement sur le fichier fourni jc_dump.bin, on obtient une erreur de décompression.

Reconstruction d'un fichier CAP
En cherchant un peu sur Internet, j'ai réussi à obtenir un fichier helloworld.cap valide, fonctionnant avec l'outil normalizer.bat. J'ai comparé son contenu avec celui du fichier fourni dans le cadre du challenge pour comprendre ce qui coinçait.
| helloworld.cap | jc_dump.bin |
|---|---|
![]() |
![]() |
Le problème réside dans le fait que les données de chaque bloc de l'applet sont compressées avant d'être flashées sur la smartcard (on remarque qu'il y a un entête ZIP PK\x03\x04).

En réalité, les données ne sont pas compressées, mais sont présentées sous la forme d'une archive ZIP. Si on regarde les octets 8 et 9 du début du fichier, ils sont nuls, ce qui n’indique aucune compression. L'archive sert juste à représenter les différents blocs sur forme de fichier.

Or, nous n'avons qu'un dump de l'applet, pas le fichier cap d'origine. Il nous faut reformater le dump de telle sorte à ce qu'on puisse retrouver la structure sous forme d'archive. Pour cela, j'ai développé le script ci-dessous.
import zipfile
COMPONENT_NAMES = {
1: "Header",
2: "Directory",
3: "Applet",
4: "Import",
5: "ConstantPool",
6: "Class",
7: "Method",
8: "StaticField",
9: "RefLocation",
11: "Descriptor",
33: "StaticResources",
34: "Custom"
}
def build_valid_cap(output_filename, components_dict, package_path):
"""
components_dict: dictionnaire { "Header.cap": b'\x01\x00...', "Method.cap": ... }
package_path: le chemin interne
"""
with zipfile.ZipFile(output_filename, 'w', compression=zipfile.ZIP_STORED) as cap:
for comp_name, data in components_dict.items():
full_path = f"{package_path}{comp_name}"
cap.writestr(full_path, data)
print(f"[OK] Archive générée : {output_filename}")
cap_components = {
}
with open("jc_dump.bin","rb") as file:
data = file.read()
pos = 0
while pos < len(data):
block_type = data[pos]
block_len = int.from_bytes(data[pos+1:pos+3])
block_data = data[pos:pos+3+block_len]
print("- %s (%i) " % (COMPONENT_NAMES[block_type], block_len))
block_name = COMPONENT_NAMES[block_type] + ".cap"
cap_components[block_name] = block_data
pos += 3 + block_len
path = "FCSC/"
build_valid_cap("jc_bin-reconstruct.cap", cap_components, path)
On peut maintenant ouvrir le fichier reconstitué en tant qu'archive

et l'utiliser avec normalizer.bat

On obtient alors un fichier AAA.class qu'on peut ouvrir dans l'outil JD-GUI.
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISOException;
import javacard.framework.Util;
import javacard.security.AESKey;
import javacard.security.CryptoException;
import javacard.security.Key;
import javacard.security.KeyBuilder;
import javacardx.crypto.Cipher;
import net.sourceforge.globalplatform.jc.helloworld.AAA;
public class AAA extends Applet {
private AESKey field_token0_descoff10;
private Cipher field_token1_descoff17;
public void process(APDU paramAPDU) {
short s1;
short s2;
byte[] arrayOfByte2;
if (selectingApplet())
return;
byte[] arrayOfByte1 = paramAPDU.getBuffer();
if (arrayOfByte1[0] != Byte.MIN_VALUE)
ISOException.throwIt((short)28160);
if (arrayOfByte1[2] != 0 || arrayOfByte1[3] != 0)
ISOException.throwIt((short)27270);
switch (arrayOfByte1[1]) {
case 1:
s1 = (short)(short)(arrayOfByte1[4] & 0xFF);
for (s2 = (short)paramAPDU.setIncomingAndReceive(); (short)s1 > 0; s2 = (short)paramAPDU.receiveBytes((short)5))
s1 = (short)(short)((short)s1 - (short)s2);
arrayOfByte2 = method_token255_descoff60(arrayOfByte1, (short)5, (short)(arrayOfByte1[4] & 0xFF));
Util.arrayCopy(arrayOfByte2, (short)0, arrayOfByte1, (short)0, arrayOfByte2.length);
paramAPDU.setOutgoingAndSend((short)0, arrayOfByte2.length);
return;
}
ISOException.throwIt((short)27904);
}
private byte[] method_token255_descoff60(byte[] paramArrayOfbyte, short paramShort1, short paramShort2) {
byte[] arrayOfByte = new byte[100];
try {
this.field_token1_descoff17.doFinal(paramArrayOfbyte, (short)paramShort1, (short)paramShort2, arrayOfByte, (short)0);
} catch (CryptoException cryptoException) {
short s = (short)cryptoException.getReason();
switch ((short)s) {
case 5:
ISOException.throwIt((short)26209);
break;
case 1:
ISOException.throwIt((short)26210);
break;
case 4:
ISOException.throwIt((short)26211);
break;
case 3:
ISOException.throwIt((short)26212);
break;
case 2:
ISOException.throwIt((short)26213);
break;
}
} catch (Exception exception) {
ISOException.throwIt((short)26214);
}
return arrayOfByte;
}
public boolean select() {
return true;
}
public static void install(byte[] paramArrayOfbyte, short paramShort, byte paramByte) {
new AAA(paramArrayOfbyte, (short)paramShort, (short)paramByte);
}
protected AAA(byte[] paramArrayOfbyte, short paramShort, byte paramByte) {
short s1 = (short)(short)paramShort;
short s2 = (short)0;
byte[] arrayOfByte1 = {
80, 25, -2, -82, 77, 43, -86, 123, -111, 35,
-84, -123, 68, 104, 111, 102 };
byte[] arrayOfByte2 = {
-54, 73, -103, 59, 115, -4, 110, 122, 100, 98,
Byte.MAX_VALUE, -27, Byte.MIN_VALUE, -42, -88, 87 };
this.field_token0_descoff10 = (AESKey)KeyBuilder.buildKey((byte)15, (short)128, false);
this.field_token0_descoff10.setKey(arrayOfByte1, (short)0);
this.field_token1_descoff17 = Cipher.getInstance((byte)23, true);
this.field_token1_descoff17.init((Key)this.field_token0_descoff10, (byte)2, arrayOfByte2, (short)0, arrayOfByte2.length);
if ((short)paramByte >= 9) {
s1 = (short)(short)((short)s1 + (short)(1 + paramArrayOfbyte[(short)paramShort]));
s1 = (short)(short)((short)s1 + (short)(1 + paramArrayOfbyte[(short)s1]));
s1 = (short)(s1 + 1);
s2 = (short)1;
}
if ((short)s2 != 0) {
register(paramArrayOfbyte, (short)((short)paramShort + 1), paramArrayOfbyte[(short)paramShort]);
} else {
register();
}
}
}
Le code initialise un objet de déchiffrement d'AES CBC dont la clé est bytes([80, 25, 254, 174, 77, 43, 170, 123, 145, 35, 172, 133, 68, 104, 111, 102]) et l'IV bytes([202, 73, 153, 59, 115, 252, 110, 122, 100, 98, 127, 229, 128, 214, 168, 87]).
Déchiffrement des données
Les commandes APDU sont envoyées sous le format
| Commandes APDU (C-APDU) | ||||||
| Header (obligatoire) | Body (obligatoire) | |||||
| CLA | INS | P1 | P2 | Lc | Data Field | Le |
| Code classe | Code instruction | Paramètre 1 | Paramètre 2 | Taille donnée envoyée | Données envoyées | Taille donnée attendue |
| 0x80 (Byte.MIN_VALUE) |
0x01 (Instruction déchiffrement) |
0x00 | 0x00 | ?? | ?? | ?? |
| Réponse APDU (R-APDU) | ||
| Data Field | SW1 | SW2 |
| Donnée de la réponse | Status (octet 1) | Status (octet 2) |
| ??? | 90 | 00 |
| Réponse "OK" standard | ||
On nous fournit une réponse APDU.
C6 5F 56 69 08 F8 A0 4A 4D CE 35 C0 1A 4B B2 AB 29 D4 1C FC EA 3D FF 7E 97 E3 42 F6 4F 60 27 14 9C C7 83 4A 04 F9 D7 C2 DE 8F 35 0E 96 77 09 6F 81 EA D0 CD 09 FB BE 74 58 D7 FE 45 2D 9D A4 43 11 87 63 31 24 EF 65 3D 6E 55 DF 54 34 AC E0 A5 90 00
On reconnait le statut 90 00 (Ok) à la fin de l'échange. On peut en déduire que tous les octets qui se situent avant font partie de la donnée chiffrée.
On a maintenant toutes les informations nécessaires pour déchiffrer l'échange.
Flag
from Crypto.Cipher import AES
import binascii
def decrypt_java_card_data(ciphertext_hex):
# 1. Conversion de la clé (Key) de Java (signed) vers Python (unsigned)
# Java: { 80, 25, -2, -82, 77, 43, -86, 123, -111, 35, -84, -123, 68, 104, 111, 102 }
key = bytes([80, 25, 254, 174, 77, 43, 170, 123, 145, 35, 172, 133, 68, 104, 111, 102])
# 2. Conversion de l'IV de Java (signed) vers Python (unsigned)
# Java: { -54, 73, -103, 59, 115, -4, 110, 122, 100, 98, 127, -27, -128, -42, -88, 87 }
iv = bytes([202, 73, 153, 59, 115, 252, 110, 122, 100, 98, 127, 229, 128, 214, 168, 87])
# 3. Préparation des données chiffrées
try:
ciphertext = binascii.unhexlify(ciphertext_hex)
except Exception:
return "Erreur : Le format hexadécimal est invalide."
# 4. Configuration du Cipher (AES en mode CBC)
# L'applet utilise NOPAD, donc la taille des données doit être un multiple de 16
cipher = AES.new(key, AES.MODE_CBC, iv)
try:
decrypted_data = cipher.decrypt(ciphertext)
return decrypted_data
except Exception as e:
return f"Erreur lors du déchiffrement : {str(e)}"
data_to_decrypt = "C6 5F 56 69 08 F8 A0 4A 4D CE 35 C0 1A 4B B2 AB 29 D4 1C FC EA 3D FF 7E 97 E3 42 F6 4F 60 27 14 9C C7 83 4A 04 F9 D7 C2 DE 8F 35 0E 96 77 09 6F 81 EA D0 CD 09 FB BE 74 58 D7 FE 45 2D 9D A4 43 11 87 63 31 24 EF 65 3D 6E 55 DF 54 34 AC E0 A5".replace(" ","")
result = decrypt_java_card_data(data_to_decrypt)
if isinstance(result, bytes):
print(f"Texte clair (Hex) : {binascii.hexlify(result).decode()}")
print(f"Texte clair (ASCII) : {result.decode(errors='ignore')}")
else:
print(result)

Bibliographie
- https://perso.telecom-paristech.fr/urien/introcarte2014.pdf
- https://inria.hal.science/hal-01250605/document
- http://pfa12.free.fr/doc_java/javacard_specifications/specs/jcvm/html/JCVM06cap.html
- https://blog.csdn.net/chair/article/details/153854266

