IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

 

Développons en Java   2.30  
Copyright (C) 1999-2022 Jean-Michel DOUDOUX    (date de publication : 15/06/2022)

[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

 

26. JCA (Java Cryptography Architecture)

 

chapitre    2 6

 

Niveau : niveau 4 Supérieur 

 

Le but de l'API JCA (Java Cryptography Architecture) est de fournir des fonctionnalités cryptographiques de base à la plate-forme Java.

JCA a été conçu pour suivre plusieurs objectifs :

  • laisser l'implémentation à des fournisseurs
  • être extensible : différentes implémentations de fonctions cryptographiques utilisant différents algorithmes peuvent être proposées par différents fournisseurs
  • assurer l'indépendance et garantir l'interopérabilité entre les différentes implémentations

L'API JCA propose d'utiliser des services cryptographiques sans se préoccuper de l'implémentation des algorithmes.

JCA est un framework qui permet d'utiliser des fonctionnalités de cryptographie comprenant plusieurs services :

  • algorithmes pour Message digest
  • algorithmes pour signature digitale
  • cryptographie symétrique par lot ou par flux
  • cryptographie asymétrique
  • Password-based encryption (PBE)
  • Elliptic Curve Cryptography (ECC)
  • Algorithmes pour Key agreement
  • Générateur de clés (Key generator)
  • Message Authentication Codes (MAC)
  • Générateur de nombres pseudo-aléatoires
  • Stockage et gestion de clés et certificats

L'API JCA est incluse à partir de Java 2.

Plusieurs exemples de cette section utilisent une méthode statique pour convertir un tableau d'octets en hexadecimal.

Exemple :
package fr.jmdoudoux.dej.securite;

public class ConvertionHelper {

  public static String bytesToHex(byte[] b) {
    char hexDigit[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
      'B', 'C', 'D', 'E', 'F' };
    StringBuffer buf = new StringBuffer();
    for (int j = 0; j < b.length; j++) {
      buf.append(hexDigit[(b[j] >> 4) & 0x0f]);
      buf.append(hexDigit[b[j] & 0x0f]);
    }
    return buf.toString();
  }
}

Ce chapitre contient plusieurs sections :

 

26.1. L'architecture de JCA

JCA propose une architecture offrant l'interopérabilité qui permet d'être indépendant des implémentations des algorithmes et d'être extensible en utilisant la notion de services.

Chaque type de service est encapsulé de manière abstraite dans une classe de type cryptographic engine qui permet :

  • d'obtenir une instance pour une implémentation particulière
  • de proposer des fonctionnalités spécifiques au service

Les implémentations respectent une interface de type SPI (Service Provider Interface). A chaque classe de type cryptographic engine correspond une classe abstraite qui définit les méthodes SPI. Les fournisseurs doivent hériter de ces classes pour implémenter leurs services.

Chaque classe de type SPI possède le même nom que la classe en se terminant par spi. Chaque instance d'une classe de l'API possède une instance de sa classe de type spi correspondante. Toutes les méthodes sont final et invoquent la méthode correspondante de l'instance de type SPI.

Toutes les classes SPI sont abstraites : chaque implémentation d'un service doit fournir une sous-classe pour chaque algorithme.

 

26.2. Les classes et interfaces de JCA

Les classes et interfaces de L'API JCA sont contenues dans le package java.security et ses sous-packages.

JCA contient des engine classes qui proposent une abstraction de certains services de cryptographie sans implémentation concrète. Chaque service permet l'obtention d'ne implémentation pour un type de fonctionnalité et son utilisation. L'API JCA propose plusieurs classes pour ses services dont les principales sont :

Classe

Rôle

AlgorithmParameterGenerator

Créer des classes qui encapsulent les paramètres pour certains algorithmes

AlgorithmParameters

Encapsuler les paramètres d'une fonction cryptographique de manière opaque

AllPermission

Une permission qui regroupe toutes les autres

DigestInputStream

Calculer une valeur de hachage pour des octets lus dans un flux

DigestOutputStream

Calculer une valeur de hachage pour des octets écrits dans un flux

GuardedObject

Contrôler l'accès à d'autres objets

Identity

Deprecated

IdentityScope

Deprecated

KeyFactory

Convertir des clés transparentes en clés opaques et vice versa.

KeyPair

Encapsuler une paire de clés publique et privée

KeyPairGenerator

Générer une paire de clés publiques/privées utilisable pour un algorithme

KeyStore

Stocker, gérer et récupérer les éléments contenus dans un dépôt de clés et de certificats.

MessageDigest

Obtenir et utiliser une implémentation d'un service de type message digest permettant de calculer une valeur de hachage pour un ensemble de données

Permission

La classe abstraite qui encapsule un accès à une ressource

Provider

Servir de point d'entrée pour une implémentation de services cryptographiques proposée par un fournisseur

SecureRandom

Générer des nombres pseudo-aléatoires forts requis pour les besoins de la cryptographie

Security

Gérer les fournisseurs de services en enregistrant ou en retirant une implémentation

Signature

Obtenir et utiliser une implémentation d'un service de type signature digitale permettant de créer une signature pour des données et vérifier une signature numérique


Chaque engine class possède une méthode statique getInstance() qui est une fabrique qui permet d'obtenir une instance particulière du service dont l'algorithme est fourni en paramètre. La fabrique recherche l'instance pour le fournisseur demandé ou recherche une implémentation parmi celles enregistrées pour les différents fournisseurs.

L'API définit plusieurs interfaces dont les principales sont :

Interface 

Rôle

Key

Décrire les fonctionnalités communes à toutes les clés opaques

PrivateKey

Une clé privée

PublicKey

Une clé publique

 

26.3. Les fournisseurs d'implémentations

L'architecture de JCA permet à des fournisseurs d'enrichir la plate-forme avec leurs propres implémentations. Un fournisseur de service propose un accès à différents algorithmes de cryptographie.

JCA propose une architecture qui permet d'utiliser des implémentations fournies par des tiers. Des fournisseurs de services cryptographiques (CSP : Cryptographic Service Provider) fournissent des implémentations particulières de services reposant sur l'API JCA.

Chaque JDK est fourni avec une ou plusieurs implémentations par défaut notamment deux nommées Sun et SunJCE pour le JDK de Sun/Oracle. Il est possible d'ajouter d'autres implémentations de fournisseurs et de préciser un ordre de préférence d'utilisation.

Sun propose avec Java 1.2 plusieurs implémentations des principaux algorithmes dans le package sun.security.provider. Avec Java 1.3, Sun propose plusieurs implémentations des algorithmes RSA dans le package com.sun.rsajca.

Le JDK de Sun propose en standard une implémentation nommée SUN qui permet d'utiliser :

  • Digital Signature Algorithm (DSA)
  • MD5
  • SHA-1
  • une fabrique pour les certificats X.509
  • une implémentation d'un keystore nommée jks

Le JDK 5.0 contient un fournisseur nommé SunPKCS11

Le JDK sous Windows contient un fournisseur nommé MSCAPI qui permet d'utiliser les services natifs CryptoAPI de Microsoft.

La plate-forme Java fournit plusieurs implémentations d'algorithmes cryptographiques couramment utilisés. Ces implémentations restent cependant « basiques » : il est possible d'utiliser des implémentations plus complexes fournies par des tiers.

L'API propose un mécanisme pour permettre l'ajout de nouvelles implémentations par des fournisseurs tiers.

Pour enregistrer de manière statique un fournisseur, il faut le déclarer dans le fichier <java_home>/lib/security/java.security :

security.provider.n=nom_pleinement_qualifie_de_la_classe_principale

Pour enregistrer de manière dynamique un fournisseur, il faut utiliser la méthode addProvider() ou la méthode insertProviderAt() de la classe java.security.Security. L'utilisation de cette classe requiert des privilèges particuliers.

L'API JCA permet à des tiers de proposer des implémentations de services relatifs à la cryptographie (Cryptographic Service Provider). Différents fournisseurs peuvent fournir leurs propres implémentations de différents algorithmes pour un ou plusieurs services.

JCA permet d'obtenir la liste des fournisseurs installés et des services qu'ils proposent.

 

26.4. La classe java.security.Provider

La classe java.security.Provider sert de point d'entrée pour une implémentation de services cryptographiques proposée par un fournisseur (désignée par l'API JCA sous le nom provider). Chaque implémentation doit fournir une classe fille de la classe Provider.

La classe Provider propose plusieurs méthodes pour obtenir des informations sur l'implémentation.

