Cryptage basé sur un mot de passe Java 256 bits AES


390

Je dois implémenter le cryptage AES 256 bits, mais tous les exemples que j'ai trouvés en ligne utilisent un "KeyGenerator" pour générer une clé 256 bits, mais je voudrais utiliser ma propre clé d'accès. Comment puis-je créer ma propre clé? J'ai essayé de le remplir à 256 bits, mais je reçois une erreur indiquant que la clé est trop longue. J'ai le correctif de juridiction illimité installé, donc ce n'est pas le problème :)

C'est à dire. Le KeyGenerator ressemble à ceci ...

// Get the KeyGenerator
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may not be available

// Generate the secret key specs.
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();

Code extrait d'ici

ÉDITER

En fait, je remplissais le mot de passe à 256 octets, pas des bits, ce qui est trop long. Ce qui suit est un code que j'utilise maintenant que j'ai plus d'expérience avec cela.

byte[] key = null; // TODO
byte[] input = null; // TODO
byte[] output = null;
SecretKeySpec keySpec = null;
keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
output = cipher.doFinal(input)

Les bits "TODO" que vous devez faire vous-même :-)


Pourriez-vous clarifier: est-ce que l'appel à kgen.init (256) fonctionne?
Mitch Wheat

2
Oui, mais cela génère automatiquement une clé ... mais comme je veux crypter les données entre deux endroits, j'ai besoin de connaître la clé à l'avance, donc je dois en spécifier une au lieu de "générer" une. Je peux spécifier un 16 bits qui fonctionne pour un cryptage 128 bits qui fonctionne. J'ai essayé un 32 bits pour le cryptage 256 bits, mais cela n'a pas fonctionné comme prévu.
Nippysaurus

4
Si je comprends bien, vous essayez d'utiliser une clé pré-arrangée de 256 bits, spécifiée, par exemple, comme un tableau d'octets. Si c'est le cas, l'approche de DarkSquid utilisant SecretKeySpec devrait fonctionner. Il est également possible de dériver une clé AES à partir d'un mot de passe; si c'est ce que vous recherchez, faites-le moi savoir et je vous montrerai la bonne façon de le faire; simplement hacher un mot de passe n'est pas la meilleure pratique.
erickson

Soyez prudent lorsque vous remplissez un nombre, vous risquez de rendre votre AES moins sécurisé.
Joshua

1
@erickson: c'est exactement ce que je dois faire (dériver une clé AES d'un mot de passe).
Nippysaurus

Réponses:


476

Partagez le password(a char[]) et salt(a - byte[]8 octets sélectionnés par un SecureRandomfait un bon sel - qui n'a pas besoin d'être gardé secret) avec le destinataire hors bande. Ensuite, pour dériver une bonne clé de ces informations:

/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

Les nombres magiques (qui pourraient être définis comme des constantes quelque part) 65536 et 256 sont le nombre d'itérations de dérivation de clé et la taille de clé, respectivement.

La fonction de dérivation de clé est itérée pour exiger un effort de calcul important, et cela empêche les attaquants d'essayer rapidement de nombreux mots de passe différents. Le nombre d'itérations peut être modifié en fonction des ressources informatiques disponibles.

La taille de la clé peut être réduite à 128 bits, ce qui est toujours considéré comme un cryptage "fort", mais cela ne donne pas beaucoup de marge de sécurité si l'on découvre des attaques qui affaiblissent AES.

Utilisée avec un mode de chaînage de blocs approprié, la même clé dérivée peut être utilisée pour crypter de nombreux messages. Dans Cipher Block Chaining (CBC) , un vecteur d'initialisation aléatoire (IV) est généré pour chaque message, produisant un texte chiffré différent même si le texte brut est identique. CBC n'est peut-être pas le mode le plus sûr à votre disposition (voir AEAD ci-dessous); il existe de nombreux autres modes avec des propriétés de sécurité différentes, mais ils utilisent tous une entrée aléatoire similaire. Dans tous les cas, les sorties de chaque opération de chiffrement sont le texte chiffré et le vecteur d'initialisation:

/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal("Hello, World!".getBytes("UTF-8"));

Stockez le ciphertextet le iv. Lors du déchiffrement, le SecretKeyest régénéré exactement de la même manière, en utilisant le mot de passe avec les mêmes paramètres de sel et d'itération. Initialisez le chiffrement avec cette clé et le vecteur d'initialisation stocké avec le message:

