Classe java dans une étape DATA
Beaucoup de fonctions JAVA possèdent leur équivalent en SAS. Cependant dans certains cas il s'avère nécessaire
de faire appel à des classes JAVA. Par exemple, il n'est pas possible de lire un fichier encrypté en AES
directement dans une étape DATA.
Lorsque l'on veut fournir des données à SAS, on envoir par exemple des fichiers csv. Dans un environement fortement sécurisé, il n'est pas souhaitable que
les données soient enregistrées en clair sur les disques. Une des solutions est de crypter ce fichier mais nativement SAS n'est pas
capable de lire le fichier encrypté. C'est ce challenge que je vous propose de relever.
Mise en place de la lecture de fichiers encryptés en AES
Listons tout d'abord les principales difficultés que nous allons rencontrer:
- Création d'une classe JAVA décryptant un fichier AES et renvoyant ligne de données par ligne de données. En effet, nous attendons
d'obtenir une table SAS avec les données décryptées.
- Synchroniser les sorties JAVA avec SAS. Le langage JAVA est fortement typé alors que les types en SAS se résument
aux nombres rééls (type float) et aux chaînes de caractères (type String).
- Avoir une fonctionnalité générique capable de lire n'importe quel format de csv, au sens DDL.
- L'accès au keystore.
De quoi avons nous besoin ?
- d'un keystore JAVA,
- d'une classe JAVA satisfaisant le besoin,
- d'un programme SAS appelant la classe JAVA et générant une table SAS après en avoir lu sa structure,
Génération du keystore JAVA
L'utilitaire keytool est disponible dans le JRE de SAS. Il suffit de lancer la commande type suivante :
keytool -genseckey -keystore sbi_keystore.jks -storetype jceks -storepass mystorepass -keyalg AES -keysize 256 -alias jceksaes -keypass mykeypass
Voici l'explication des paramètres:
genseckey
Génére une clé secrète indiquant la création d'une clé synchrone qui deviendra notre clé AES.
keystore
Emplacement du magasin de clés. Si le keystore n'existe pas, l'outil créera un nouveau magasin. Les chemins peuvent être relatifs ou absolus mais doivent être locaux.
storetype
Type du magasin (JCE, PK12, JCEKS, etc). JCEKS est utilisé pour stocker des clés symétriques (AES) non contenues dans un certificat.
storepass
Mot de passe lié au magasin de clés. Il est fortement recommandé de créer une phrase de passe forte pour le keystore.
keyalg
Algorithme utilisé pour créer la clé (AES/DES/etc).
keysize
Taille de la clé (128, 192, 256, etc.).
alias
Alias donné à la clé nouvellement créée dans lequel on peut se référer lors de l'utilisation de la clé.
keypass
Mot de passe protégeant l'utilisation de la clé.
Création de la classe JAVA
Pour être sûre de la compatibilité des JRE, il est plus prudent de compiler la classe avec le JRE de
SAS (<binaire SAS>/SASPrivateJavaRuntimeEnvironment/9.4/jre/bin).
JAVA possède nativement des packages de classes implémentant les algorithmes AES. Cependant, ceux-ci
fonctionnent en décryptant bloc d'octets par bloc d'octets ce qui peut engendrer les soucis suivants
entres autres:
- si le fichier de données contient peu de colonnes, le bloc peut contenir une ou plusieurs lignes
de données,
- si le fichier de données contient beaucoup de colonnes, le bloc peut ne pas contenir de lignes complètes,
- le bloc lu s'arrête au milieu d'une données (cas le plus probable),
- le bloc lu s'arrête au milieu d'un caractère multi-octets, c'est à dire qu'un caractère qui nécessite
plus d'un octet pour être codé (cas le plus problématique).
Autre problème potentiel, le caractère de fin de ligne. Ce dernier peut être \r\n ou \n. Il convient d'harmoniser
celui ci en \n par exemple et donc de détecter la présence de \r\n pour les remplacer par \n. Mais ce
caractère de fin de ligne peut potentiellement poser problème lors de l'envoie de la chaîne décryptée vers SAS.
J'ai pris parti de remplacer ce \n par la chaîne de caractère <<end of line>> qui ne posera aucun problème
à SAS.
Maintenant il est temps de se poser la question de ce que l'on veut renvoyer vers SAS. Comme l'on peut avoir décrypté
zéro, une ou plusieurs lignes complètes et une ligne incomplète, je veux plutôt que JAVA n'envoie que des lignes
complètes (une ou n ligne) et donc que JAVA doit garder dans un buffer la ligne incomplète.
Ces problèmes posés, nous pouvons maintenant implémenter la classe. Nous allons créer une méthode line qui renvoie
à SAS un ou n lignes complètes. Celle-ci sera appelé autant de fois que nécessaire jusqu'à la fin du fichier. En voici l'algorithme:
- Lecture du bloc suivant.
- Concaténation du bloc lu avec une variable servant de buffer(Arrays.copyOfRange). Ce dernier est un tableau d'octets, plus pratique pour
gérer les problèmes liés aux caractères multi-octets.
- Test fin de fichier:
- si oui, le buffer est transformé en String encodé en UTF-8 (new String(ret.getBytes(),UTF-8)), les éventuels \n transformés en <<end of line>> puis envoyé à SAS.
- si non, nous bouclons tant qu'un \n n'a pas été trouvé et/ou que la fin de fichier n'a pas été atteinte. Le buffer est concaténé avec
le nouveau bloc lu durant l'itération transformé en String encodé en UTF-8 (new String(ret.getBytes(),UTF-8)), les éventuels \n transformés en <<end of line>> puis envoyé à SAS.
La partie lue mais ne constituant pas une ligne entière de données (ne finissant donc pas par un \n) constitue dorénavant le buffer.
L'indicateur de fin de fichier est une variable globale qui va permettre d'informer la classe que la fin du fichier a été atteinte.
Enfin SAS n'a pas de fonction de split de chaînes de caractères avec un mot comme délimiteur. L'on peut saisir
un mot mais SAS interprétera chaque caractère de la chaîne comme des délimiteurs. Du coup, nous allons
également créer une telle méthode en JAVA qui sera appelé dans SAS:

Cas particulier:
- le premier bloc contient le IV (le vecteur d'initialisation) en seize caractères. Il faut donc commencer à lire
le premier bloc après ce vecteur.
Programme SAS de décryption
Nous allons créer une nouvelle transformation DIS pour notre besoin. Celle-ci prendra une table en
entrée et une table en sortie. La transformation analysera la structure de la table pour créer la table
décryptée en sortie.
Voici le comportement en JAVA que nous allons transposé en SAS :

- Le constructeur:
AESFiles aesfile = new AESFiles("/SAS/projects/aes/sbi_keystore.jks", "******", "******", "******", "/SAS/projects/aes/CARS.txt.aes");
devient en SAS:
DECLARE javaobj j("com.sbi.aes.AESFiles","&KEYSTORE_PATH","&KEYSTORE_PASS","&KEYSTORE_KEY", "&KEY_PASS","&aesfile.");
- Le getter:
aesfile.getEof()
devient en SAS:
j.callIntMethod("getEof",eof);
La variable SAS eof récupère le retour du getter getEof()
- La récupération de la donnée non cryptée:
String s = aesfile.line();
devient en SAS:
j.callStringMethod("line", line);
- Enfin le parsing des lignes:
for (int nb = 0; nb < aesfile.lengthArray(s); nb++) aesfile.scanWord(s, nb)
devient en SAS:
do i=0 to l-1; j.callStringMethod("scanWord",line, i, out); end;
La variable SAS out doit encore être parsé par ";" pour créer les variables.
Nous obtenons donc :

Le fichier dcl contient la structure de la table en sortie et le fichier scan contient un code permettant de parser la ligne afin de renseigner dans les colonnes de la table en sortie les valeurs.
Voici un exemple de job DIS pour décrypter un fichier AES contenant les données de la table sashelp.cars:

et un extrait de la log:

Nous voyons que le programme a correctement analysé la structure de la table sashelp.cars pour en déduire sa structure puis parser correctement la chaîne décryptée provenant de la classe JAVA.