Skip to content

Category archive for: Java

Se préparer à l’arrivée de Java 9 avec docker et maven

Comme vous le savez peut-être, Java 9 se rapproche (feature complete annoncé pour le 10 décembre 2015 et general availability le 22 septembre 2016, plus d’infos sur la page du projet jdk9).

docker-logo

Il est donc temps de commencer à s’y intéresser, mais peut-être préférez-vous ne pas « polluer » votre environnement avec cette installation, alors pourquoi ne pas utiliser docker ?

Dans ce post, nous allons préparer un conteneur docker fournissant :

  • la version en cours du jdk 9
  • et maven 3.3.3

Création d’un conteneur docker pour le JDK 9

Autant ne pas réinventer la roue, je suis parti d’un Dockerfile défini dans l’article suivant :Build OpenJDK 9 using Docker

J’ai un peu refactoré l’ensemble pour regrouper les installations de package et défini un utilisateur dédié au développement pour ne pas travailler en root.

Voici le résultat :

FROM phusion/baseimage:latest

# Pre-install
RUN \
apt-get update && \
apt-get install -y \
libxt-dev zip pkg-config libX11-dev libxext-dev \
libxrender-dev libxtst-dev libasound2-dev libcups2-dev libfreetype6-dev \
mercurial ca-certificates-java build-essential wget && \
rm -rf /var/lib/apt/lists/*

# User
RUN export uid=1000 gid=1000 && \
mkdir -p /home/javadev && \
echo « javadev:x:${uid}:${gid}:JavaDev,,,:/home/javadev:/bin/bash » >> /etc/passwd && \
echo « javadev:x:${uid}: » >> /etc/group && \
echo « javadev ALL=(ALL) NOPASSWD: ALL » > /etc/sudoers.d/javadev && \
chmod 0440 /etc/sudoers.d/javadev && \
chown ${uid}:${gid} -R /home/javadev

ENV JAVA_HOME=/opt/java-bin
ENV PATH=$JAVA_HOME/bin:$PATH

# We need JDK8 to build
RUN \
wget –no-check-certificate –header « Cookie: oraclelicense=accept-securebackup-cookie » http://download.oracle.com/otn-pub/java/jdk/8u65-b17/jdk-8u65-linux-x64.tar.gz

RUN \
tar zxvf jdk-8u65-linux-x64.tar.gz -C /opt

# Let’s get JDK9
RUN \
cd /tmp && \
hg clone http://hg.openjdk.java.net/jdk9/jdk9 openjdk9 && \
cd openjdk9 && \
sh ./get_source.sh

RUN \
cd /tmp/openjdk9 && \
bash ./configure –with-cacerts-file=/etc/ssl/certs/java/cacerts –with-boot-jdk=/opt/jdk1.8.0_65

RUN \
cd /tmp/openjdk9 && \
make clean images

RUN \
cd /tmp/openjdk9 && \
cp -a build/linux-x86_64-normal-server-release/images/jdk \
/opt/openjdk9

RUN \
cd /tmp/openjdk9 && \
find /opt/openjdk9 -type f -exec chmod a+r {} + && \
find /opt/openjdk9 -type d -exec chmod a+rx {} +

ENV PATH /opt/openjdk9/bin:$PATH
ENV JAVA_HOME /opt/openjdk9

# Maven
RUN mkdir /apache-maven
RUN curl -fSL http://apache.mirrors.ovh.net/ftp.apache.org/dist/maven/maven-3/3.3.3/binaries/apache-maven-3.3.3-bin.tar.gz -o maven.tar.gz \
&& tar -xvf maven.tar.gz -C apache-maven –strip-components=1 \
&& rm maven.tar.gz*

ENV PATH /opt/openjdk9/bin:/opt/apache-maven/bin:$PATH

USER javadev
WORKDIR /home/javadev
VOLUME /home/javadev

(aussi disponible sur github : docker-jdk9-maven)

La création du conteneur est classique :

> docker build -t gcastel/openjdk9 .

à lancer dans le répertoire du Dockerfile.

Utilisation du conteneur

Tout s’est bien déroulé, pas d’erreurs ?
Il s’agit à l’heure actuelle d’un work in progress, on peut donc toujours avoir des surprises.

Si tout va bien, nous pouvons tester ce conteneur :

> docker run -it gcastel/openjdk9 java -version
openjdk version « 1.9.0-internal »
OpenJDK Runtime Environment (build 1.9.0-internal-_2015_10_20_08_31-b00)
OpenJDK 64-Bit Server VM (build 1.9.0-internal-_2015_10_20_08_31-b00, mixed mode)

Voilà, vous pouvez désormais compiler vos projets avec ce container, par exemple en montant un répertoire de travail local (localdir) comme répertoire home :

> docker run -v localdir:/home/javadev -it gcastel/openjdk9 bash

Cool ! Maintenant, occupons-nous de maven avec un petit projet utilisant une fonctionnalité de java 9 (la nouvelle API Process).

J’ai créé un petit projet de test disponible sur github.

La classe principale sera toute simple :

package fr.gcastel;

public class TestProcessAPI {
  public static void main(String[] args) throws Exception {
        System.out.println("Your pid is " + ProcessHandle.current().getPid());
  }
}

Pas besoin de dépendances particulières, on se contentera de prévenir maven qu’on est en java 9, cf. pom.xml:

  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.3.2</version>
    <configuration>
      <source>1.9</source>
      <target>1.9</target>
    </configuration>
  </plugin>

Allons-y, nous pouvons tester :

> mvn compile

Et si tout est ok :

> cd target/classes
> java fr.gcastel.TestProcessAPI
Your pid is 24

Une dernière chose : si vous souhaitez aider la communauté java, mettez en place un jenkins dédié à la compilation de vos projets avec le JDK 9 dès qu’il sera disponible en release candidate. N’hésitez pas, ça peut permettre d’éviter des problèmes tels que ceux rencontrés par lucene à la sortie de Java 7.

Références :

Java 8 sur Raspberry Pi : No soucy !

Je continue mes expériences avec la JVM sur Raspberry Pi : attaquons-nous au JDK 8.

Vous pouvez télécharger le « JDK 8 (with JavaFX) for ARM Early Access » à l’adresse suivante : http://jdk8.java.net/fxarmpreview/
Update : Un nouveau build est disponible ici : http://jdk8.java.net/download.html

Dézippez le tar.gz obtenu dans le répertoire de votre choix. Exemple :

cd lerepertoiredemonchoix
tar zxvf jdk-8-ea-b36e-linux-arm-hflt-29_nov_2012.tar.gz

Ensuite, une solution simple pour utiliser la VM est de faire pointer la variable d’environnement JAVA_HOME sur ce répertoire et d’ajouter le chemin $JAVA_HOME/bin à la variable PATH.

Mais on peut mieux faire : utiliser jenv

Jenv
Jenv va nous permettre de gérer plusieurs environnements d’exécution Java en fonction du répertoire d’où la JVM est lancée.
Pour l’installer, il suffit de suivre les instructions du site :

$ git clone https://github.com/hikage/jenv.git ~/.jenv
$ echo 'export PATH="$HOME/.jenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(jenv init -)"' >> ~/.bash_profile
$ exec $SHELL -l

Il faut maintenant paramétrer les JVMs disponibles, ex :

$ jenv add /usr/lib/jvm/java-7-openjdk
openjdk32-1.7.0.17 added
$ jenv add /path/to/jdk1.8.0
oracle32-1.8.0-ea added

Reste maintenant à définir les JDKs à utiliser en fonction du répertoire.
Tout d’abord, définissons le JDK global :

$ jenv global oracle32-1.8.0-ea

Autant utiliser le JDK 1.8 partout (si on peut ainsi envoyer des rapports de bugs et s’assurer de la compatibilité de nos applications, c’est toujours ça de pris !).

Pour les applications nécessitant java 7, pas de problèmes :

$ cd uneapplicationjava7
$ jenv local openjdk32-1.7.0.17

Bon, on teste ?
En global :

$ cd
$ java -version
java version "1.8.0-ea"
Java(TM) SE Runtime Environment (build 1.8.0-ea-b36e)
Java HotSpot(TM) Client VM (build 25.0-b04, mixed mode)

et en local :

$ cd uneapplicationjava7
$ java -version
java version "1.7.0_17"
OpenJDK Runtime Environment (IcedTea7 2.3.8) (ArchLinux build 7.u17_2.3.8-1-arm)
OpenJDK Zero VM (build 22.0-b10, mixed mode)

Et voilà, vous avez un java 8 utilisable à côté de votre java 7.

Note: Si vous obtenez une erreur « java: No such file or directory » à l’exécution de « java -version », exécutez la commande suivante en root :

ln /lib/ld-linux.so.3 /lib/ld-linux-armhf.so.3

Déstresser en codant : des annotations à glisser dans les programmes Java

En développant, on est souvent amener à reprendre ou à être confronté :) au code d’autres personnes. Et il n’est pas rare de vouloir laisser un commentaire bien senti (ou totalement hypocrite) sur la qualité de ce code.

On peut aussi développer un bout de code et vouloir laisser un petit message à ceux qui tomberont sur ce code (pour expliquer les circonstances, atténuantes ou non, qui ont mené à ce programme).

Réjouissez-vous ! c’est possible, et de manière plus élégante qu’un simple commentaire java : avec des annotations.

Des annotations pour faire des commentaires
Parmi les annotations destinées à commenter le code, on trouve :

  • @AhaMoment
  • @LegacySucks
  • @Fail
  • @OhNoYouDidnt
  • @RTFM
  • @WTF

et le génial @BossMadeMeDoIt pour reporter la responsabilité sur son chef :).

Mais il y a aussi des annotations prenant des paramètres, par exemple (exemples issus du site) :

@AnimalsHarmedDuringTheMaking(
    number = 1,
    animal = "hamster",
    disclosure = "didn't feed Fermie for 2 days to finish this on time")
public class ConstantTimePrimalityTest {

ou

@ProbablyIllegalIn(number = 17, region = STATES)
public Money extractFractionalPennies(Account account);

et

@WrittenWhile("surfing Chatroulette")
public interface You {
  void spinRightRoundBabyRightRound(Me me);
}

Des annotations pour générer des actions

En fournissant un agent à la jvm (option -javaagent:gag-agent.jar), vous pouvez aussi déclencher des comportements « particuliers » :

@Roulette(
    probability = 0.005,
    exception = PayYourContractorException,
    message = "Courtesy reminder")
public Service getRockSolidService() {

lancera une exception avec une probabilité de 5 pour mille … bon, ça reste à utiliser avec des pincettes 😉
De son côté, l’annotation @Noop désactivera une méthode.

Et il y a encore plein d’autres annotations à explorer, jetez un coup d’oeil à la JavaDoc.

[ Google Annotations Gallery ]
[ La JavaDoc ]

Exemple de stockage de mot de passe en Java : Hashage et salage, est-ce suffisant ?

Récemment, plusieurs cas de vol de mots de passe ont frappé des sociétés du net; problème : les mots de passe étaient parfois stockés en clair dans la BDD.

Bon, nous faisons tous des erreurs de jeunesse, mais je m’étonne toujours que des gros sites omettent encore de brouiller les mots de passe, et quand je dis brouiller, je ne parle pas de chiffrement (qui suppose qu’une opération de déchiffrement est possible), mais de hashage et de salage !

Je ne prétends pas être un expert en sécurité, mais je vais essayer de vous fournir une structure de base pour l’authentification par mot de passe de vos utilisateurs (pour les pressés, l’implémentation est en fin d’article :) ).

Pourquoi brouiller le mot de passe ?

Je pense que la nécessité de ne pas stocker un mot de passe en clair est évidente pour tout le monde : ainsi l’accès à votre BDD ne suffit pas à avoir les informations de connexion d’un utilisateur (et je ne parle pas juste de piratage : un administrateur/développeur malintentionné ne peut pas lire vos mots de passe via un SELECT).

Comment s’y prendre ?

En utilisant une fonction de hachage, c’est à dire (dixit wikipedia) :

« une fonction particulière qui, à partir d’une donnée fournie en entrée, calcule une empreinte servant à identifier rapidement, bien qu’incomplètement, la donnée initiale »

La caractéristique principale d’une fonction de hachage en cryptographie est qu’on ne peut pas facilement effectuer l’opération inverse (à partir de DFCD3454, retrouver ‘Renard’) : exactement ce qu’il nous faut.

Quelle fonction de hachage utiliser ?

Il existe plein de fonctions de hachage (MD5, SHA-1, SHA-256, RIPEMD-160 …), mais certaines d’entre elles ne doivent plus être utilisées :

  • MD5 ne doit plus être utilisé comme algorithme de signature (collision en quelques secondes)
  • SHA-1 est aussi vulnérable à la collision
  • SHA-256 et RIPEMD-160 sont pour l’instant OK

Ce n’est pas suffisant !

Et non …

Les pirates ont à leur disposition des dictionnaires appelés « rainbow tables« .

L’idée est très simple : Imaginons que je récupère une liste de mots de passe hashés en SHA-1. Que faire pour les forcer ?

Et si j’avais une gigantesque base de mots de passe générés automatiquement et déjà hashés en SHA-1, je pourrais comparer directement les hashs et en déduire le mot de passe ?  C’est précisément le principe des rainbow tables : des tables précalculées de hashs qui permettent de trouver très rapidement le mot de passe initial.

Il faut donc rajouter une étape à notre algorithme …

Le salage

L’idée du salage est d’ajouter au mot de passe de l’utilisateur une chaîne aléatoire (le sel) qui va compliquer l’utilisation des rainbow tables sur le hash en multipliant le nombre de hash possibles pour un même mot de passe.

Un premier algorithme

Pour gérer nos mots de passe, il suffirait donc de :

  • demander son mot de passe à l’utilisateur
  • générer un sel
  • le stocker
  • l’ajouter au mot de passe
  • hasher le sel+mot de passe
  • stocker le résultat

Et pour vérifier un mot de passe :

  • demander son mot de passe à l’utilisateur
  • lire le sel enregistré
  • l’ajouter au mot de passe
  • hasher le sel+mot de passe
  • comparer le résultat obtenu avec le hashage stocké

Bon … c’est ok sur le principe, ce type d’algorithme est utilisable et est même assez répandu, mais il reste un gros problème : les fonctions de hashage à notre disposition …

Le problème avec les fonctions de hachage

Le souci est que les fonctions de hachage disponibles sont très rapides ! Oui, vous avez bien lu : la rapidité est un défaut.

Les fonctions de hachage ont généralement comme objectif d’être rapide (notamment pour générer des signatures), mais dans le cas d’une authentification, la vitesse est notre ennemi !

Si l’algorithme est rapide, le crackage est rapide; même si on rajoute un sel, une attaque par dictionnaire peut donc suffire à craquer notre mot de passe (la puissance de calcul actuelle est telle qu’avec la bonne carte graphique vous pouvez génére 2,5 millions de SHA-1 en une seconde)

Alors, une solution ?

Oui, il y a une solution : l’algorithme bcrypt.

En vulgarisant un peu, il s’agit d’une fonction de hachage reposant sur l’algorithme Blowfish. Elle est conçue de façon à pouvoir être ralentie en augmentant le nombre de passage dans la fonction (workfactor).

Elle embarque la gestion d’un salage pour contourner les rainbow tables. Mais cette fois, le temps de crackage d’une clé dépassera la centaine de millisecondes (voire plus, en fonction des paramètres de bcrypt).

En conclusion

Le plus simple actuellement est de gérer les mots de passe avec bcrypt. En java, vous pouvez utiliser la bibliothèque jbcrypt, dont voici le bloc de dépendance maven :

<dependency>
  <groupId>org.mindrot</groupId>
  <artifactId>jbcrypt</artifactId>
  <version>0.3m</version>
</dependency>

L’utilisation de la bibliothèque est assez simple (exemple issu du site de jbcrypt) :

// Hashage d'un mot de passe
String hashed = BCrypt.hashpw(password, BCrypt.gensalt());

// Il est possible d'augmenter la complexité (et donc le temps
// de traitement) en passant le "workfactor" en paramètre
// ici : 12 La valeur par défaut est 10
String hashed = BCrypt.hashpw(password, BCrypt.gensalt(12));

// Vérification d'un mot de passe à partir du hash
if (BCrypt.checkpw(candidate, hashed))
	System.out.println("It matches");
else
	System.out.println("It does not match");

Il suffit de stocker le hash du mot de passe, et tout est géré : le sel est embarqué dedans ainsi que le « workfactor ».

Update

Pour info, voici quelques temps de hachage sur ma machine (Core i5 à  2,4 GHz) :

workfactor = 5 – temps moyen = 4ms
workfactor = 6 – temps moyen = 6ms
workfactor = 7 – temps moyen = 13ms
workfactor = 8 – temps moyen = 26ms
workfactor = 9 – temps moyen = 51ms
workfactor = 10 – temps moyen = 102ms
workfactor = 11 – temps moyen = 217ms
workfactor = 12 – temps moyen = 422ms
workfactor = 13 – temps moyen = 820ms
workfactor = 14 – temps moyen = 1650ms
workfactor = 15 – temps moyen = 3259ms

A vous de moduler votre workfactor en fonction de vos besoins de qualité de service et de sécurité.