/* Decrypt the message, given derived key and initialization vector. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
String plaintext = new String(cipher.doFinal(ciphertext), "UTF-8");
System.out.println(plaintext);

Java 7 incluait la prise en charge de l' API pour les modes de chiffrement AEAD , et le fournisseur "SunJCE" inclus avec les distributions OpenJDK et Oracle les implémente à partir de Java 8. L'un de ces modes est fortement recommandé à la place de CBC; il protégera l'intégrité des données ainsi que leur confidentialité.


Un java.security.InvalidKeyExceptionavec le message "Taille de clé illégale ou paramètres par défaut" signifie que la puissance de cryptographie est limitée; les fichiers de politique de compétence de force illimitée ne sont pas au bon endroit. Dans un JDK, ils doivent être placés sous${jdk}/jre/lib/security

D'après la description du problème, il semble que les fichiers de stratégie ne soient pas correctement installés. Les systèmes peuvent facilement avoir plusieurs runtimes Java; revérifiez pour vous assurer que l'emplacement correct est utilisé.


29
@Nick: Lisez PKCS # 5. Les sels sont nécessaires pour PBKDF2, c'est pourquoi l'API pour le chiffrement par mot de passe les requiert comme entrée pour la dérivation des clés. Sans sels, une attaque par dictionnaire pourrait être utilisée, permettant une liste précalculée des clés de chiffrement symétriques les plus probables. Les chiffrements IV et les sels de dérivation de clés ont des fonctions différentes. Les IV permettent de réutiliser la même clé pour plusieurs messages. Les sels empêchent les attaques de dictionnaire sur la clé.
erickson

2
Tout d'abord, ce serait le cryptage DES, pas AES. La plupart des fournisseurs n'ont pas un bon support pour les PBEwith<prf>and<encryption>algorithmes; par exemple, le SunJCE ne fournit pas et PBE pour AES. Deuxièmement, l'activation de jasypt n'est pas un objectif. Un paquet qui prétend offrir la sécurité sans exiger une compréhension des principes sous-jacents semble dangereux à première vue.
erickson

6
J'ai implémenté la réponse de @ erickson en tant que classe: github.com/mrclay/jSecureEdit/tree/master/src/org/mrclay/crypto (PBE fait le travail, PBEStorage est un objet de valeur pour stocker le IV / texte chiffré ensemble.)
Steve Clay

3
@AndyNuss Cet exemple concerne le chiffrement réversible, qui ne doit généralement pas être utilisé pour les mots de passe. Vous pouvez utiliser la dérivation de clé PBKDF2 pour "hacher" les mots de passe en toute sécurité. Cela signifie que dans l'exemple ci-dessus, vous stockez le résultat de tmp.getEncoded()comme hachage. Vous devez également stocker les saltitérations et (65536 dans cet exemple) afin de pouvoir recalculer le hachage lorsque quelqu'un essaie de s'authentifier. Dans ce cas, générez le sel avec un générateur de nombres aléatoires cryptographiques à chaque changement de mot de passe.
erickson

6
Pour exécuter ce code, assurez-vous que vous disposez des bons fichiers de stratégie de juridiction de force illimitée dans votre JRE, comme indiqué dans ngs.ac.uk/tools/jcepolicyfiles
Amir Moghimi

75

Pensez à utiliser le module Spring Security Crypto

Le module Spring Security Crypto prend en charge le chiffrement symétrique, la génération de clés et l'encodage de mot de passe. Le code est distribué dans le cadre du module de base mais n'a aucune dépendance sur aucun autre code Spring Security (ou Spring).

Il fournit une abstraction simple pour le chiffrement et semble correspondre à ce qui est requis ici,

La méthode de chiffrement "standard" est AES 256 bits en utilisant PBKDF2 (fonction de dérivation de clé basée sur mot de passe # 2) de PKCS # 5. Cette méthode nécessite Java 6. Le mot de passe utilisé pour générer la SecretKey doit être conservé dans un endroit sûr et non partagé. Le sel est utilisé pour empêcher les attaques de dictionnaire contre la clé au cas où vos données chiffrées seraient compromises. Un vecteur d'initialisation aléatoire de 16 octets est également appliqué pour que chaque message chiffré soit unique.

Un regard sur les internes révèle une structure similaire à la réponse d' Erickson .

Comme indiqué dans la question, cela nécessite également la politique de juridiction de force illimitée JCE (Java Cryptography Extension) (sinon vous rencontrerez InvalidKeyException: Illegal Key Size). Il est téléchargeable pour Java 6 , Java 7 et Java 8 .

Exemple d'utilisation

import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.crypto.keygen.KeyGenerators;

public class CryptoExample {
    public static void main(String[] args) {
        final String password = "I AM SHERLOCKED";  
        final String salt = KeyGenerators.string().generateKey();

        TextEncryptor encryptor = Encryptors.text(password, salt);      
        System.out.println("Salt: \"" + salt + "\"");

        String textToEncrypt = "*royal secrets*";
        System.out.println("Original text: \"" + textToEncrypt + "\"");

        String encryptedText = encryptor.encrypt(textToEncrypt);
        System.out.println("Encrypted text: \"" + encryptedText + "\"");

        // Could reuse encryptor but wanted to show reconstructing TextEncryptor
        TextEncryptor decryptor = Encryptors.text(password, salt);
        String decryptedText = decryptor.decrypt(encryptedText);
        System.out.println("Decrypted text: \"" + decryptedText + "\"");

        if(textToEncrypt.equals(decryptedText)) {
            System.out.println("Success: decrypted text matches");
        } else {
            System.out.println("Failed: decrypted text does not match");
        }       
    }
}

Et un échantillon de sortie,

Sel: "feacbc02a3a697b0"
Texte original: "* secrets royaux *"
Texte crypté: "7c73c5a83fa580b5d6f8208768adc931ef3123291ac8bc335a1277a39d256d9a" 
Texte décrypté: "* secrets royaux *"
Succès: correspondances de texte déchiffré

Pouvez-vous utiliser ce module sans charger tout Spring? Ils ne semblent pas avoir rendu les fichiers jar disponibles au téléchargement.
theglauber

5
@theglauber Oui, vous pouvez utiliser le module sans Spring Security ou le framework Spring. En regardant le pom , la seule dépendance d'exécution est apache commons-logging 1.1.1 . Vous pouvez retirer le bocal avec maven ou le télécharger directement depuis le dépôt binaire officiel (voir Téléchargement des binaires Spring 4 pour plus d'informations sur les binaires Spring).
John McCarthy

1
Est-il possible de définir la longueur de clé sur 128 bits? La modification du dossier de sécurité sur chaque PC n'est pas une option pour moi.
IvanRF

1
@IvanRF désolé, ça ne ressemble pas à ça. 256 est codé en dur dans la source
John McCarthy

2
L' NULL_IV_GENERATORutilitaire Spring n'est pas sécurisé. Si l'application ne fournit pas de IV, laissez le fournisseur le choisir et interrogez-le après l'initialisation.
erickson

32

Après avoir lu les suggestions d'Erickson et glané ce que je pouvais de quelques autres publications et de cet exemple ici , j'ai essayé de mettre à jour le code de Doug avec les changements recommandés. N'hésitez pas à modifier pour le rendre meilleur.

  • Le vecteur d'initialisation n'est plus corrigé
  • la clé de chiffrement est dérivée à l'aide du code erickson
  • Un sel de 8 octets est généré dans setupEncrypt () à l'aide de SecureRandom ()
  • la clé de déchiffrement est générée à partir du sel de chiffrement et du mot de passe
  • le chiffrement de déchiffrement est généré à partir de la clé de déchiffrement et du vecteur d'initialisation
  • suppression du twiddling hexadécimal au lieu des routines hexadécimales du codec org.apache.commons

Quelques remarques: Cela utilise une clé de cryptage 128 bits - java ne fera apparemment pas de cryptage 256 bits prêt à l'emploi. L'implémentation de 256 nécessite l'installation de fichiers supplémentaires dans le répertoire d'installation java.

De plus, je ne suis pas un crypto. Prenez garde.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

public class Crypto
{
    String mPassword = null;
    public final static int SALT_LEN = 8;
    byte [] mInitVec = null;
    byte [] mSalt = null;
    Cipher mEcipher = null;
    Cipher mDecipher = null;
    private final int KEYLEN_BITS = 128; // see notes below where this is used.
    private final int ITERATIONS = 65536;
    private final int MAX_FILE_BUF = 1024;

    /**
     * create an object with just the passphrase from the user. Don't do anything else yet 
     * @param password
     */
    public Crypto (String password)
    {
        mPassword = password;
    }

    /**
     * return the generated salt for this object
     * @return
     */
    public byte [] getSalt ()
    {
        return (mSalt);
    }

    /**
     * return the initialization vector created from setupEncryption
     * @return
     */
    public byte [] getInitVec ()
    {
        return (mInitVec);
    }

    /**
     * debug/print messages
     * @param msg
     */
    private void Db (String msg)
    {
        System.out.println ("** Crypt ** " + msg);
    }

    /**
     * this must be called after creating the initial Crypto object. It creates a salt of SALT_LEN bytes
     * and generates the salt bytes using secureRandom().  The encryption secret key is created 
     * along with the initialization vectory. The member variable mEcipher is created to be used
     * by the class later on when either creating a CipherOutputStream, or encrypting a buffer
     * to be written to disk.
     *  
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidParameterSpecException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     * @throws InvalidKeyException
     */
    public void setupEncrypt () throws NoSuchAlgorithmException, 
                                                           InvalidKeySpecException, 
                                                           NoSuchPaddingException, 
                                                           InvalidParameterSpecException, 
                                                           IllegalBlockSizeException, 
                                                           BadPaddingException, 
                                                           UnsupportedEncodingException, 
                                                           InvalidKeyException
    {
        SecretKeyFactory factory = null;
        SecretKey tmp = null;

        // crate secureRandom salt and store  as member var for later use
         mSalt = new byte [SALT_LEN];
        SecureRandom rnd = new SecureRandom ();
        rnd.nextBytes (mSalt);
        Db ("generated salt :" + Hex.encodeHexString (mSalt));

        factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

        /* Derive the key, given password and salt. 
         * 
         * in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
         * The end user must also install them (not compiled in) so beware. 
         * see here:  http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
         */
        KeySpec spec = new PBEKeySpec (mPassword.toCharArray (), mSalt, ITERATIONS, KEYLEN_BITS);
        tmp = factory.generateSecret (spec);
        SecretKey secret = new SecretKeySpec (tmp.getEncoded(), "AES");

        /* Create the Encryption cipher object and store as a member variable
         */
        mEcipher = Cipher.getInstance ("AES/CBC/PKCS5Padding");
        mEcipher.init (Cipher.ENCRYPT_MODE, secret);
        AlgorithmParameters params = mEcipher.getParameters ();

        // get the initialization vectory and store as member var 
        mInitVec = params.getParameterSpec (IvParameterSpec.class).getIV();

        Db ("mInitVec is :" + Hex.encodeHexString (mInitVec));
    }



    /**
     * If a file is being decrypted, we need to know the pasword, the salt and the initialization vector (iv). 
     * We have the password from initializing the class. pass the iv and salt here which is
     * obtained when encrypting the file initially.
     *   
     * @param initvec
     * @param salt
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     * @throws DecoderException
     */
    public void setupDecrypt (String initvec, String salt) throws NoSuchAlgorithmException, 
                                                                                       InvalidKeySpecException, 
                                                                                       NoSuchPaddingException, 
                                                                                       InvalidKeyException, 
                                                                                       InvalidAlgorithmParameterException, 
                                                                                       DecoderException
    {
        SecretKeyFactory factory = null;
        SecretKey tmp = null;
        SecretKey secret = null;

        // since we pass it as a string of input, convert to a actual byte buffer here
        mSalt = Hex.decodeHex (salt.toCharArray ());
       Db ("got salt " + Hex.encodeHexString (mSalt));

        // get initialization vector from passed string
        mInitVec = Hex.decodeHex (initvec.toCharArray ());
        Db ("got initvector :" + Hex.encodeHexString (mInitVec));


        /* Derive the key, given password and salt. */
        // in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
        // The end user must also install them (not compiled in) so beware. 
        // see here: 
      // http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
        factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec spec = new PBEKeySpec(mPassword.toCharArray (), mSalt, ITERATIONS, KEYLEN_BITS);

        tmp = factory.generateSecret(spec);
        secret = new SecretKeySpec(tmp.getEncoded(), "AES");

        /* Decrypt the message, given derived key and initialization vector. */
        mDecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        mDecipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(mInitVec));
    }


    /**
     * This is where we write out the actual encrypted data to disk using the Cipher created in setupEncrypt().
     * Pass two file objects representing the actual input (cleartext) and output file to be encrypted.
     * 
     * there may be a way to write a cleartext header to the encrypted file containing the salt, but I ran
     * into uncertain problems with that. 
     *  
     * @param input - the cleartext file to be encrypted
     * @param output - the encrypted data file
     * @throws IOException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public void WriteEncryptedFile (File input, File output) throws 
                                                                                          IOException, 
                                                                                          IllegalBlockSizeException, 
                                                                                          BadPaddingException
    {
        FileInputStream fin;
        FileOutputStream fout;
        long totalread = 0;
        int nread = 0;
        byte [] inbuf = new byte [MAX_FILE_BUF];

        fout = new FileOutputStream (output);
        fin = new FileInputStream (input);

        while ((nread = fin.read (inbuf)) > 0 )
        {
            Db ("read " + nread + " bytes");
            totalread += nread;

            // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
            // and results in full blocks of MAX_FILE_BUF being written. 
            byte [] trimbuf = new byte [nread];
            for (int i = 0; i < nread; i++)
                trimbuf[i] = inbuf[i];

            // encrypt the buffer using the cipher obtained previosly
            byte [] tmp = mEcipher.update (trimbuf);

            // I don't think this should happen, but just in case..
            if (tmp != null)
                fout.write (tmp);
        }

        // finalize the encryption since we've done it in blocks of MAX_FILE_BUF
        byte [] finalbuf = mEcipher.doFinal ();
        if (finalbuf != null)
            fout.write (finalbuf);

        fout.flush();
        fin.close();
        fout.close();

        Db ("wrote " + totalread + " encrypted bytes");
    }


    /**
     * Read from the encrypted file (input) and turn the cipher back into cleartext. Write the cleartext buffer back out
     * to disk as (output) File.
     * 
     * I left CipherInputStream in here as a test to see if I could mix it with the update() and final() methods of encrypting
     *  and still have a correctly decrypted file in the end. Seems to work so left it in.
     *  
     * @param input - File object representing encrypted data on disk 
     * @param output - File object of cleartext data to write out after decrypting
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     */
    public void ReadEncryptedFile (File input, File output) throws 
                                                                                                                                            IllegalBlockSizeException, 
                                                                                                                                            BadPaddingException, 
                                                                                                                                            IOException
    {
        FileInputStream fin; 
        FileOutputStream fout;
        CipherInputStream cin;
        long totalread = 0;
        int nread = 0;
        byte [] inbuf = new byte [MAX_FILE_BUF];

        fout = new FileOutputStream (output);
        fin = new FileInputStream (input);

        // creating a decoding stream from the FileInputStream above using the cipher created from setupDecrypt()
        cin = new CipherInputStream (fin, mDecipher);

        while ((nread = cin.read (inbuf)) > 0 )
        {
            Db ("read " + nread + " bytes");
            totalread += nread;

            // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
            byte [] trimbuf = new byte [nread];
            for (int i = 0; i < nread; i++)
                trimbuf[i] = inbuf[i];

            // write out the size-adjusted buffer
            fout.write (trimbuf);
        }

        fout.flush();
        cin.close();
        fin.close ();       
        fout.close();   

        Db ("wrote " + totalread + " encrypted bytes");
    }


    /**
     * adding main() for usage demonstration. With member vars, some of the locals would not be needed
     */
    public static void main(String [] args)
    {

        // create the input.txt file in the current directory before continuing
        File input = new File ("input.txt");
        File eoutput = new File ("encrypted.aes");
        File doutput = new File ("decrypted.txt");
        String iv = null;
        String salt = null;
        Crypto en = new Crypto ("mypassword");

        /*
         * setup encryption cipher using password. print out iv and salt
         */
        try
      {
          en.setupEncrypt ();
          iv = Hex.encodeHexString (en.getInitVec ()).toUpperCase ();
          salt = Hex.encodeHexString (en.getSalt ()).toUpperCase ();
      }
      catch (InvalidKeyException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchAlgorithmException e)
      {
          e.printStackTrace();
      }
      catch (InvalidKeySpecException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchPaddingException e)
      {
          e.printStackTrace();
      }
      catch (InvalidParameterSpecException e)
      {
          e.printStackTrace();
      }
      catch (IllegalBlockSizeException e)
      {
          e.printStackTrace();
      }
      catch (BadPaddingException e)
      {
          e.printStackTrace();
      }
      catch (UnsupportedEncodingException e)
      {
          e.printStackTrace();
      }

        /*
         * write out encrypted file
         */
        try
      {
          en.WriteEncryptedFile (input, eoutput);
          System.out.printf ("File encrypted to " + eoutput.getName () + "\niv:" + iv + "\nsalt:" + salt + "\n\n");
      }
      catch (IllegalBlockSizeException e)
      {
          e.printStackTrace();
      }
      catch (BadPaddingException e)
      {
          e.printStackTrace();
      }
      catch (IOException e)
      {
          e.printStackTrace();
      }


        /*
         * decrypt file
         */
        Crypto dc = new Crypto ("mypassword");
        try
      {
          dc.setupDecrypt (iv, salt);
      }
      catch (InvalidKeyException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchAlgorithmException e)
      {
          e.printStackTrace();
      }
      catch (InvalidKeySpecException e)
      {
          e.printStackTrace();
      }
      catch (NoSuchPaddingException e)
      {
          e.printStackTrace();
      }
      catch (InvalidAlgorithmParameterException e)
      {
          e.printStackTrace();
      }
      catch (DecoderException e)
      {
          e.printStackTrace();
      }

        /*
         * write out decrypted file
         */
        try
      {
          dc.ReadEncryptedFile (eoutput, doutput);
          System.out.println ("decryption finished to " + doutput.getName ());
      }
      catch (IllegalBlockSizeException e)
      {
          e.printStackTrace();
      }
      catch (BadPaddingException e)
      {
          e.printStackTrace();
      }
      catch (IOException e)
      {
          e.printStackTrace();
      }
   }


}