Méthode

Rôle

String getName()

Obtenir le nom sensible à la casse

double getVersion()

Obtenir le numéro de version

String getInfo()

Obtenir une description


La classe Provider.Service encapsule un service de l'implémentation : la classe Provider propose plusieurs méthodes pour gérer ces services.

Méthode

Rôle

Provider.Service getService(String type, String algorithm)

Obtenir le service correspondant au type et à l'algorithme fournis en paramètres

Set<Provider.Service> getServices()

Obtenir une collection immuable des services

protected void removeService(Provider.Service s)

Retirer un service

protected void putService(Provider.Service s)

Ajouter un service


Dans chaque JVM, les fournisseurs sont enregistrés dans un ordre particulier qui permet de rechercher une implémentation particulière si aucun fournisseur n'est pas explicitement précisé.

Pour pouvoir utiliser une implémentation, il faut l'installer et la configurer.

  1. Il faut l'ajouter l'implémentation dans le classpath.
  2. Il faut ajouter le fournisseur à la liste de ceux utilisables dans le fichier java.security contenu dans le sous-répertoire lib/security du JRE.

L'enregistrement se fait dans le fichier sous la forme :

security.provider.n=NomDeLaClassePrincipale

Le n permet de préciser l'ordre dans lequel le fournisseur sera utilisé lors d'une recherche d'un algorithme. Le fournisseur le plus prioritaire possède l'ordre numéro 1.

La valeur NomDeLaClassePrincipale est le nom de la classe précisée par le fournisseur dans sa documentation. Cette classe hérite de la classe Provider.

Il est possible d'enregistrer dynamiquement un fournisseur en invoquant les méthodes addProvider() ou insertProviderAt() de la classe Security.

 

26.5. La classe java.security.Security

La classe java.security.Security permet de gérer les fournisseurs de services en enregistrant ou en retirant une implémentation. Elle permet aussi d'obtenir la liste des fournisseurs enregistrés.

Cette classe ne contient que des méthodes statiques : il n'est donc pas nécessaire de l'instancier pour l'utiliser.

Méthode

Rôle

Provider[] getProviders()

Renvoyer un tableau des fournisseurs installés dans leur ordre de préférence d'utilisations

Provider getProvider
(String providerName)

Renvoyer un fournisseur à partir de son nom. Renvoie null si le nom n'est pas trouvé

int addProvider(Provider provider)

Ajouter un fournisseur à la fin de la liste. Renvoie la position d'insertion ou -1 si l'ajout est impossible car le fournisseur est déjà installé

int insertProviderAt
(Provider provider, int position)

Insérer un fournisseur à la position précisée dans la liste. Renvoyer la position d'insertion ou -1 si l'ajout est impossible car le fournisseur est déjà installé

void removeProvider(String name)

Retirer le fournisseur de la liste dont le nom est fourni en paramètre. Si le fournisseur est retiré, la position des autres fournisseurs est décalée en conséquence

String getProperty(String key)

Obtenir la valeur d'une propriété relative à la sécurité

void setProperty(String key, String data)

Modifier la valeur d'une propriété relative à la sécurité


Pour modifier la position d'un fournisseur, il faut le retirer puis l'ajouter à nouveau.

L'exemple ci-dessous affiche la liste de tous les fournisseurs enregistrés et utilisables dans la JVM.

Exemple :
package fr.jmdoudoux.dej.securite;

import java.security.Provider;
import java.security.Security;

public class TestProviders {

  public static void main(String[] args) {
    Provider[] providers = Security.getProviders();

    for (Provider provider : providers) {
      System.out.println("Provider : " + provider.getName() + " v"
          + provider.getVersion());
    }
  }
}

L'exemple ci-dessous, affiche tous les services du fournisseur SunJCE fourni en standard avec le JDK de Sun.

Exemple :
package fr.jmdoudoux.dej.securite;

import java.security.Provider;
import java.security.Provider.Service;
import java.security.Security;

public class TestProvider {

  public static void main(String[] args) {
    Provider provider = Security.getProvider("SunJCE");

    System.out.println("Services du provider " + provider.getName());
    for (Service service : provider.getServices()) {
      System.out.println("\t" + service.getType() + " "
          + service.getAlgorithm());
    }
  }
}
Résultat :
Services du provider SunJCE
        Cipher RSA
        Cipher DES
        Cipher DESede
        Cipher DESedeWrap
        Cipher PBEWithMD5AndDES
        Cipher PBEWithMD5AndTripleDES
        Cipher PBEWithSHA1AndRC2_40
        Cipher PBEWithSHA1AndDESede
        Cipher Blowfish
        Cipher AES
        Cipher AESWrap
        Cipher RC2
        Cipher ARCFOUR
        KeyGenerator DES
        KeyGenerator DESede
...

La méthode getAlgorithms() attend en paramètre le nom d'un type de service (Signature, MessageDigest, Cipher, Mac, KeyStore, ...) et renvoie un ensemble de noms d'algorithmes disponibles auprès des différents providers enregistrés.

Exemple :
package fr.jmdoudoux.dej.securite;

import java.security.Security;

public class TestProvider {

  public static void main(String[] args) {
    for (String algo : Security.getAlgorithms("Cipher")) {
      System.out.println(algo);
    }
  }
}
Résultat :
BLOWFISH
ARCFOUR
PBEWITHMD5ANDDES
RC2
RSA
PBEWITHMD5ANDTRIPLEDES
PBEWITHSHA1ANDDESEDE
DESEDE
AESWRAP
AES
DES
DESEDEWRAP
RSA/ECB/PKCS1PADDING
PBEWITHSHA1ANDRC2_40

La classe Security encapsule une liste de propriétés relatives à la sécurité globale de la JVM. Les valeurs de ces propriétés peuvent être obtenues en utilisant la méthode getProperty() et modifiées avec la méthode setProperty().

La classe Security ne peut être utilisée que dans certaines circonstances :

  • une application locale qui ne s'exécute pas avec un Security Manager
  • une applet ou une application qui a la permission

 

26.6. La classe java.security.MessageDigest

La classe MessageDigest permet d'obtenir et d'utiliser une implémentation d'un service de type message digest tel que MD5 ou SHA-1 qui calcule une valeur de hachage de taille fixe à partir d'une quantité de données fournies sous la forme d'un tableau d'octets.

La valeur de hachage produite permet d'identifier de manière quasi unique les données avec lesquelles elle a été calculée.

La méthode statique getInstance() permet de demander l'implémentation d'un algorithme particulier : plusieurs algorithmes sont utilisables (MD5 128bits, SHA-1 160bits, ...). Il existe plusieurs surcharges de la méthode getInstance(). La plus simple attend uniquement le nom de l'algorithme : ce nom n'est pas sensible à la casse.

Exemple :
MessageDigest.getInstance("sha-1");
MessageDigest.getInstance("SHA-1");

Une autre surcharge permet de préciser le nom de l'algorithme et le nom du fournisseur. Une troisième surcharge permet de préciser le nom de l'algorithme et le fournisseur sous la forme d'une instance de type Provider.

  • static MessageDigest getInstance(String algorithm, String provider)
  • static MessageDigest getInstance(String algorithm, Provider provider)

Les surcharges de la méthode update() permettent d'ajouter des données à traiter en une seule ou plusieurs fois.

  • void update(byte input)
  • void update(byte[] input)
  • void update(byte[] input, int offset, int len)

Une fois que toutes les données ont été ajoutées, il faut invoquer une des surcharges de la méthode digest() pour calculer la valeur de hachage pour les données fournies.

  • byte[] digest()
  • byte[] digest(byte[] input)
  • int digest(byte[] buf, int offset, int len)

Les deux premières surcharges renvoient la valeur de hachage. La seconde surcharge invoque la méthode update() avec les données fournies en paramètre avant de calculer la valeur de hachage. La troisième surcharge copie la valeur de hachage dans le tampon fourni en paramètre et renvoie la taille des données copiées dans le tampon. Si le tampon est trop petit alors une exception de type DigestException est levée.

Exemple :
  public static byte[] calculerValeurDeHachage(String algorithme,
      String monMessage) {
    byte[] digest = null;
    try {
      MessageDigest sha = MessageDigest.getInstance(algorithme);
      sha.update(monMessage.getBytes());
      digest = sha.digest();
      System.out.println("algorithme : " + algorithme);
      System.out.println(bytesToHex(digest));
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    }
    return digest;
  }

