Problème sur le déchiffrage de certains champs hérités

Bonjour,

Lorsque j’ai voulu crypter les champs de certains objets, j’ai rencontré des erreurs, j’ai suivi cet exemple pour le faire. Chiffrement des données - #3 by scampano

J’ai hérité l’objet A de EncryptedObjectDB et j’ai mis à jour les champs à crypter avec la classification. Pour simplifier l’explication, j’ai des objets qui héritent de l’objet A


Dans l’objet A 3 champs sont cryptés
Dans l’objet B les 3 champs hérités sont bien cryptés dans la BD et décryptés pour l’affichage mais dans les objets C et D, je n’ai que le Field A2 hérité dont le cryptage fonctionne correctement, les deux autres champs, Field A3 et Field A4 sont cryptés dans la BD mais dans le postSelect j’ai des erreurs (Length of Base64 encoded input string is not a multiple of 4., Illegal character in Base64 encoded data.) et les données sont affichées sans décryptage.

tempsnip

Le code de la classe intermédiaire EncryptedObjectDB
package com.simplicite.commons.Workforce;

import java.util.*;

import com.simplicite.util.*;
import com.simplicite.util.exceptions.*;
import com.simplicite.util.tools.*;

/**
 * Shared code EncryptedObjectDB
 */
public class EncryptedObjectDB extends ObjectDB {
	private static final long serialVersionUID = 1L;
	
	@Override
	public String preSave() {
		try{
			for(ObjectField f : getFields())
				if(isEncrypted(f) && !Tool.isEmpty(f.getValue())){
					f.setValue(EncryptionTool.encrypt(f.getValue(), getEncryptionKey()));
				}
		}
		catch(EncryptionException e){
			AppLog.error(e, getGrant());	
		}
		
		return super.preSave();
	}
	
	@Override
	public void postSelect(String rowId, boolean copy) {
		try{
			for(ObjectField f : getFields())
				if(isEncrypted(f) && !Tool.isEmpty(f.getValue())){
					f.setValue(EncryptionTool.decrypt(f.getValue(), getEncryptionKey()));
					
				}
		}
		catch(EncryptionException e){
			AppLog.error(e, getGrant());	
		}
		
		super.postSelect(rowId, copy);
	}
	
	private static boolean isEncrypted(ObjectField f){
		return !f.isEmpty() && "CONFIDENTIAL".equals(f.getClassification());
	}
	
	private static String getEncryptionKey(){
		return Grant.getSystemAdmin().getParameter("ENCRYPTION_KEY"); 
	}

	
}

Technical information

Instance /health
[Application]
ApplicationVersion=
ContextPath=
ContextURL=http://localhost:8082
ActiveSessions=1
TotalUsers=24
EnabledUsers=3
LastLoginDate=2022-10-12 08:00:05

[Server]
ServerInfo=Apache Tomcat/9.0.64
ServerType=WEB
ServerActiveSessions=1
ServerSessionTimeout=30

[OS]
Name=Linux
Architecture=amd64
Version=5.10.16.3-microsoft-standard-WSL2
DockerImageName=centos7
SystemEncoding=UTF-8

[JavaVM]
Version=17.0.3
Vendor=Eclipse Adoptium
VMName=OpenJDK 64-Bit Server VM
VMVersion=17.0.3+7
ScriptEngine=rhino
ScriptEngineVersion=Rhino 1.7.13 2020 09 02
HeapFree=443089
HeapSize=655360
HeapMaxSize=1585152
TotalFreeSize=1372881

[Cache]
ObjectCache=105
ObjectCacheMax=10000
ObjectCacheRatio=1
ProcessCache=0
ProcessCacheMax=10000
ProcessCacheRatio=0
APIGrantCache=0
APIGrantCacheMax=1000
APIGrantRatio=0

[Database]
Vendor=3
ProductName=PostgreSQL
ProductVersion=12.10
DriverName=PostgreSQL JDBC Driver
DriverVersion=42.4.0
DBDate=2022-10-12 11:03:39
DBDateOffset=0
DBPatchLevel=5;P02;83aba48650c13a6dae0ef5f65dc65a81
UsingBLOBs=true

[Healthcheck]
Date=2022-10-12 11:03:39
ElapsedTime=62
Simplicité logs
2022-10-12 10:00:46,642|SIMPLICITE|ERROR||http://d2b2523dca42:8080||ERROR|system|com.simplicite.util.ObjectHooks|postSelect||Event: Implementation error in the postSelect hook of object WrkPerson
    java.lang.IllegalArgumentException: Length of Base64 encoded input string is not a multiple of 4.
     at com.simplicite.util.tools.Base64Tool.decode(Base64Tool.java:141)
     at com.simplicite.util.tools.Base64Tool.decode(Base64Tool.java:127)
     at com.simplicite.util.tools.EncryptionTool.decrypt(EncryptionTool.java:117)
     at com.simplicite.commons.Workforce.EncryptedObjectDB.postSelect(EncryptedObjectDB.java:35)

2022-10-12 10:49:48,883|SIMPLICITE|ERROR||http://d2b2523dca42:8080||ERROR|system|com.simplicite.util.ObjectHooks|postSelect||Event: Implementation error in the postSelect hook of object WrkPerson
    java.lang.IllegalArgumentException: Illegal character in Base64 encoded data.
     at com.simplicite.util.tools.Base64Tool.decode(Base64Tool.java:161)
     at com.simplicite.util.tools.Base64Tool.decode(Base64Tool.java:127)
     at com.simplicite.util.tools.EncryptionTool.decrypt(EncryptionTool.java:117)
     at com.simplicite.commons.Workforce.EncryptedObjectDB.postSelect(EncryptedObjectDB.java:35)