13
C'est fondamentalement la même réponse que celle d'Erickson, entourée d'une enveloppe - pas si bien programmée à mon avis -. printStackTrace()
Maarten Bodewes

2
@owlstead - C'est une excellente réponse. Il montre comment chiffrer un flux en chiffrant le tampon d'octets, au lieu d'avoir tout en mémoire. La réponse d'Erickson ne fonctionnera pas pour les fichiers volumineux, qui ne tiennent pas en mémoire. Donc +1 à wufoo. :)
dynamokaj

2
@dynamokaj L'utilisation CipherInputStreamet CipherOutputStreamn'est pas vraiment un problème. Mélanger toutes les exceptions sous le tableau est un problème. Le fait que le sel soit soudainement devenu un champ et que la perfusion intraveineuse soit nécessaire est un problème. Le fait qu'il ne respecte pas les conventions de codage Java est un problème. Et le fait que cela ne fonctionne que sur les fichiers alors qu'il n'a pas été demandé est un problème. Et que le reste du code est fondamentalement une copie n'aide pas non plus. Mais peut-être que je vais l'ajuster pour l'améliorer, comme suggéré ...
Maarten Bodewes

@owlstead Je suis d'accord pour dire que le codage aurait pu mieux paraître. Je l'ai réduit à 1/4 ou quelque chose, mais j'aime bien qu'il m'a présenté le CipherInputStream et le CipherOutputStream, car c'était exactement ce dont j'avais besoin hier! ;)
dynamokaj

