Cryptographie dans Spring

Chiffrement et déchiffrement

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com

Dans notre application on veut cacher certaines informations qui pourraient dévoiler aux utilisateurs le volume de l'activité. Pour ce faire on utilisera les méthodes cryptographiques qui seront expliquées dans cet article.

Cryptographie dans Spring

Grâce à la cryptographie on peut encoder et décoder les messages. Cela veut dire que l'on peut les transformer en un format lisible uniquement par des personnes autorisées. Cette partie ne sera pas consacrée à l'aspect théorique de la cryptographie. Pour en savoir plus, je vous conseille de consulter l'article consacré à la cryptographie symétrique et asymétrique.

Le cryptage dans notre application servira à encoder et décoder les identifiants. Vu que dans notre base toutes les tables ont une clé primaire auto-incrementé (l'identifiant correspond donc au nombre d'éléments), on ne veut pas divulger la valeur de cette clé. Pour ce faire, on adoptera la stratégie d'obfuscation à l'aide de cryptographie.

Pour mieux comprendre le besoin, analysons le scénario d'enregistrement. L'utilisateur remplit d'abord le formulaire d'ouverture du compte. Ensuite l'application lui envoie un mail dans lequel est compris le lien de confirmation de compte. Mais pour savoir à quel utilisateur correspond chaque lien, on doit avoir une valeur unique par l'utilisateur. Maintenant on peut utiliser une nouvelle valeur (par exemple timestamp de l'inscription). Mais cette solution rajoute une donnée qui souvent sera vide. On peut prendre l'identifiant du client mais cela ne pourrait pas illustrer l'implémentation d'obfuscation. C'est pourquoi on optera pour le cryptage de l'identifiant de l'utilisateur enregistré. Ce mécanisme est expliqué ci-dessous, en commençant par la configuration XML :

    <beans:bean id="cryptograph" class="library.security.CryptographDES">
        <beans:property name="random" value="_secret-Page-Key_$" />
    </beans:bean>

... en passant par le code du cryptograph :

// Cryptograph.java
public interface Cryptograph {
    public void setRandom(String random);
    public String getRandom();
    public String encrypt(String toEncrypt);
    public String decrypt(String toDecrypt);
}

// CryptographDES.java
public class CryptographDES implements Cryptograph {
    private String random = null;
    final Logger logger = LoggerFactory.getLogger(CryptographDES.class);
    
    
    public void setRandom(String random) {
        logger.info("Set random " + random);
        this.random = random;
    }

    public String getRandom() {
        return random;
    }

    public String encrypt(String toEncrypt) {
        try {
            SecretKey key = getGeneratedKey();
            Cipher cipher = Cipher.getInstance("DES");
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] cipherText = cipher.doFinal(toEncrypt.getBytes("UTF-8"));
            return Base64.encodeBase64URLSafeString(cipherText);
        } catch (NoSuchAlgorithmException nsae) {
            logger.error("NoSuchAlgorithmException reported on encrypting "+toEncrypt, nsae);
        } catch (InvalidKeyException ike) {
            logger.error("InvalidKeyException reported on encrypting "+toEncrypt, ike);
        } catch(NoSuchPaddingException nspe) {
            logger.error("NoSuchPaddingException reported on encrypting "+toEncrypt, nspe);
        } catch(BadPaddingException bpe) {
            logger.error("BadPaddingException reported on encrypting "+toEncrypt, bpe);
        } catch(IllegalBlockSizeException ibse) {
            logger.error("IllegalBlockSizeException reported on encrypting "+toEncrypt, ibse);
        } catch(UnsupportedEncodingException ueex) {
            logger.error("UnsupportedEncodingException reported on encrypting "+toEncrypt, ueex);
        }
        return null;
    }

    public String decrypt(String toDecrypt) {
        try {
            SecretKey key = getGeneratedKey();
            Cipher cipher = Cipher.getInstance("DES");
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] cipherText = cipher.doFinal(Base64.decodeBase64(toDecrypt));
            return new String(cipherText, "UTF-8");
        } catch (NoSuchAlgorithmException nsae) {
            logger.error("NoSuchAlgorithmException reported on decrypting "+toDecrypt, nsae);
        } catch (InvalidKeyException ike) {
            logger.error("InvalidKeyException reported on decrypting "+toDecrypt, ike);
        } catch (NoSuchPaddingException nspe) {
            logger.error("NoSuchPaddingException reported on decrypting "+toDecrypt, nspe);
        } catch (BadPaddingException bpe) {
            logger.error("BadPaddingException reported on decrypting "+toDecrypt, bpe);
        } catch (IllegalBlockSizeException ibse) {
            logger.error("IllegalBlockSizeException reported on decrypting "+toDecrypt, ibse);
        } catch (UnsupportedEncodingException ueex) {
            logger.error("UnsupportedEncodingException reported on decrypting "+toDecrypt, ueex);
        }
        return null;
    }

    private SecretKey getGeneratedKey() {
        try {
            SecureRandom secran = SecureRandom.getInstance("SHA1PRNG");
            secran.setSeed(random.getBytes());
            KeyGenerator keygen = KeyGenerator.getInstance("DES");
            keygen.init(secran);
            return keygen.generateKey();
        } catch (NoSuchAlgorithmException nsae) {
            logger.error("NoSuchAlgorithmException reported on generating SecretKey", nsae);
        }
        return null;
    }
}

... et en terminant sur l'exemple d'implémentation de cryptage et de décryptage :

@Service("subscriberService")
public class SubscriberServiceImpl implements SubscriberService {
    @Autowired 
    private Cryptograph cryptograph;
    // ...
    
    public Subscriber save(Subscriber subscriber) {
        // ...
        // only for demonstration purposes : 
        tplVars.put("idCrypted", cryptograph.encrypt(Long.toString(result.getId())));
        tplVars.put("idDecrypted", cryptograph.decrypt((String)tplVars.get("idCrypted")));
        // ...
    }
    // ...
}

Le code est évident à comprendre. Il s'agit de l'implémentation de l'algorithme DES.

TODO : DES n'est plus recommandé, à remplacer par un autre algorithme : https://fr.wikipedia.org/wiki/Data_Encryption_Standard

Bartosz KONIECZNY Sécurité

Une question ? Une remarque ?

*

*

Un conseil Symfony2

Un problème filemtime()

Si pendant le développement de votre projet Symfony2 vous rencontrez un problème avec fonction filemtime, il peut s'agir d'un dysfonctionnement temporaire. Warning: filemtime() [function.filemtime]: stat failed for C:\Program Files (x86)\EasyPHP-5.3.5.0\www\gagu\src\Bun\DleBundle/Resources/views/Bundle/show.html.php in C:\Program Files (x86)\EasyPHP-5.3.5.0\www\appli\app\cache\dev\classes.php line 2064 Pour résoudre ce problème, vous pouvez être amenés à supprimer le cache du répertoire dev (si vous êtes en mode développement).