Après le calcul de la valeur de hachage, l'instance de type MessageDigest est automatiquement réinitialisée pour être de nouveau capable de calculer la valeur de hachage d'un nouveau message.

La méthode reset() permet de réinitialiser l'objet pour calculer une valeur de hachage pour d'autres données.

Exemple :
package fr.jmdoudoux.dej.securite;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class TestMessageDigest {
  public static void main(String[] args) {
    String monMessage = "Mon message";

    calculerValeurDeHachage("MD2", monMessage);
    calculerValeurDeHachage("MD5", monMessage);
    calculerValeurDeHachage("SHA-1", monMessage);
    calculerValeurDeHachage("SHA-256", monMessage);
    calculerValeurDeHachage("SHA-384", monMessage);
    calculerValeurDeHachage("SHA-512", monMessage);

  }

  public static byte[] calculerValeurDeHachage(String algorithme,
      String monMessage) {
    byte[] digest = null;
    try {
      MessageDigest sha = MessageDigest.getInstance(algorithme);
      digest = sha.digest(monMessage.getBytes());
      System.out.println("algorithme : " + algorithme);
      System.out.println(bytesToHex(digest));
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    }
    return digest;
  }

  public static String bytesToHex(byte[] b) {
    char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
        'B', 'C', 'D', 'E', 'F' };
    StringBuffer buffer = new StringBuffer();
    for (int j = 0; j < b.length; j++) {
      buffer.append(hexDigits[(b[j] >> 4) & 0x0f]);
      buffer.append(hexDigits[b[j] & 0x0f]);
    }
    return buffer.toString();
  }

}
Résultat :
algorithme : MD2
78B9CEFD91776D6518008B8A9A92AC6F
algorithme : MD5
F547EA699E14B43ABE0C43FA7B809705
algorithme : SHA-1
36BF135AA507ABC0613117EAFFE5280250A13BBF
algorithme : SHA-256
E648CBA96738CF7F6DD84FBB92223063D7DFEE2B5678AE18BA9D006E82337648
algorithme : SHA-384
555A7AFAA68B5A9EF632F69287725ABAF58A3AA944A270E977F52D9DCFAC9EA1
CD4A3C5DFDB09AF3A8B6BF75883E0EAB algorithme : SHA-512 D279F5BD5AA43D74080D88C92621315489A3071A7D423307E33D6AFE6BBF2CAD
9B34400ED822DE346269AE3410FAB055D16E89CB4785D93997EAF2B8100CC02B

 

26.7. Les classes DigestInputStream et DigestOuputStream

Les classes java.security.DigestInputStream et java.security.DigestOutputStream permettent de calculer une valeur de hachage pour des octets contenus dans un flux.

Ces deux classes héritent de la classe FilterOuputStream : les octets lus du flux ou écrits dans le flux sont ajoutés aux données à traiter par un objet de type MessageDigest associé à l'instance.

La classe DigestInputStream possède un seul constructeur qui attend en paramètre une instance de type InputStream et une instance de type MessageDigest.

La lecture de données du flux doit se faire en utilisant une des surcharges de la méthode read() de la classe DigestInputStream.