pourquoi deux fois? fout.close (); fout.close ();
Marian Paździoch

7

Générer votre propre clé à partir d'un tableau d'octets est simple:

byte[] raw = ...; // 32 bytes in size for a 256 bit key
Key skey = new javax.crypto.spec.SecretKeySpec(raw, "AES");

Mais la création d'une clé 256 bits n'est pas suffisante. Si le générateur de clés ne peut pas générer de clés 256 bits pour vous, leCipher classe ne prend probablement pas en charge AES 256 bits non plus. Vous dites que le patch de juridiction illimitée est installé, donc le chiffrement AES-256 devrait être pris en charge (mais les clés 256 bits devraient l'être aussi, donc cela pourrait être un problème de configuration).

Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skey);
byte[] encrypted = cipher.doFinal(plainText.getBytes());

Une solution de contournement en raison du manque de prise en charge de l'AES-256 consiste à prendre une implémentation librement disponible de l'AES-256 et à l'utiliser comme fournisseur personnalisé. Cela implique de créer votre propre Providersous-classe et de l'utiliser avec Cipher.getInstance(String, Provider). Mais cela peut être un processus complexe.


5
Vous devez toujours indiquer le mode et l'algorithme de remplissage. Java utilise le mode ECB non sécurisé par défaut.
Maarten Bodewes