Bonjour,

  1. Est-ce que A, B, C et D sont bien paramétrés pour utiliser la même table?
  2. Avez-vous essayé de reproduire votre exemple schématique avec A,B,C,D sur une instance vierge?

Une remarque, vous avez supprimé un commentaire fondamental concernant le lieu de stockage de la clef de cryptage du code initial. Stocker la clef de cryptage dans la même base que les données que vous cryptez revient à ne rien crypter du tout.

	private static String getEncryptionKey(){
		// for ease of use, this method uses a db-stored param. It's advisable to use a JVM or System paremeter (see doc)
		return Grant.getSystemAdmin().getParameter("MY_ENCRYPTION_KEY"); // Simplicite012345
	}

J’ai reproduit votre cas sur une instance vierge (tel que vous devriez le faire avant d’ouvrir un ticket), les données s’affichent bien et sont bien cryptées en base

Le module est ici si vous souhaitez essayer de reproduire le cas sur le cas décrit:

En conclusion je pense qu’il y a erreur de diagnostic et que votre problème vient plutôt d’une mauvaise configuration de l’héritage.

Bonjour @ihabtak

Après analyse de votre cas réel, il se trouve que votre objet A contient des attributs du méta-modèle, notamment usr_login qui est classifié confidentiel.

  1. le try / catch de EncryptedObjectDB englobe toute la boucle parcourant les fields, ce qui aboutit à une situation où, en cas d’EncryptionException, on puisse se retrouver avec certains champs correctement décryptés et d’autres pas, selon leur ordre de traîtement.
    • => Corrigez EncryptedObjectDB pour mettre le try/catch à l’intérieur de la boucle for (j’ai corrigé également l’exemple auquel vous faisiez référence)
  2. l’utilisation du champ “confidentiel”, que j’indiquais comme un détournement du méta-modèle, est une mauvaise pratique, un raccourci pris pour démontrer la réalisabilité du cas d’usage
    • => Trouvez un moyen plus discriminant de “marquer” les champs à crypter (par exemple en ajoutant une vérification sur le trigrammme du field, ou en maintenant une liste “en dur” dans un paramètre système ou dans la classe EncryptedObjectDB évitant ainsi la boucle for, ou en ajoutant un trigramme “cry” dans le nom des champs à crypter, etc etc)
    • ou bien excluez les champs confidentiels système

L’exemple de EncryptedObjectDB est une preuve de réalisabilité, non transposable tel quel, car il y a de nombreux écueils à éviter:

  • le détournement du méta-modèle peut déboucher sur les comportements imprévisibles, vous en avez fait les frais et je m’en excuse
  • la clef de cryptage doit absolument être stockée en-dehors de la BDD
  • la taille des champs est à contrôler avec attention: avec un varchar de 255 en base, il faut sévèrement limiter la longueur de la valeur saisie par l’utilisateur car la valeur cryptée est bien plus longue que la valeur saisie, et une fois tronquée par la BDD elle ne sera plus décryptable…
  • etc… (j’insiste: l’implémentation est à analyser et à implémenter avec attention :pray: )
1 Like

Bonjour @scampano

J’ai pu appliquer vos consignes en faisant une liste de champs à crypter pour les tester et cela fonctionne très bien, il faut juste ajouter une condition au moment de postSelect pour tester les anciennes valeurs qu’il veut décrypter mais qui ne sont pas encore cryptées dans la base de données sinon cela fait planter le décryptage des autres champs.

    @Override
	public void postSelect(String rowId, boolean copy){
		for(ObjectField f : getFields())
			try{
				if(isEncrypted(f))
				   if(isBase64Encoded(f.getValue()))
					f.setValue(EncryptionTool.decrypt(f.getValue(), getEncryptionKey()));
			}
			catch(EncryptionException e){
				AppLog.error(e, getGrant());	
			}
		
		super.postSelect(rowId, copy);
	}
    private static boolean isBase64Encoded(String s) {
            String pattern = "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$";
            Pattern r = Pattern.compile(pattern);
            Matcher m = r.matcher(s);
            return m.find();
        }

Merci pour votre aide.

1 Like

C’est surprenant, le passage du try-catch dans la boucle for devrait permettre le déchiffrement des autres champs même en cas d’erreur. Personnellement j’aurai simplement ignoré les exceptions (peut-être avec un log warning à la place d’un error), surtout que sur une base propre le cas ne devrait pas se produire.

A minima, sortez la compilation du pattern de la méthode et mettez-là en private final Pattern BASE64, c’est pas très écolo de recompiler la regex à chaque déchiffrement de champ :stuck_out_tongue:

Si vous avez une liste de champs définie, il est aussi inutile d’utiliser getFields() et de boucler sur tous les champs de votre objet, boucler sur votre liste sera plus efficace. Mieux, faites une méthode List<String> getEncryptedFields() à surcharger sur chaque objet chiffré :wink:

C’est certes de l’optimisation, mais quand on affiche des listes de 50 objets avec 20 champs chiffrés et 100 champs non chiffrés, ce n’est pas négligeable. (et ça me ferait mal au cœur que votre client se plaigne de lenteurs du moteur Simplicité pour un mauvais conseil de chiffrement :innocent:)

1 Like

This topic was automatically closed 60 minutes after the last reply. New replies are no longer allowed.