Exemple :
package fr.jmdoudoux.dej.securite;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class TestDigestInputStream {

  public static void main(String[] args) {
    InputStream is;
    DigestInputStream dis = null;
    try {
      is = new BufferedInputStream(new FileInputStream("monfichier.txt"));
      MessageDigest md = MessageDigest.getInstance("SHA-1");

      dis = new DigestInputStream(is, md);

      byte[] buffer = new byte[64];
      while (dis.read(buffer) != -1)
        ;

    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (dis != null) {
        try {
          dis.close();
          byte[] hash = dis.getMessageDigest().digest();
          System.out.println(ConvertionHelper.bytesToHex(hash));
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }
}

La classe DigestOutputStream possède un seul constructeur qui attend en paramètre une instance de type OutputStream et une instance de type MessageDigest.

L'écriture des données dans le flux doit se faire en utilisant une des surcharges de la méthode write() de la classe DigestOutputStream.

Exemple :
package fr.jmdoudoux.dej.securite;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class TestDigestOutputStream {

  public static void main(String[] args) {

    byte[] donnees = "Hello World".getBytes();

    FileOutputStream fop = null;
    File file;
    DigestOutputStream dos = null;

    try {
      MessageDigest md = MessageDigest.getInstance("SHA-512");

      file = new File("fichier.txt");
      fop = new FileOutputStream(file);

      dos = new DigestOutputStream(fop, md);
      dos.write(donnees);
    } catch (IOException e) {
      e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } finally {
      try {
        if (dos != null) {
          dos.close();
          byte[] hash = dos.getMessageDigest().digest();
          System.out.println(ConvertionHelper.bytesToHex(hash));
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}

 

26.8. La classe java.security.Signature

La classe java.security.Signature permet d'obtenir et d'utiliser une implémentation d'un service de type signature digitale tel que DSA ou RSA avec SHA1 ou MD5.

Elle permet de signer des données et de vérifier des données signées.

Un objet de type Signature possède un état qui peut prendre trois valeurs :

  • UNINITIALIZED
  • SIGN
  • VERIFY

Pour créer une signature digitale, il faut instancier un objet de type Signature et l'initialiser avec une clé privée. Les données sont fournies à l'objet Signature pour créer la signature.

Pour vérifier la signature, il faut créer un objet de type Signature et l'initialiser avec la clé publique. Les données et la signature sont fournies à l'objet Signature pour vérification.

Pour obtenir une instance de type Signature avec un algorithme spécifique, il faut invoquer la méthode statique getInstance() en lui précisant le nom de l'algorithme qui n'est pas sensible à la casse. Deux surcharges de la méthode getInstance() permettent aussi de préciser explicitement le fournisseur par son nom ou par une instance de type Provider :

  • public static Signature getInstance(String algorithm)
  • public static Signature getInstance(String algorithm, String provider)
  • public static Signature getInstance(String algorithm, Provider provider)

Chaque implémentation de la plate-forme Java doit fournir au minimum l'implémentation de trois algorithmes : SHA1withDSA, SHA1withRSA et SHA256withRSA.

A sa création, une instance de type Signature est initialisée à l'état UNINITIALIZED.

Pour utiliser un objet de type Signature, il faut l'initialiser dans un mode de fonctionnement.

La classe Signature possède deux méthodes initSign() et initVerify() qui permettent respectivement de passer l'état à SIGN ou VERIFY selon l'utilisation qui doit être faite.

La méthode initSign() attend en paramètre un objet de type PrivateKey qui encapsule la clé privée pour signer des données.

  • void initSign(PrivateKey privateKey)

La méthode initVerify() possède deux surcharges qui attendent respectivement en paramètre :

  • un objet de type PublicKey qui encapsule la clé publique : void initVerify(PublicKey publicKey)
  • un objet de type Certificate qui encapsule le certificat : void initVerify(Certificate certificate)

Pour signer des données, il faut utiliser une instance de type Signature dans l'état SIGN. Les données doivent être fournies en utilisant une des surcharges de la méthode update() :

  • final void update(byte b)
  • final void update(byte[] data)
  • final void update(byte[] data, int off, int len)

La méthode update() doit être invoquée autant de fois que nécessaire pour fournir toutes les données qui doivent être signées.

Pour générer la signature des données avec la clé, il faut invoquer une des surcharges de la méthode sign() :

  • final byte[] sign() : renvoie la signature sous la forme d'un tableau d'octets
  • final int sign(byte[] outbuf, int offset, int len) : stocke la signature dans le tableau d'octets fourni en paramètre. Elle renvoie le nombre d'octets insérés dans le tableau à partir de l'offset précisé

A la fin de l'invocation de la méthode sign(), l'instance de type Signature est réinitialisée à son état suite à l'invocation de la méthode initSign(). Elle peut alors être réutilisée pour signer une autre quantité de données avec la clé. Pour préciser une autre clé, il faut de nouveau invoquer la méthode initSign().

Pour vérifier des données signées, il faut utiliser une instance de type Signature dans l'état VERIFY. Les données doivent être fournies en utilisant une des surcharges de la méthode update() :

  • final void update(byte b)
  • final void update(byte[] data)
  • final void update(byte[] data, int off, int len)

La méthode update() doit être invoquées autant de fois que nécessaire pour fournir toutes les données qui doivent être signées.

Pour vérifier les données avec la signature, il faut invoquer une des surcharge de la méthode verify() qui renvoie un booléen indiquant si oui ou non la signature encodée est la signature authentique des données fournies grâce à la méthode update() :

  • final boolean verify(byte[] signature) : elle attend en paramètre un tableau d'octets qui contient la signature
  • final boolean verify(byte[] signature, int offset, int length)

A la fin de l'invocation de la méthode verify(), l'instance de type Signature est réinitialisée à son état suite à l'invocation de la méthode initVerify(). Elle peut alors être réutilisée pour vérifier une autre signature pour d'autres données. Pour préciser une autre clé, il faut de nouveau invoquer la méthode initVerify().

Exemple :
package fr.jmdoudoux.dej.securite;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Signature;

public class TestSignature {

  public static void main(String[] args) throws Exception {
    byte[] message = "Hello world".getBytes();

    KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
    keyPairGen.initialize(1024, new SecureRandom());
    KeyPair keyPair = keyPairGen.generateKeyPair();

    Signature signature = Signature.getInstance("SHA1withRSA");

    signature.initSign(keyPair.getPrivate(), new SecureRandom());
    signature.update(message);
    byte[] signatureBytes = signature.sign();
    System.out.println(ConvertionHelper.bytesToHex(signatureBytes));

    signature.initVerify(keyPair.getPublic());
    signature.update(message);
    System.out.println(signature.verify(signatureBytes));
  }
}
Résultat :
63E72D3E8271401DC4E2A0930EA6B3E14AB1FF7BB88DD61EAF57BDDDFB7B64C0
33AB5F67CE780B724CC0B52D153BA32663B456E49B980BA4B9BE521423D9CAED
3EC3C2C00600A685FEB0C9C30FCFF2C27A0A3C102D85E3F5CC758497763290E9
4C741BDAED3576A529698F7F05A8119C54256931B5F9A8658FFE9619574F112F
true

 

26.9. La classe java.security.KeyStore

La classe KeyStore permet de stocker, gérer et récupérer les éléments contenus dans un dépôt de clés.

La classe KeyStore permet d'accéder et de modifier les deux types d'éléments que peut contenir un dépôt : des clés et des certificats. Chaque élément contenu dans un keyStore est identifié par un alias unique.

L'implémentation est libre de choisir la solution pour rendre persistantes les données qu'elle contient et pour sécuriser l'accès à ces données.

Pour obtenir une instance de type KeyStore, il faut invoquer la méthode getInstance() qui est une fabrique attendant une chaîne de caractères qui précise le type de KeyStore à obtenir. Deux autres surcharges attendent en paramètre un objet de type Provider ou une chaîne de caractères qui permet de préciser le fournisseur de l'implémentation.

  • static KeyStore getInstance(String type)
  • static KeyStore getInstance(String type, String provider)
  • static KeyStore getInstance(String type, Provider provider)

Le type de dépôt n'est pas sensible à la casse.

La classe KeyStore possède plusieurs méthodes :

Méthode

Rôle

final void load(InputStream stream, char[] password)

Pour charger les données du keystore, il faut invoquer la méthode load()

Le second paramètre est un mot de passe qui, s'il est fourni, permet de vérifier l'intégrité des données.

Pour créer un keystore vide, il faut invoquer la méthode load() avec la valeur null comme premier paramètre.

Enumeration aliases()

Obtenir une énumération de tous les alias associés à un élément du keystore

boolean isKeyEntry(String alias)

Renvoyer un booléen qui précise si l'élément associé à l'alias fourni en paramètre est une clé

boolean isCertificateEntry(String alias)

Renvoyer un booléen qui précise si l'élément associé à l'alias fourni en paramètre est un certificat

void setCertificateEntry(String alias, Certificate cert)

Ajouter un certificat dans le keystore et lui associer un alias si l'entrée n'existe pas dans le keystore ou modifier le certificat s'il existe

final void setKeyEntry(String alias, Key key, char[] password, Certificate[] chain)

final void setKeyEntry(String alias, byte[] key, Certificate[] chain)

Ajouter une clé dans le keystore et lui associer un alias si l'entrée n'existe pas dans le keystore ou modifier le certificat s'il existe

final void deleteEntry(String alias)

Supprimer l'entrée dans le dépôt de clés pour l'alias en fourni en paramètre

final Key getKey(String alias, char[] password)

Obtenir la clé correspondant à l'alias fourni en paramètre. Le mot de passe fourni en paramètre doit être celui associé à la protection de la clé.

final Certificate getCertificate(String alias)

final Certificate[] getCertificateChain(String alias)

Obtenir le certificat ou les certificats correspondant à l'alias fourni en paramètre

final String getCertificateAlias(Certificate cert)

Obtenir l'alias du premier élément du dépôt associé au certificat fourni en paramètre

final void store(OutputStream stream, char[] password)

Sauvegarder le keystore. Le mot de passe est utilisé pour calculer un checksum des données et vérifier ultérieurement son intégrité

final static String getDefaultType()

Obtenir le type par défaut de dépôt de clés à utiliser : cette valeur est définie par la propriété keystore.type dans le fichier de sécurité. Si cette valeur n'est pas définie explicitement, elle est par défaut à «JKS»


Pour obtenir une instance de type KeyStore, il faut utiliser la méthode statique getInstance() qui est une fabrique.

Il faut ensuite charger en mémoire le contenu du dépôt de clés en utilisant la méthode load(). Le paramètre optionnel password est utilisé pour vérifier l'intégrité des données du dépôt : si le paramètre n'est pas fourni alors la vérification n'est pas effectuée.

Exemple : afficher un certificat du dépôt

Exemple :
package fr.jmdoudoux.dej.securite;

import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;

public class TestKeyStoreExportCert {

  public static void main(String[] args) {

    char[] mdp = { '1', '2', '3', '4', '5', '6' };

    FileInputStream is;
    try {
      is = new FileInputStream("c:/java/jmPrivateKey.store");
      KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
      keystore.load(is, mdp);

      String alias = "jmTrustKey";
      Certificate cert = keystore.getCertificate(alias);

      System.out.println(cert);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Exemple : obtenir une paire de clés du dépôt

Exemple :
package fr.jmdoudoux.dej.securite;

import java.io.FileInputStream;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;

public class TestKeyStoreObtenirKeyPair {

  public static void main(String[] args) {

    char[] mdpDepot = { '1', '2', '3', '4', '5', '6' };
    char[] mdpCle = { 'a', 'b', 'c', 'd', 'e', 'f' };

    FileInputStream is;
    try {
      is = new FileInputStream("c:/java/jmPrivateKey.store");
      KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
      keystore.load(is, mdpDepot);

      KeyPair cles = getPrivateKey(keystore, "jmTrustKey", mdpCle);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public static KeyPair getPrivateKey(KeyStore keystore, String alias, char[] password)
      throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException {
    KeyPair resultat = null;

    Key clePrivee = keystore.getKey(alias, password);
    
    if (clePrivee instanceof PrivateKey) {
      Certificate cert = keystore.getCertificate(alias);
      PublicKey clePublique = cert.getPublicKey();
      resultat =  new KeyPair(clePublique, (PrivateKey) clePrivee);
    }
    return resultat;
  }
}

Plusieurs outils du JDK utilisent la classe KeyStore : keytool, jarsigner et Policy Tool.

L'implémentation de la classe KeyStore est dépendante du fournisseur : le JDK de Sun/Oracle propose une implémentation nommée SUN qui stocke le dépôt dans un fichier. Le type de ce dépôt est JKS.

L'implémentation nommée SunJCE propose le type de dépôt JCEKS qui met en oeuvre un chiffrement des mots de passes utilisant un algorithme de type Triple DES.

 

26.10. Les interfaces de type java.security.Key

L'interface Key décrit les fonctionnalités communes à toutes les clés opaques. Une clé opaque ne permet pas un accès direct à sa valeur et possède plusieurs caractéristiques :

  • un algorithme (par exemple : DSA, RSA, MD5withRSA, SHA1withRSA, ...)
  • une forme encodée qui permet une utilisation hors de la JVM par exemple pour transmettre la clé à un tiers
  • le format qui a encodé la clé

Elle définit plusieurs méthodes pour les obtenir :

Méthode

Rôle

String getAlgorithm()

Retourner le nom de l'algorithme pour lequel la clé est adaptée

byte[] getEncoded()

Retourner la forme encodée de la clé

String getFormat()

Retourner le format utilisé pour encoder la clé


De nombreuses interfaces héritent de l'interface Key.

JCA

JCE

java.security.PrivateKey
java.security.PublicKey
java.security.interfaces.DSAPrivateKey
java.security.interfaces.DSAPublicKey
java.security.interfaces.ECPrivateKey
java.security.interfaces.ECPublicKey
java.security.interfaces.RSAMultiPrimePrivateCrtKey
java.security.interfaces.RSAPrivateCrtKey
java.security.interfaces.RSAPrivateKey
java.security.interfaces.RSAPublicKey

javax.crypto.interfaces.DHPrivateKey
javax.crypto.interfaces.DHPublicKey
javax.crypto.interfaces.PBEKey
javax.crypto.SecretKey


Les interfaces java.security.PublicKey et java.security.PrivateKey héritent de l'interface Key. Elles sont uniquement des marqueurs : elles ne définissent aucune méthode supplémentaire. Les interfaces pour les clés publiques ou privées de certains algorithmes héritent de ces interfaces.

 

26.11. La classe java.security.KeyPair

La classe KeyPair encapsule une paire de clés : une clé publique et une clé privée.

Elle possède deux méthodes pour obtenir les clés :

  • PrivateKey getPrivate() : obtenir la clé privée
  • PublicKey getPublic() : obtenir la clé publique

Les objets de type Key (clé opaque) et KeySpec (clé transparente) sont deux représentations des données d'une clé. Les algorithmes de cryptage utilisent une clé opaque mais les clés doivent être transformées dans un format plus portable pour être transmises ou stockées.

Il est possible d'accéder à chacun des éléments d'une clé transparente en utilisant des getters : ces propriétés dépendent de l'implémentation de la clé. Par exemple, classe DSAPrivateKeySpec permet un accès aux paramètres utilisés pour le calcul de la clé : nombre premier p, nombre premier q, la base g et à la clé privée x.

La représentation opaque d'une clé est définit par l'interface Key qui ne contient que trois méthodes : getAlgorithm(), getFormat() et getEncoded().

 

26.12. La classe java.security.KeyPairGenerator

La classe KeyPairGenerator permet de générer une paire de clés : une publique et une privée.

La génération d'une paire de clés peut se faire de deux manières :

  • de manière indépendante de tout algorithme
  • de manière spécifique à un algorithme : dans ce cas, la seule différence est la nécessité d'initialiser l'objet.

Pour obtenir une instance de type KeyPairGenerator, il faut invoquer la méthode getInstance() qui est une fabrique qui attend en paramètre le nom de l'algorithme. Deux autres surcharges, permettent aussi de préciser quel est le fournisseur de l'algorithme à utiliser.

  • static KeyPairGenerator getInstance(String algorithm)
  • static KeyPairGenerator getInstance(String algorithm, String provider)
  • static KeyPairGenerator getInstance(String algorithm, Provider provider)

La classe KeyPairGenerator permet de créer une paire de clés publique/privée utilisable pour un algorithme particulier.

Un objet de type KeyPairGenerator doit être initialisé avant de pouvoir générer des clés. Cette initialisation concerne la taille de la clé et une solution pour générer des nombres aléatoires.

  • void initialize(int keysize, SecureRandom random)

Une surcharge de la méthode initialize() attend uniquement en paramètre la taille de la clé et utilise un objet de type SecureRandom par défaut.

Pour certains algorithmes, cette initialisation peut en plus être particulière. Deux surcharges de la méthode initialize() attendent en paramètre un objet de type AlgorithmParameterSpec

  • void initialize(AlgorithmParameterSpec params, SecureRandom random)
  • void initialize(AlgorithmParameterSpec params)

La méthode generateKeyPair() renvoie une instance de type KeyPair.

Plusieurs appels à la méthode generateKeyPair() permet d'obtenir plusieurs instances.

Exemple :
package fr.jmdoudoux.dej.securite;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;

public class TestKeyPairGenerator {
  public static void main(String[] argv) throws Exception {
    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
    keyGen.initialize(1024);
    KeyPair keypair = keyGen.genKeyPair();
    PrivateKey privateKey = keypair.getPrivate();
    System.out.println(privateKey);
    PublicKey publicKey = keypair.getPublic();
    System.out.println(publicKey);
  }
}
Résultat :
Sun DSA Private Key 
parameters:
    p:
    fd7f5381 1d751229 52df4a9c 2eece4e7 f611b752 3cef4400 c31e3f80 b6512669
    455d4022 51fb593d 8d58fabf c5f5ba30 f6cb9b55 6cd7813b 801d346f f26660b7
    6b9950a5 a49f9fe8 047b1022 c24fbba9 d7feb7c6 1bf83b57 e7c6a8a6 150f04fb
    83f6d3c5 1ec30235 54135a16 9132f675 f3ae2b61 d72aeff2 2203199d d14801c7
    q:
    9760508f 15230bcc b292b982 a2eb840b f0581cf5
    g:
    f7e1a085 d69b3dde cbbcab5c 36b857b9 7994afbb fa3aea82 f9574c0b 3d078267
    5159578e bad4594f e6710710 8180b449 167123e8 4c281613 b7cf0932 8cc8a6e1
    3c167a8b 547c8d28 e0a3ae1e 2bb3a675 916ea37f 0bfa2135 62f1fb62 7a01243b
    cca4f1be a8519089 a883dfe1 5ae59f06 928b665e 807b5525 64014c3b fecf492a

x:     4e775468 567ff544 85411a5e ba8b839b ec56beb4

Sun DSA Public Key
    Parameters:
    p:
    fd7f5381 1d751229 52df4a9c 2eece4e7 f611b752 3cef4400 c31e3f80 b6512669
    455d4022 51fb593d 8d58fabf c5f5ba30 f6cb9b55 6cd7813b 801d346f f26660b7
    6b9950a5 a49f9fe8 047b1022 c24fbba9 d7feb7c6 1bf83b57 e7c6a8a6 150f04fb
    83f6d3c5 1ec30235 54135a16 9132f675 f3ae2b61 d72aeff2 2203199d d14801c7
    q:
    9760508f 15230bcc b292b982 a2eb840b f0581cf5
    g:
    f7e1a085 d69b3dde cbbcab5c 36b857b9 7994afbb fa3aea82 f9574c0b 3d078267
    5159578e bad4594f e6710710 8180b449 167123e8 4c281613 b7cf0932 8cc8a6e1
    3c167a8b 547c8d28 e0a3ae1e 2bb3a675 916ea37f 0bfa2135 62f1fb62 7a01243b
    cca4f1be a8519089 a883dfe1 5ae59f06 928b665e 807b5525 64014c3b fecf492a

  y:
    2e5efc6a 8a8e046e c06cfb5f dc3d1acb 83ff9209 1b189208 0378eaef ac919241
    96242890 c285e26d 91a0386d 1b30e856 aaf6e3bc 90184f0e 76ea8551 d6333faa
    349a480e 13baa599 0c6e571b f649251f 099726dc a9b1412c ebf57990 5ccee057
    9fa2b515 0585afa1 19365c58 dd741347 889c0e25 0f112476 d49dc7af 70b61867
Exemple :
package fr.jmdoudoux.dej.securite;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;

public class TestKeyPairGenerator2 {

  public static void main(String[] args) throws Exception {
    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");

    byte[] userSeed = new byte[256];

    SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
    random.setSeed(userSeed);
    keyGen.initialize(1024, random);
    KeyPair keypair = keyGen.genKeyPair();
    PrivateKey privateKey = keypair.getPrivate();
    System.out.println(privateKey);
    PublicKey publicKey = keypair.getPublic();
    System.out.println(publicKey);
  }

}

 

26.13. La classe java.security.KeyFactory

La classe KeyFactory permet de convertir des clés transparentes en clés opaques et vice versa.

Pour obtenir une instance de type KeyFactory, il faut invoquer sa méthode getInstance() en lui fournissant le nom de l'algorithme à utiliser. Ce nom n'est pas sensible à la casse.

La méthode getInstance() possède deux autres surcharges qui permettent de préciser le nom du fournisseur sous la forme d'une chaîne de caractères ou d'une instance de type Provider.

La méthode generatePublic() attend en paramètre un objet de type KeySpec et renvoie un objet de type PublicKey.

  • PublicKey generatePublic(KeySpec keySpec)

Elle permet de transformer une clé publique transparente en une clé publique opaque.

Exemple :
package fr.jmdoudoux.dej.securite;

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;

public class TestKeyFactory {

  public static void main(String[] args) {
    try {

      // Generation d'une paire de cles opaques pour RSA

      KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
      keyGen.initialize(1024);
      KeyPair keypair = keyGen.genKeyPair();
      PublicKey publicKey = keypair.getPublic();
      System.out.println(ConvertionHelper.bytesToHex(publicKey.getEncoded()));

      // Convertion de la cle opaque en cle transparente

      KeyFactory keyFactory = KeyFactory.getInstance("RSA");
      byte[] publicKeyBytes = publicKey.getEncoded();
      EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);

      // Reconvertion de la cle transparente en cle opaque

      PublicKey publicKey2 = keyFactory.generatePublic(publicKeySpec);
      System.out.println(ConvertionHelper.bytesToHex(publicKey2.getEncoded()));
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } catch (InvalidKeySpecException e) {
      e.printStackTrace();
    }
  }
}

La méthode generatePrivate() attend en paramètre un objet de type KeySpec et renvoie un objet de type PrivateKey.

  • PrivateKey generatePrivate(KeySpec keySpec)

Elle permet de transformer une clé privée transparente en une clé privée opaque.

Exemple :
package fr.jmdoudoux.dej.securite;

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;

public class TestKeyFactory {

  public static void main(String[] args) {
    try {
      // Generation d'une paire de cles opaques pour RSA

      KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
      keyGen.initialize(1024);
      KeyPair keypair = keyGen.genKeyPair();
      PrivateKey privateKey = keypair.getPrivate();
      System.out.println(ConvertionHelper.bytesToHex(privateKey.getEncoded()));

      // Convertion de la cle opaque en cle transparente

      KeyFactory keyFactory = KeyFactory.getInstance("RSA");
      byte[] privateKeyBytes = privateKey.getEncoded();
      EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);

      // Reconvertion de la cle transparente en cle opaque

      PrivateKey privateKey2 = keyFactory.generatePrivate(privateKeySpec);
      System.out.println(ConvertionHelper.bytesToHex(privateKey2.getEncoded()));

    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } catch (InvalidKeySpecException e) {
      e.printStackTrace();
    }
  }
}

La méthode getKeySpec() attend en paramètres deux objets de type Key pour l'un et KeySpec pour l'autre. Elle renvoie un objet de type KeySpec.

  • KeySpec getKeySpec(Key key, Class keySpec)

Elle permet de transformer une clé opaque en une clé transparente.

Exemple :
package fr.jmdoudoux.dej.securite;

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;

public class TestKeyFactory {

  public static void main(String[] args) {

    try {
      // Generation d'une paire de cles opaques pour RSA

      KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
      keyGen.initialize(1024);
      KeyPair keypair = keyGen.generateKeyPair();
      PublicKey publicKey = keypair.getPublic();

      // Convertion de la cle opaque en cle transparente

      KeyFactory kfactory = KeyFactory.getInstance("RSA");
      RSAPublicKeySpec keySpec = kfactory.getKeySpec(publicKey,
          RSAPublicKeySpec.class);
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } catch (InvalidKeySpecException e) {
      e.printStackTrace();
    }
  }
}

 

26.14. La classe java.security.SecureRandom

La classe de base du JDK pour générer un nombre aléatoire est la classe java.util.Random qui utilise une graine (seed en anglais ) de 48 bits. Si le seed n'est pas fourni explicitement, alors une valeur basée sur l'heure courante est utilisée pour l'initialiser.

La génération de nombres aléatoires est très importante dans la cryptographie moderne : elle est fréquemment utilisée pour générer les clés ou les paramètres de certains algorithmes.

La classe java.security.SecureRandom est une implémentation d'un PRNG (Pseudo Random Number Generator) fort requis pour les besoins de la cryptographie. Elle diffère de la classe Random en produisant des nombres aléatoires utilisables avec des fonctions cryptographiques. Les implémentations de la classe SecureRandom tentent de rendre le plus aléatoire possible l'état interne du générateur.

Comme pour toutes les classes de type engine de JCA, pour obtenir une instance de type SecureRandom, il faut invoquer la méthode statique getInstance()

Pour créer une instance de type SecuredRandom, il est préférable d'utiliser une des surcharges de la méthode getInstance() qui est une fabrique :

  • public static SecureRandom getInstance(String algorithm)
  • public static SecureRandom getInstance(String algorithm, String provider)
  • public static SecureRandom getInstance(String algorithm, Provider provider)

Il est aussi possible de préciser le fournisseur de l'implémentation à utiliser soit par son nom sous la forme d'une chaîne de caractères ou sous la forme d'une instance de type Provider.

L'implémentation du JCA proposée par Sun/Oracle propose deux implémentations d'algorithmes pour la génération de nombres aléatoires :

  • SHA1PRNG par le provider SUN
  • Windows-PRNG par le provider SunMSCAPI

Pour des raisons de compatibilité, il est possible d'utiliser un des constructeurs de la classe SecureRandom pour obtenir une instance :

  • public SecureRandom()
  • public SecureRandom(byte[] seed)

La méthode setSeed() permet de fournir une valeur qui sera utilisée pour renforcer le caractère aléatoire des valeurs générées en assurant la dispersion de ces valeurs.

  • synchronized public void setSeed(byte[] seed)
  • public void setSeed(long seed)

La méthode setSeed() permet de préciser l'état initial du générateur.

Il est aussi possible de ré-invoquer la méthode setSeed() sur une instance de SecureRandom qui est utilisée plusieurs fois : dans ce cas, les données fournies sont ajoutées aux données existantes au lieu de les remplacer pour ne pas risquer de diminuer le caractère aléatoire des valeurs générées.

Pour générer un nombre pseudo-aléatoire, il faut invoquer l'une des méthodes :

  • int nextInt() : pour obtenir un entier
  • int next(int n) : pour obtenir un entier entre 0 et la valeur n exclue
  • double nextDouble() : pour obtenir un double
  • float nextFloat() : pour obtenir un nombre flottant
  • long nextLong() : pour obtenir un entier long
  • void nextBytes(byte[] bytes): pour obtenir un tableau d'octets contenant des valeurs aléatoires

La méthode generateSeed() permet de générer un tableau d'octets de valeurs aléatoires dont la taille est fournie en paramètre.

  • byte[] generateSeed(int numBytes)

Cette valeur peut être passée en paramètre de la méthode setSeed() pour réinitialiser la valeur seed d'une instance de type SecureRandom.

Exemple :
package fr.jmdoudoux.dej.securite;

import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;

public class TestSecureRandom {

  public static void main(String[] args) {
    SecureRandom secureRandom;

    try {
      secureRandom = SecureRandom.getInstance("SHA1PRNG");
      int randomValue = secureRandom.nextInt();
      System.out.println("valeur=" + randomValue);
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    }

    try {
      secureRandom = SecureRandom.getInstance("SHA1PRNG");
      int seedByteCount = 128;
      byte[] seed = secureRandom.generateSeed(seedByteCount);
      secureRandom.setSeed(seed);
      System.out.println("valeur=" + secureRandom.nextDouble());
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    }

    try {
      secureRandom = SecureRandom.getInstance("Windows-PRNG", "SunMSCAPI");
      int randomValue = secureRandom.nextInt();
      System.out.println("valeur=" + randomValue);
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } catch (NoSuchProviderException e) {
      e.printStackTrace();
    }
  }
}
Résultat :
valeur=-813765588
valeur=0.027554423801555594
valeur=125142869

 

26.15. La classe java.security.AlgorithmParameters

La classe AlgorithmParameters encapsule les paramètres d'une fonction cryptographique de manière opaque.

La méthode statique getInstance() est une fabrique pour créer une instance de type AlgorithmParameters qui possède plusieurs surcharges :

  • public static AlgorithmParameters getInstance(String algorithm)
  • public static AlgorithmParameters getInstance(String algorithm, String provider)
  • public static AlgorithmParameters getInstance(String algorithm, Provider provider)

L'instance doit être initialisée en utilisant une des surcharges de la méthode init()

  • void init(AlgorithmParameterSpec paramSpec)
  • void init(byte[] params)
  • void init(byte[] params, String format)

Le tableau d'octets contient les paramètres encodés et la chaîne de caractères précise le format à utiliser pour décoder les données (ASN.1 par défaut).

Une instance de type AlgorithmParameters ne peut être initialisée qu'une seule fois.

La méthode getEncoded() renvoie un tableau d'octets qui contient l'AlgorithmParameters de manière encodée, par défaut avec le format ASN.1.

  • byte[] getEncoded()

Une surcharge de la méthode getEncoded() permet de préciser le format d'encodage. Si la valeur null est passée en paramètre alors c'est le format par défaut qui est utilisé.

  • byte[] getEncoded(String format)

La méthode getParameterSpec(Class paramSpec) permet de renvoyer une instance de type AlgorithmParameterSpec encapsulant les paramètres de manière transparente. Le paramètre de type Class permet de préciser le type de l'instance retournée.

Exemple :
package fr.jmdoudoux.dej.securite;

import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.spec.DSAParameterSpec;

public class TestAlgorithmParameters {

  public static void main(String[] args) {
    BigInteger p = new BigInteger("D24700960FFA32D3F1557344E5871"
        + "01237532CC641646ED7A7C104743377F6D46251698B665CE2A6"
        + "CBAB6714C2569A7D2CA22C0CF03FA40AC930201090202020", 16);
    BigInteger q = new BigInteger("09", 16);
    BigInteger g = new BigInteger("512");

    try {
      DSAParameterSpec dsaParamsSpec = new DSAParameterSpec(p, q, g);

      AlgorithmParameters algParams = AlgorithmParameters.getInstance("DSA");
      algParams.init(dsaParamsSpec);

      System.out.println(ConvertionHelper.bytesToHex(algParams.getEncoded()));
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

 

26.16. La classe java.security.AlgorithmParameterGenerator

La classe java.security.AlgorithmParameterGenerator permet de créer des classes qui encapsulent des paramètres pour certains algorithmes.

Pour créer une instance de type AlgorithmParameterGenerator, il faut invoquer la méthode static getInstance() qui est une fabrique.

  • public static AlgorithmParameterGenerator getInstance(String algorithm)
  • public static AlgorithmParameterGenerator getInstance(String algorithm, String provider)
  • public static AlgorithmParameterGenerator getInstance(String algorithm, Provider provider)

L'instance obtenue doit être initialisée de manière dépendante ou indépendante de l'algorithme.

L'initialisation de l'instance de manière indépendante repose sur deux paramètres partagés par tous les algorithmes : une taille et une source pour obtenir des valeurs aléatoires. Deux surcharges de la méthode init() permettent de fournir ces informations :

  • void init(int size, SecureRandom random);
  • void init(int size)

La taille peut avoir une signification et une utilité particulière selon l'algorithme.

Deux autres surcharges de la méthode init() permettent d'initialiser l'instance en utilisant des paramètres spécifiques à l'algorithme encapsulés dans une instance de type AlgorithmParameterSpec.

  • void init(AlgorithmParameterSpec genParamSpec, SecureRandom random)
  • void init(AlgorithmParameterSpec genParamSpec)
Exemple :
package fr.jmdoudoux.dej.securite;

import java.math.BigInteger;
import java.security.AlgorithmParameterGenerator;
import java.security.AlgorithmParameters;
import java.security.NoSuchAlgorithmException;
import java.security.spec.DSAParameterSpec;
import java.security.spec.InvalidParameterSpecException;

public class TestAlgorithmParameterGenerator {

  public static void main(String[] args) {
    BigInteger q = null;
    BigInteger p = null;
    BigInteger g = null;

    AlgorithmParameterGenerator apgDSA;
    try {
      apgDSA = AlgorithmParameterGenerator.getInstance("DSA");
      AlgorithmParameters apDSA = apgDSA.generateParameters();
      DSAParameterSpec dsaparameterspec = apDSA
          .getParameterSpec(DSAParameterSpec.class);
      p = dsaparameterspec.getP();
      q = dsaparameterspec.getQ();
      g = dsaparameterspec.getG();
      System.out.println(p);
      System.out.println(q);
      System.out.println(g);
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } catch (InvalidParameterSpecException e) {
      e.printStackTrace();
    }
  }
}

 

26.17. La classe java.security.cert.CertificateFactory

La classe CertificateFactory est une fabrique pour générer des certificats et des CRL (Certificate Revocation List).

La classe permet d'obtenir une instance de type java.security.cert.X509Certificate pour un certificat X.509.

La classe permet d'obtenir une instance de type java.security.cert.X509CRL pour un CRL.

La méthode getInstance() permet d'obtenir une instance de type CertificateFactory : elle attend en paramètre le nom du type de certificat qui sera généré par la fabrique. Ce nom n'est pas sensible à la casse. Deux autres surcharges de la méthode getInstance() permettent de préciser le fournisseur.

  • static CertificateFactory getInstance(String type)
  • static CertificateFactory getInstance(String type, String provider)
  • static CertificateFactory getInstance(String type, Provider provider)

Pour générer une instance de type Certificate, il faut invoquer la méthode generateCertificate(). Elle attend en paramètre un objet de type InputStream qui sera utilisé pour lire le contenu du certificat.

  • final Certificate generateCertificate(InputStream inStream)

La méthode generateCertificates() retourne une collection des certificats lus à partir de l'instance de type InputStream fournie en paramètre.

  • final Collection generateCertificates(InputStream inStream)

La méthode generateCRL() permet de créer une instance de type CLR qui encapsule une liste de certificats révoqués (Certificate Revocation List). Elle attend en paramètre un objet de type InputStream qui permet de lire les données.

  • final CRL generateCRL(InputStream inStream)

La méthode generateCLRs() permet de créer une collection d'objets de type CLR.

  • final Collection generateCRLs(InputStream inStream)
Exemple :
package fr.jmdoudoux.dej.securite;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;

public class TestCertificateFactory {

  public static void main(String[] args) {
    FileInputStream in = null;

    try {
      CertificateFactory cf = CertificateFactory.getInstance("X.509");
      in = new FileInputStream("C:/java/moncertificat.cer");
      Certificate cert = cf.generateCertificate(in);
      System.out.println(cert);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      try {
        in.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}
Résultat :
[
[
  Version: V3
  Subject: CN=Doudoux Jean-Michel, OU=Developpement, O=jmdoudoux.fr, L=Pont a Mousson, 
  ST=Lorraine, C=FR
  Signature Algorithm: SHA1withDSA, OID = 1.2.840.10040.4.3

  Key:  Sun DSA Public Key
    Parameters:DSA
        p:     fd7f5381 1d751229 52df4a9c 2eece4e7 f611b752 3cef4400 c31e3f80 b6512669
    455d4022 51fb593d 8d58fabf c5f5ba30 f6cb9b55 6cd7813b 801d346f f26660b7
    6b9950a5 a49f9fe8 047b1022 c24fbba9 d7feb7c6 1bf83b57 e7c6a8a6 150f04fb
    83f6d3c5 1ec30235 54135a16 9132f675 f3ae2b61 d72aeff2 2203199d d14801c7
        q:     9760508f 15230bcc b292b982 a2eb840b f0581cf5
        g:     f7e1a085 d69b3dde cbbcab5c 36b857b9 7994afbb fa3aea82 f9574c0b 3d078267
    5159578e bad4594f e6710710 8180b449 167123e8 4c281613 b7cf0932 8cc8a6e1
    3c167a8b 547c8d28 e0a3ae1e 2bb3a675 916ea37f 0bfa2135 62f1fb62 7a01243b
    cca4f1be a8519089 a883dfe1 5ae59f06 928b665e 807b5525 64014c3b fecf492a

  y:
    0af345a2 4b0238b3 4e499986 6d9d9f1b 21b51090 a4cb28dc f98b587a 91bb98a1
    5e6f2d59 be186ee6 dd6bebbb a7800a5e 22c3f999 ab53ec9b 467522c5 9ef03ac7
    a034c4e4 56e3a116 93bc4737 4c0898dc a450df3b 71299b75 12c4a038 66a7a5ae
    3e57fde2 bdfc122d 6351b2c6 d6eae42c 890110d2 c263a8b4 d67d0743 a8c6eaa3

  Validity: [From: Sun Sep 01 22:53:57 CEST 2013,
               To: Sat Nov 30 21:53:57 CET 2013]
  Issuer: CN=Doudoux Jean-Michel, OU=Developpement, O=jmdoudoux.fr, L=Pont a Mousson, 
  ST=Lorraine, C=FR
  SerialNumber: [    51bd99b8]

La méthode generateCertPath() permet de créer et initialiser une instance de type CertPath. Elle possède plusieurs surcharges :

  • final CertPath generateCertPath(InputStream inStream)
  • final CertPath generateCertPath(InputStream inStream, String encoding)
  • final CertPath generateCertPath(List certificates)
Exemple :
package fr.jmdoudoux.dej.securite;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.cert.CertPath;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.List;

public class TestCertificateFactory {

  public static void main(String[] args) {
    FileInputStream in = null;
    FileInputStream in2 = null;

    try {
      CertificateFactory cf = CertificateFactory.getInstance("X.509");
      in = new FileInputStream("C:/java/moncertificat.cer");
      Certificate cert = cf.generateCertificate(in);
      // System.out.println(cert);


      List<Certificate> listeCert = new ArrayList<Certificate>();
      listeCert.add(cert);

      in2 = new FileInputStream("C:/java/montrustcert.cer");
      Certificate cert2 = cf.generateCertificate(in2);
      listeCert.add(cert2);

      CertPath cp = cf.generateCertPath(listeCert);
      System.out.println(cp);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      try {
        in.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
      try {
        in2.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}

La méthode getCertPathEncodings() renvoie les encodages supportés par la fabrique de certificats. Le premier encodage est l'encodage par défaut.

final Iterator getCertPathEncodings()

 

26.18. L'interface java.security.spec.KeySpec et ses implémentations

L'interface java.security.spec.KeySpec est un marqueur pour toutes les implémentations de clés transparentes.

La classe java.security.spec.DSAPrivateKeySpec encapsule une clé privée DSA.

Plusieurs méthodes permettent d'obtenir la clé privée (x) et les paramètres utilisés pour déterminer la clé (un nombre premier (p), un second nombre premier (q) et la base (g)) :

BigInteger getX()
BigInteger getP()
BigInteger getQ()
BigInteger getG()

La classe java.security.spec.DSAPublicKeySpec encapsule une clé publique DSA.

Plusieurs méthodes permettent d'obtenir la clé publique (y) et les paramètres utilisés pour déterminer la clé (un nombre premier (p), un second nombre premier (q) et la base (g)) :

BigInteger getY()
BigInteger getP()
BigInteger getQ()
BigInteger getG()

La classe java.security.spec.RSAPrivateKeySpec encapsule une clé privée RSA.

Deux méthodes permettent d'obtenir le modulo (n) et l'exposant (d) qui permettent de construire la clé :

BigInteger getModulus()
BigInteger getPrivateExponent()

La classe java.security.spec.RSAPrivateCrtKeySpec encapsule une clé privée RSA telle que précisé dans le standard PKCS #1. Elle hérite de la classe RSAPrivateKeySpec.

Plusieurs méthodes permettent d'obtenir l'exponent public (e) et les entiers qui composent le CRT (Chinese Remainder Theorem : le prime factor (p) du modulus (n), le prime factor (q) de n, l'exponent d mod (p-1), l'exponent d mod (q-1), et le coefficient du CRT (inverse de q) mod p) qui sont utilisés pour construire la clé :

BigInteger getPublicExponent()
BigInteger getPrimeP()
BigInteger getPrimeQ()
BigInteger getPrimeExponentP()
BigInteger getPrimeExponentQ()
BigInteger getCrtCoefficient()

La classe java.security.spec.RSAPublicKeySpec encapsule une clé publique RSA.

Deux méthodes fournissent le modulo (n) et l'exposant (d) qui permettent de construire la clé :

BigInteger getModulus()
BigInteger getPrivateExponent()

 

26.19. La classe java.security.spec.EncodedKeySpec et ses sous-classes

La classe abstraite java.security.spec.EncodedKeySpec encapsule une clé publique ou privée de manière encodée.

La méthode getEncoded() permet d'obtenir la clé encodée :

  • abstract byte[] getEncoded();

La méthode getFormat() renvoie le format d'encodage :

  • abstract String getFormat();

Cette classe possède deux classes filles : PKCS8EncodedKeySpec et X509EncodedKeySpec.

La classe java.security.spec.X509EncodedKeySpec encapsule une clé publique encodée selon le standard X.509.

La méthode getFormat() renvoie "X.509".

 

26.19.1. La classe java.security.spec.PKCS8EncodedKeySpec

La classe java.security.spec.PKCS8EncodedKeySpec encapsule une clé publique encodée selon le standard PKCS #8.

La méthode getFormat() renvoie "PKCS#8".

Il est possible d'utiliser OpenSSL pour générer une clé au format PKCS8. OpenSSL (Win32OpenSSL-1_0_1e.exe) peut être téléchargé sur le site www.openssl.org/related/binaries.html

Il faut générer une clé RSA

Résultat :
C:\OpenSSL-Win32\bin>openssl genrsa -out c:\java\key.pem 1024
WARNING: can't open config file: /usr/local/ssl/openssl.cnf
Loading 'screen' into random state - done
Generating RSA private key, 1024 bit long modulus
.................++++++
...........++++++
unable to write 'random state'
e is 65537 (0x10001)

L'exemple ci-dessus génère une clé RSA de 1024 bits.

Il faut ensuite convertir la clé RSA au format DER avec PKCS#8.

Résultat :
C:\OpenSSL-Win32\bin>openssl pkcs8 -topk8 -in "c:\java\key.pem"  -outform DER 
-out c:\java\key.pkcs8 WARNING: can't open config file: /usr/local/ssl/openssl.cnf Enter Encryption Password: Verifying - Enter Encryption Password: Loading 'screen' into random state - done unable to write 'random state'

L'exemple ci-dessous va lire le contenu de la clé pkcs8 et l'utiliser pour signer des données.

Exemple :
package fr.jmdoudoux.dej.securite;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;

public class TestPKCS8EncodedKeySpec {

  public static void main(String[] args) {
    PrivateKey privateKey = null;
    FileInputStream privateKeyIs = null;
    try {
      privateKeyIs = new FileInputStream("C:/java/key.pkcs8");

      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      byte[] buffer = new byte[1024];
      int len;
      while ((len = privateKeyIs.read(buffer)) >= 0)
        baos.write(buffer, 0, len);
      privateKeyIs.close();
      baos.close();
      byte[] privateKeyByte = baos.toByteArray();
      
      KeyFactory keyFactory = KeyFactory.getInstance("RSA");
      EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyByte);
      privateKey = keyFactory.generatePrivate(privateKeySpec);

      byte[] donneesSignees = null;
      byte[] donnees = "mes donnees".getBytes();
      Signature signature = Signature.getInstance("SHA1withRSA");
      signature.initSign(privateKey);
      signature.update(donnees);
      donneesSignees = signature.sign();
      System.out.println(ConvertionHelper.bytesToHex(donneesSignees));
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

 

26.20. L'interface java.security.spec.AlgorithmParameterSpec

L'interface java.security.spec.AlgorithmParameterSpec est un marqueur pour des classes qui encapsulent les paramètres d'algorithmes cryptographiques.

De nombreuses classes du JDK implémentent l'interface AlgorithmParameterSpec : DSAParameterSpec, DSAPrivateKeySpec, DSAPublicKeySpec, ECFieldF2m, ECFieldFp, ECGenParameterSpec, ECParameterSpec, ECPoint, ECPrivateKeySpec, ECPublicKeySpec, EllipticCurve, EncodedKeySpec, MGF1ParameterSpec, PKCS8EncodedKeySpec, PSSParameterSpec, RSAKeyGenParameterSpec, RSAMultiPrimePrivateCrtKeySpec, RSAOtherPrimeInfo, RSAPrivateCrtKeySpec, RSAPrivateKeySpec, RSAPublicKeySpec, X509EncodedKeySpec, ...

 

26.20.1. La classe java.security.spec.DSAParameterSpec

La classe java.security.spec.DSAParameterSpec encapsule les paramètres pour un algorithme de type DSA.

Elle possède trois méthodes :

Méthode

Rôle

BigInteger getG()

Renvoyer la base G

BigInteger getP()

Renvoyer le nombre premier P

BigInteger getQ()

Renvoyer le nombre premier Q


Elle possède un constructeur qui attend en paramètres les trois valeurs de type BaseInteger.

 

 


[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

78 commentaires Donner une note à l´article (5)

 

Copyright (C) 1999-2022 Jean-Michel DOUDOUX. Vous pouvez copier, redistribuer et/ou modifier ce document selon les termes de la Licence de Documentation Libre GNU, Version 1.1 ou toute autre version ultérieure publiée par la Free Software Foundation; les Sections Invariantes étant constitués du chapitre Préambule, aucun Texte de Première de Couverture, et aucun Texte de Quatrième de Couverture. Une copie de la licence est incluse dans la section GNU FreeDocumentation Licence. La version la plus récente de cette licence est disponible à l'adresse : GNU Free Documentation Licence.