Vous ne pouvez pas créer votre propre fournisseur, les fournisseurs doivent être signés (je ne peux pas croire que j'ai relu cette erreur au départ). Même si vous le pouvez, la restriction de la taille de la clé réside dans l'implémentation de Cipher, pas dans le fournisseur lui-même. Vous pouvez utiliser AES-256 dans Java 8 et versions antérieures, mais vous devez utiliser une API propriétaire. Ou un runtime qui ne pose pas de restrictions sur la taille de la clé bien sûr.
Maarten Bodewes

Les versions récentes d'OpenJDK (et Android) n'ont pas de restrictions sur l'ajout de votre propre fournisseur de sécurité / crypto. Mais vous le faites à vos risques et périls, bien sûr. Si vous oubliez de mettre à jour vos bibliothèques, vous risquez de vous exposer à des risques de sécurité.
Maarten Bodewes

1
@ MaartenBodewes + OpenJDK n'a jamais eu le problème de la `` politique de cryptographie limitée '' en premier lieu, et Oracle JDK l'a supprimé il y a plus d'un an pour 8u161 et 9 (et peut-être certaines versions inférieures maintenant payantes mais je ne les ai pas vérifiées)
dave_thompson_085

6

Ce que j'ai fait dans le passé, c'est hacher la clé via quelque chose comme SHA256, puis extraire les octets du hachage dans l'octet de clé [].

Une fois que vous avez votre octet [], vous pouvez simplement faire:

SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(clearText.getBytes());

12
Pour d'autres: ce n'est pas une méthode très sécurisée. Vous devez utiliser PBKDF 2 spécifié dans PKCS # 5. erickson a expliqué comment procéder ci-dessus. La méthode de DarkSquid est vulnérable aux attaques par mot de passe et ne fonctionne également que si la taille de votre texte en clair est un multiple de la taille de bloc d'AES (128 bits) car il a omis le remplissage. De plus, il ne spécifie pas le mode; lire les modes de fonctionnement de Block Cipher de Wikipedia pour les préoccupations.
Hut8

1
@DarkSquid Cipher aes256 = Cipher.getInstance("AES/OFB/NoPadding"); MessageDigest keyDigest = MessageDigest.getInstance("SHA-256"); byte[] keyHash = keyDigest.digest(secret.getBytes("UTF-8")); SecretKeySpec key = new SecretKeySpec(keyHash, "AES"); aes256.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(initializationVector)); Je fais aussi la même chose que suggéré dans votre réponse, mais je me retrouve toujours avec cette java.security.InvalidKeyException: taille de clé illégale Le téléchargement du fichier de stratégie JCE est-il obligatoire?
Niranjan Subramanian

2
N'UTILISEZ PAS cette méthode dans aucun type d'environnement de production. Au début du cryptage par mot de passe, de nombreux utilisateurs sont submergés par des murs de code et ne comprennent pas comment fonctionnent les attaques par dictionnaire et autres hacks simples. Bien qu'il puisse être frustrant d'apprendre, c'est un investissement valable pour rechercher cela. Voici un bon article pour débutants: adambard.com/blog/3-wrong-ways-to-store-a-password
IcedDante

1

Ajout aux modifications de @ Wufoo, la version suivante utilise InputStreams plutôt que des fichiers pour faciliter le travail avec une variété de fichiers. Il stocke également l'IV et le sel au début du fichier, ce qui fait que seul le mot de passe doit être suivi. Étant donné que l'IV et le sel n'ont pas besoin d'être secrets, cela rend la vie un peu plus facile.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
    public final static int SALT_LEN     = 8;
    static final String     HEXES        = "0123456789ABCDEF";
    String                  mPassword    = null;
    byte[]                  mInitVec     = null;
    byte[]                  mSalt        = new byte[SALT_LEN];
    Cipher                  mEcipher     = null;
    Cipher                  mDecipher    = null;
    private final int       KEYLEN_BITS  = 128;    // see notes below where this is used.
    private final int       ITERATIONS   = 65536;
    private final int       MAX_FILE_BUF = 1024;

    /**
     * create an object with just the passphrase from the user. Don't do anything else yet
     * @param password
     */
    public AES(String password) {
        mPassword = password;
    }

    public static String byteToHex(byte[] raw) {
        if (raw == null) {
            return null;
        }

        final StringBuilder hex = new StringBuilder(2 * raw.length);

        for (final byte b : raw) {
            hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
        }

        return hex.toString();
    }

    public static byte[] hexToByte(String hexString) {
        int    len = hexString.length();
        byte[] ba  = new byte[len / 2];

        for (int i = 0; i < len; i += 2) {
            ba[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
                                + Character.digit(hexString.charAt(i + 1), 16));
        }

        return ba;
    }

    /**
     * debug/print messages
     * @param msg
     */
    private void Db(String msg) {
        System.out.println("** Crypt ** " + msg);
    }

    /**
     * This is where we write out the actual encrypted data to disk using the Cipher created in setupEncrypt().
     * Pass two file objects representing the actual input (cleartext) and output file to be encrypted.
     *
     * there may be a way to write a cleartext header to the encrypted file containing the salt, but I ran
     * into uncertain problems with that.
     *
     * @param input - the cleartext file to be encrypted
     * @param output - the encrypted data file
     * @throws IOException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public void WriteEncryptedFile(InputStream inputStream, OutputStream outputStream)
            throws IOException, IllegalBlockSizeException, BadPaddingException {
        try {
            long             totalread = 0;
            int              nread     = 0;
            byte[]           inbuf     = new byte[MAX_FILE_BUF];
            SecretKeyFactory factory   = null;
            SecretKey        tmp       = null;

            // crate secureRandom salt and store  as member var for later use
            mSalt = new byte[SALT_LEN];

            SecureRandom rnd = new SecureRandom();

            rnd.nextBytes(mSalt);
            Db("generated salt :" + byteToHex(mSalt));
            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

            /*
             *  Derive the key, given password and salt.
             *
             * in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
             * The end user must also install them (not compiled in) so beware.
             * see here:  http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
             */
            KeySpec spec = new PBEKeySpec(mPassword.toCharArray(), mSalt, ITERATIONS, KEYLEN_BITS);

            tmp = factory.generateSecret(spec);

            SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

            /*
             *  Create the Encryption cipher object and store as a member variable
             */
            mEcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            mEcipher.init(Cipher.ENCRYPT_MODE, secret);

            AlgorithmParameters params = mEcipher.getParameters();

            // get the initialization vectory and store as member var
            mInitVec = params.getParameterSpec(IvParameterSpec.class).getIV();
            Db("mInitVec is :" + byteToHex(mInitVec));
            outputStream.write(mSalt);
            outputStream.write(mInitVec);

            while ((nread = inputStream.read(inbuf)) > 0) {
                Db("read " + nread + " bytes");
                totalread += nread;

                // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
                // and results in full blocks of MAX_FILE_BUF being written.
                byte[] trimbuf = new byte[nread];

                for (int i = 0; i < nread; i++) {
                    trimbuf[i] = inbuf[i];
                }

                // encrypt the buffer using the cipher obtained previosly
                byte[] tmpBuf = mEcipher.update(trimbuf);

                // I don't think this should happen, but just in case..
                if (tmpBuf != null) {
                    outputStream.write(tmpBuf);
                }
            }

            // finalize the encryption since we've done it in blocks of MAX_FILE_BUF
            byte[] finalbuf = mEcipher.doFinal();

            if (finalbuf != null) {
                outputStream.write(finalbuf);
            }

            outputStream.flush();
            inputStream.close();
            outputStream.close();
            outputStream.close();
            Db("wrote " + totalread + " encrypted bytes");
        } catch (InvalidKeyException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidParameterSpecException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchPaddingException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Read from the encrypted file (input) and turn the cipher back into cleartext. Write the cleartext buffer back out
     * to disk as (output) File.
     *
     * I left CipherInputStream in here as a test to see if I could mix it with the update() and final() methods of encrypting
     *  and still have a correctly decrypted file in the end. Seems to work so left it in.
     *
     * @param input - File object representing encrypted data on disk
     * @param output - File object of cleartext data to write out after decrypting
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     */
    public void ReadEncryptedFile(InputStream inputStream, OutputStream outputStream)
            throws IllegalBlockSizeException, BadPaddingException, IOException {
        try {
            CipherInputStream cin;
            long              totalread = 0;
            int               nread     = 0;
            byte[]            inbuf     = new byte[MAX_FILE_BUF];

            // Read the Salt
            inputStream.read(this.mSalt);
            Db("generated salt :" + byteToHex(mSalt));

            SecretKeyFactory factory = null;
            SecretKey        tmp     = null;
            SecretKey        secret  = null;

            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

            KeySpec spec = new PBEKeySpec(mPassword.toCharArray(), mSalt, ITERATIONS, KEYLEN_BITS);

            tmp    = factory.generateSecret(spec);
            secret = new SecretKeySpec(tmp.getEncoded(), "AES");

            /* Decrypt the message, given derived key and initialization vector. */
            mDecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

            // Set the appropriate size for mInitVec by Generating a New One
            AlgorithmParameters params = mDecipher.getParameters();

            mInitVec = params.getParameterSpec(IvParameterSpec.class).getIV();

            // Read the old IV from the file to mInitVec now that size is set.
            inputStream.read(this.mInitVec);
            Db("mInitVec is :" + byteToHex(mInitVec));
            mDecipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(mInitVec));

            // creating a decoding stream from the FileInputStream above using the cipher created from setupDecrypt()
            cin = new CipherInputStream(inputStream, mDecipher);

            while ((nread = cin.read(inbuf)) > 0) {
                Db("read " + nread + " bytes");
                totalread += nread;

                // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
                byte[] trimbuf = new byte[nread];

                for (int i = 0; i < nread; i++) {
                    trimbuf[i] = inbuf[i];
                }

                // write out the size-adjusted buffer
                outputStream.write(trimbuf);
            }

            outputStream.flush();
            cin.close();
            inputStream.close();
            outputStream.close();
            Db("wrote " + totalread + " encrypted bytes");
        } catch (Exception ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * adding main() for usage demonstration. With member vars, some of the locals would not be needed
     */
    public static void main(String[] args) {

        // create the input.txt file in the current directory before continuing
        File   input   = new File("input.txt");
        File   eoutput = new File("encrypted.aes");
        File   doutput = new File("decrypted.txt");
        String iv      = null;
        String salt    = null;
        AES    en      = new AES("mypassword");

        /*
         * write out encrypted file
         */
        try {
            en.WriteEncryptedFile(new FileInputStream(input), new FileOutputStream(eoutput));
            System.out.printf("File encrypted to " + eoutput.getName() + "\niv:" + iv + "\nsalt:" + salt + "\n\n");
        } catch (IllegalBlockSizeException | BadPaddingException | IOException e) {
            e.printStackTrace();
        }

        /*
         * decrypt file
         */
        AES dc = new AES("mypassword");

        /*
         * write out decrypted file
         */
        try {
            dc.ReadEncryptedFile(new FileInputStream(eoutput), new FileOutputStream(doutput));
            System.out.println("decryption finished to " + doutput.getName());
        } catch (IllegalBlockSizeException | BadPaddingException | IOException e) {
            e.printStackTrace();
        }
    }
}

1
Cette solution semble utiliser une gestion de tampon maladroite et une gestion des exceptions absolument inférieure à la normale, les journalisant essentiellement puis les oubliant. Soyez averti que l'utilisation de CBC est OK pour les fichiers mais pas pour la sécurité du transport. L'utilisation de PBKDF2 et AES peut bien sûr être défendue, en ce sens, elle peut constituer une bonne base pour une solution.
Maarten Bodewes

1

(Peut-être utile pour d'autres ayant une exigence similaire)

J'avais une exigence similaire pour utiliser le AES-256-CBCcryptage et le décryptage en Java.

Pour obtenir (ou spécifier) ​​le cryptage / décryptage de 256 octets, la Java Cryptography Extension (JCE)stratégie doit être définie sur"Unlimited"

Il peut être défini dans le java.securityfichier sous $JAVA_HOME/jre/lib/security(pour JDK) ou $JAVA_HOME/lib/security(pour JRE)

crypto.policy=unlimited

Ou dans le code comme

Security.setProperty("crypto.policy", "unlimited");

Java 9 et les versions ultérieures ont cette option activée par défaut.


0

Pensez à utiliser Encryptor4j dont je suis l'auteur.

Assurez-vous d'abord que les fichiers de stratégie de juridiction de force illimitée sont installés avant de continuer afin de pouvoir utiliser des clés AES 256 bits.

Procédez ensuite comme suit:

String password = "mysupersecretpassword"; 
Key key = KeyFactory.AES.keyFromPassword(password.toCharArray());
Encryptor encryptor = new Encryptor(key, "AES/CBC/PKCS7Padding", 16);

Vous pouvez maintenant utiliser le crypteur pour crypter votre message. Vous pouvez également effectuer un chiffrement en streaming si vous le souhaitez. Il génère et ajoute automatiquement un IV sécurisé pour votre commodité.

Si c'est un fichier que vous souhaitez compresser, jetez un œil à cette réponse Crypter un gros fichier avec AES en utilisant JAVA pour une approche encore plus simple.


2
Salut Martin, tu devrais toujours indiquer que tu es l'auteur de la bibliothèque si tu veux le signaler. Il y a des tas de wrappers cryptographiques essayant de rendre les choses faciles. Est-ce que celui-ci a un document de sécurité ou a-t-il reçu des critiques pour que cela en vaille la peine?
Maarten Bodewes

-1

Utilisez cette classe pour le chiffrement. Ça marche.

public class ObjectCrypter {


    public static byte[] encrypt(byte[] ivBytes, byte[] keyBytes, byte[] mes) 
            throws NoSuchAlgorithmException,
            NoSuchPaddingException,
            InvalidKeyException,
            InvalidAlgorithmParameterException,
            IllegalBlockSizeException,
            BadPaddingException, IOException {

        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = null;
        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
        return  cipher.doFinal(mes);

    }

    public static byte[] decrypt(byte[] ivBytes, byte[] keyBytes, byte[] bytes) 
            throws NoSuchAlgorithmException,
            NoSuchPaddingException,
            InvalidKeyException,
            InvalidAlgorithmParameterException,
            IllegalBlockSizeException,
            BadPaddingException, IOException, ClassNotFoundException {

        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
        return  cipher.doFinal(bytes);

    }
}

Et ce sont des ivBytes et une clé aléatoire;

String key = "e8ffc7e56311679f12b6fc91aa77a5eb";

byte[] ivBytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
keyBytes = key.getBytes("UTF-8");

10
"ça marche" .... oui, mais il ne répond pas aux exigences de création d'une solution cryptographiquement sécurisée (ni aux normes de codage Java en ce qui concerne la gestion des exceptions, à mon avis).
Maarten Bodewes

2
IV est initialisé à zéro. Recherchez les attaques BEAST et ACPA.
Michele Giuseppe Fadda

Des exceptions sur le wazoo, la méthode de génération de la clé "aléatoire" et un IV nul est un problème avec cette implémentation, mais ces problèmes sont triviaux à résoudre. +1.
Phil
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.