Comment crypter une chaîne en Java

150

Ce dont j'ai besoin est de crypter la chaîne qui apparaîtra dans le code à barres 2D (PDF-417) afin que lorsque quelqu'un a une idée de scanner, rien ne sera lisible.

Autres exigences:

  • ne devrait pas être compliqué
  • il ne doit pas être constitué de RSA, d'infrastructure PKI, de paires de clés, etc.

Cela doit être assez simple pour se débarrasser des gens qui fouinent et facile à déchiffrer pour les autres entreprises intéressées à obtenir ces données. Ils nous appellent, nous leur disons la norme ou leur donnons une clé simple qui peut ensuite être utilisée pour le décryptage.

Ces entreprises pourraient probablement utiliser des technologies différentes, il serait donc bon de s'en tenir à une norme qui n'est pas liée à une plate-forme ou une technologie particulière.

Que suggérez-vous? Y a-t-il une classe Java qui fait encrypt()et decrypt()sans trop de complications pour atteindre des normes de sécurité élevées?

ante.sabo
la source
Avertissement . Un grand nombre des réponses ci-dessous montrent une méthode ou une autre pour effectuer tout type de cryptographie sur Java. Les réponses peuvent ne pas refléter les bonnes pratiques cryptographiques et peuvent ne pas être bien examinées; il n'existe pas de sécurité par copier / coller . Les réponses doivent au moins prendre en compte la conversion de chaînes. La question réelle avec le code-barres 2D inclus est beaucoup trop large et devrait nécessiter une solution spécifique au client.
Maarten Bodewes

Réponses:

156

C'est la première page qui s'affiche via Google et les vulnérabilités de sécurité dans toutes les implémentations me font grincer des dents, alors je publie ceci pour ajouter des informations concernant le cryptage pour les autres, car cela remonte à 7 ans depuis la publication originale. Je suis titulaire d'une maîtrise en génie informatique et j'ai passé beaucoup de temps à étudier et à apprendre la cryptographie, donc je jette mes deux cents pour rendre Internet plus sûr.

Notez également qu'une grande partie de la mise en œuvre peut être sécurisée pour une situation donnée, mais pourquoi les utiliser et éventuellement faire une erreur accidentelle? Utilisez les outils les plus puissants dont vous disposez, sauf si vous avez une raison spécifique de ne pas le faire. Dans l'ensemble, je conseille vivement d'utiliser une bibliothèque et de rester à l'écart des détails essentiels si vous le pouvez.

MISE À JOUR 4/5/18: J'ai réécrit certaines parties pour les rendre plus simples à comprendre et j'ai changé la bibliothèque recommandée de Jasypt vers la nouvelle bibliothèque de Google Tink , je recommanderais de supprimer complètement Jasypt d'une configuration existante.

Préface

Je décrirai ci-dessous les bases de la cryptographie symétrique sécurisée et soulignerai les erreurs courantes que je vois en ligne lorsque les gens implémentent eux-mêmes la cryptographie avec la bibliothèque Java standard. Si vous souhaitez simplement ignorer tous les détails de la nouvelle bibliothèque de Google, Tink, importez-la dans votre projet et utilisez le mode AES-GCM pour tous vos cryptages et vous serez en sécurité.

Maintenant, si vous voulez apprendre les détails sur la façon de crypter en java, lisez la suite :)

Bloquer les chiffrements

Tout d'abord, vous devez choisir un chiffrement par bloc à clé symétrique. Un chiffrement par bloc est une fonction / un programme informatique utilisé pour créer un pseudo-aléatoire. Le pseudo-aléatoire est un faux hasard qu'aucun ordinateur autre qu'un ordinateur quantique ne serait capable de faire la différence entre celui-ci et le vrai hasard. Le Block Cipher est comme la pierre angulaire de la cryptographie, et lorsqu'il est utilisé avec différents modes ou schémas, nous pouvons créer des cryptages.

En ce qui concerne les algorithmes de chiffrement par blocs disponibles aujourd'hui, assurez-vous de ne JAMAIS , je répète, de ne JAMAIS utiliser DES , je dirais même de ne JAMAIS utiliser 3DES . Le seul Block Cipher que même la version NSA de Snowden a pu vérifier comme étant vraiment aussi proche que possible du pseudo-aléatoire est AES 256 . Il existe également AES 128; la différence est que AES 256 fonctionne dans des blocs de 256 bits, tandis que AES 128 fonctionne dans 128 blocs. Dans l'ensemble, AES 128 est considéré comme sécurisé bien que certaines faiblesses aient été découvertes, mais 256 est aussi solide que possible.

Fait amusant, le DES a été brisé par la NSA lors de sa création et a en fait gardé un secret pendant quelques années. Bien que certaines personnes affirment toujours que 3DES est sécurisé, il existe de nombreux articles de recherche qui ont trouvé et analysé les faiblesses de 3DES .

Modes de cryptage

Le chiffrement est créé lorsque vous prenez un chiffrement par bloc et utilisez un schéma spécifique afin que le caractère aléatoire soit combiné avec une clé pour créer quelque chose qui est réversible tant que vous connaissez la clé. Ceci est appelé un mode de cryptage.

Voici un exemple de mode de cryptage et du mode le plus simple connu sous le nom de ECB, juste pour que vous puissiez comprendre visuellement ce qui se passe:

Mode ECB

Les modes de cryptage que vous verrez le plus souvent en ligne sont les suivants:

ECB CTR, CBC, GCM

Il existe d'autres modes en dehors de ceux énumérés et les chercheurs travaillent toujours vers de nouveaux modes pour améliorer les problèmes existants.

Passons maintenant aux implémentations et à ce qui est sécurisé. N'utilisez JAMAIS ECB, c'est mauvais pour cacher les données répétitives comme le montre le célèbre pingouin Linux .Exemple de pingouin Linux

Lors de l'implémentation en Java, notez que si vous utilisez le code suivant, le mode ECB est défini par défaut:

Cipher cipher = Cipher.getInstance("AES");

... DANGER CECI EST UNE VULNÉRABILITÉ! et malheureusement, cela se voit partout dans StackOverflow et en ligne dans des tutoriels et des exemples.

Nonces et IV

En réponse au problème rencontré avec le mode ECB, des noms également appelés IV ont été créés. L'idée est que nous générons une nouvelle variable aléatoire et l'attachons à chaque cryptage afin que lorsque vous cryptez deux messages identiques, ils sortent différents. La beauté derrière cela est qu'un IV ou nonce est de notoriété publique. Cela signifie qu'un attaquant peut y avoir accès, mais tant qu'il n'a pas votre clé, il ne peut rien faire avec cette connaissance.

Les problèmes courants que je vais voir sont que les gens définiront l'IV comme une valeur statique comme dans la même valeur fixe dans leur code. et voici le piège des IV au moment où vous en répétez un, vous compromettez en fait toute la sécurité de votre cryptage.

Générer une IV aléatoire

SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);

Remarque: SHA1 est cassé mais je n'ai pas pu trouver comment implémenter correctement SHA256 dans ce cas d'utilisation, donc si quelqu'un veut essayer de le mettre à jour, ce serait génial! De plus, les attaques SHA1 ne sont toujours pas conventionnelles car cela peut prendre quelques années sur un énorme cluster pour se fissurer. Consultez les détails ici.

Implémentation CTR

Aucun remplissage n'est requis pour le mode CTR.

 Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

Mise en œuvre de CBC

Si vous choisissez d'implémenter le mode CBC, faites-le avec PKCS7Padding comme suit:

 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");

Vulnérabilité CBC et CTR et pourquoi vous devriez utiliser GCM

Bien que certains autres modes tels que CBC et CTR soient sécurisés, ils se heurtent au problème où un attaquant peut retourner les données chiffrées, en modifiant leur valeur une fois déchiffrées. Alors disons que vous cryptez un message bancaire imaginaire "Sell 100", votre message crypté ressemble à ce "eu23ng", l'attaquant change un bit en "eu53ng" et tout d'un coup, une fois votre message déchiffré, il se lit comme "Sell 900".

Pour éviter cela, la majorité d'Internet utilise GCM, et chaque fois que vous voyez HTTPS, ils utilisent probablement GCM. GCM signe le message chiffré avec un hachage et vérifie que le message n'a pas été modifié à l'aide de cette signature.

J'éviterais de mettre en œuvre GCM en raison de sa complexité. Vous feriez mieux d'utiliser la nouvelle bibliothèque de Googles Tink, car là encore, si vous répétez accidentellement une IV, vous compromettez la clé dans le cas de GCM, qui est la faille de sécurité ultime. De nouveaux chercheurs travaillent sur des modes de cryptage IV résistants aux répétitions où même si vous répétez l'IV, la clé n'est pas en danger, mais cela n'est pas encore devenu courant.

Maintenant, si vous souhaitez implémenter GCM, voici un lien vers une belle implémentation GCM . Cependant, je ne peux pas assurer la sécurité ou si elle est correctement mise en œuvre, mais cela fait tomber la base. Notez également qu'avec GCM, il n'y a pas de rembourrage.

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

Clés vs mots de passe

Une autre note très importante, c'est que lorsqu'il s'agit de cryptographie, une clé et un mot de passe ne sont pas les mêmes choses. Une clé en cryptographie doit avoir une certaine quantité d'entropie et d'aléatoire pour être considérée comme sécurisée. C'est pourquoi vous devez vous assurer d'utiliser les bibliothèques cryptographiques appropriées pour générer la clé pour vous.

Vous avez donc vraiment deux implémentations que vous pouvez faire ici, la première consiste à utiliser le code trouvé sur ce thread StackOverflow pour la génération de clés aléatoires . Cette solution utilise un générateur de nombres aléatoires sécurisé pour créer une clé à partir de zéro que vous pouvez utiliser.

L'autre option moins sécurisée consiste à utiliser une entrée utilisateur telle qu'un mot de passe. Le problème dont nous avons discuté est que le mot de passe n'a pas assez d'entropie, nous devrions donc utiliser PBKDF2 , un algorithme qui prend le mot de passe et le renforce. Voici une implémentation StackOverflow qui m'a plu . Cependant, la bibliothèque Google Tink a tout cela intégré et vous devriez en profiter.

Développeurs Android

Un point important à souligner ici est de savoir que votre code Android est rétro-ingénieur et dans la plupart des cas, la plupart du code java l'est aussi. Cela signifie que si vous stockez le mot de passe en texte brut dans votre code. Un hacker peut facilement le récupérer. Habituellement, pour ce type de cryptage, vous souhaitez utiliser la cryptographie asymétrique et ainsi de suite. Cela sort du cadre de cet article, j'éviterai donc de m'y plonger.

Une lecture intéressante de 2013 : souligne que 88% des implémentations de Crypto sous Android ont été mal effectuées.

Dernières pensées

Encore une fois, je suggérerais d'éviter d'implémenter directement la bibliothèque java pour crypto et d'utiliser Google Tink , cela vous évitera des maux de tête car ils ont vraiment fait du bon travail en implémentant correctement tous les algorithmes. Et même dans ce cas, assurez-vous de vérifier les problèmes soulevés sur le github Tink, les vulnérabilités pop-up ici et là.

Si vous avez des questions ou des commentaires, n'hésitez pas à commenter! La sécurité est en constante évolution et vous devez faire de votre mieux pour la suivre :)

Konstantino Sparakis
la source
15
C'est la chose la plus propre que j'aie jamais vue.
Seraf
1
@SabirKhan Cela pourrait être une source de préoccupation, mais les algorithmes de base n'ont toujours pas été cassés, donc je ne serais pas trop inquiet à ce sujet. Dans le cas où vous ne lui faites pas confiance, consultez également github.com/google/keyczar , il a été développé par l'équipe de sécurité de googles.
Konstantino Sparakis
1
@KonstantinoSparakis: Si je n'ai pas mal interprété la documentation de BasicTextEncryptor et StrongTextEncryptor de Jasypt, ces classes utilisent DES et 3DES pour le cryptage, ce que vous dites aux lecteurs de ne pas utiliser. OMI, vous devez remplacer les exemples de code donnés par un autre qui utilise StandardPBEStringEncryptor de Jasypt et définit manuellement un algorithme AES à utiliser.
xpages-noob
1
@ xpages-noob J'ai mis à jour le message. J'ai en fait trouvé Google Tink, qui est la dernière bibliothèque prise en charge pour la cryptographie, vous devriez donc la vérifier!
Konstantino Sparakis
2
La taille du bloc AES est de 128 bits. Dans AES 256, la taille de la clé est de 256 bits. De même, AES 192 et AES 128. De plus, depuis Java 8, la getInstanceStrong()méthode de Cipherest préférable à SHA1PRNG
Saptarshi Basu
110

Je recommanderais d'utiliser un chiffrement symétrique standard largement disponible comme DES , 3DES ou AES . Bien que ce ne soit pas l'algorithme le plus sûr, il existe de nombreuses implémentations et il vous suffit de donner la clé à quiconque est censé déchiffrer les informations du code-barres. javax.crypto.Cipher est ce avec quoi vous voulez travailler ici.

Supposons que les octets à crypter soient

byte[] input;

Ensuite, vous aurez besoin de la clé et des octets du vecteur d'initialisation

byte[] keyBytes;
byte[] ivBytes;

Vous pouvez maintenant initialiser le chiffrement pour l'algorithme que vous sélectionnez:

// wrap key data in Key/IV specs to pass to cipher
SecretKeySpec key = new SecretKeySpec(keyBytes, "DES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// create the cipher with the algorithm you choose
// see javadoc for Cipher class for more info, e.g.
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");

Le cryptage se déroulerait comme ceci:

cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted= new byte[cipher.getOutputSize(input.length)];
int enc_len = cipher.update(input, 0, input.length, encrypted, 0);
enc_len += cipher.doFinal(encrypted, enc_len);

Et un décryptage comme celui-ci:

cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = new byte[cipher.getOutputSize(enc_len)];
int dec_len = cipher.update(encrypted, 0, enc_len, decrypted, 0);
dec_len += cipher.doFinal(decrypted, dec_len);
VoidPointer
la source
9
Puis-je vous suggérer de mettre à jour cet exemple pour référencer l' DESedealgorithme? Puisqu'il s'agit d'une question (et d'une réponse) populaire, il serait dommage d'encourager les gens à utiliser DES, car le chiffrement est si faible par rapport aux normes actuelles.
Duncan Jones
quelque chose ne va pas avec javax.crypto.BadPaddingException: le bloc final n'est pas correctement rempli lors du décryptage
curiosité
2
@Duncan En effet DES est faible mais je suppose qu'AES serait préférable à DESede (alias TipleDES): http://security.stackexchange.com/a/26181/69785
Piovezan
2
Cela devrait être mis à jour pour avoir AES / GCM / NoPadding, DES est vulnérable aux attaques de force brute, TripleDes n'est pas recommandé non plus
Konstantino Sparakis
1
La réponse de Konstantino Sparakis ci-dessous est tellement meilleure que celle-ci.
Steve
22

avertissement

Ne l'utilisez pas comme une sorte de mesure de sécurité.

Le mécanisme de cryptage de cet article est un pad à usage unique, ce qui signifie que la clé secrète peut être facilement récupérée par un attaquant à l'aide de 2 messages cryptés. XOR 2 messages cryptés et vous obtenez la clé. C'est simple!

Signalé par Moussa


J'utilise le Base64Encoder / Decoder de Sun qui se trouve dans le JRE de Sun, pour éviter encore un autre JAR dans la lib. C'est dangereux du point de vue de l'utilisation d'OpenJDK ou du JRE d'un autre. En plus de cela, y a-t-il une autre raison pour laquelle je devrais envisager d'utiliser Apache commons lib avec Encoder / Decoder?

public class EncryptUtils {
    public static final String DEFAULT_ENCODING = "UTF-8"; 
    static BASE64Encoder enc = new BASE64Encoder();
    static BASE64Decoder dec = new BASE64Decoder();

    public static String base64encode(String text) {
        try {
            return enc.encode(text.getBytes(DEFAULT_ENCODING));
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }//base64encode

    public static String base64decode(String text) {
        try {
            return new String(dec.decodeBuffer(text), DEFAULT_ENCODING);
        } catch (IOException e) {
            return null;
        }
    }//base64decode

    public static void main(String[] args) {
        String txt = "some text to be encrypted";
        String key = "key phrase used for XOR-ing";
        System.out.println(txt + " XOR-ed to: " + (txt = xorMessage(txt, key)));

        String encoded = base64encode(txt);       
        System.out.println(" is encoded to: " + encoded + " and that is decoding to: " + (txt = base64decode(encoded)));
        System.out.print("XOR-ing back to original: " + xorMessage(txt, key));
    }

    public static String xorMessage(String message, String key) {
        try {
            if (message == null || key == null) return null;

            char[] keys = key.toCharArray();
            char[] mesg = message.toCharArray();

            int ml = mesg.length;
            int kl = keys.length;
            char[] newmsg = new char[ml];

            for (int i = 0; i < ml; i++) {
                newmsg[i] = (char)(mesg[i] ^ keys[i % kl]);
            }//for i

            return new String(newmsg);
        } catch (Exception e) {
            return null;
        }
    }//xorMessage
}//class
ante.sabo
la source
1
J'ai également utilisé cette proposition de solution via sun.misc.BASE64Encoder, mais lors de l'utilisation de chaînes assez grandes pour encoder, l'encodeur a renvoyé des chaînes fragmentées (76 caractères chacune). Je suis ensuite passé à Apache Commons Codec Base64 qui propose des méthodes d'encodage sans chunking!
basZero
78
Le mécanisme de cryptage que vous avez décrit est TRÈS DANGEREUX s'il est utilisé plus d'une fois. c'est la raison pour laquelle il est appelé One-time pad. La clé secrète peut être facilement récupérée par un attaquant à l'aide de 2 messages chiffrés. xor 2 messages cryptés et vous obtenez la clé. C'est simple!
xtrem
3
Son idée n'est pas d'être lourde, mais simplement d'empêcher les gens d'essayer de lire ce qui est écrit dans les codes à barres 2D PDF-417. Et de toute façon, il n'y a que quelques index qui ne sont cruciaux pour personne ...
ante.sabo
2
D'ACCORD. Je crains simplement que quelqu'un l'utilise comme mécanisme de cryptage.
xtrem
Pour le chiffrement, l'encodeur (par exemple, BASE64Encoder) peut être évité pour avoir des attaques par force brute.
Jagrut Dalwadi
13

merci ive a fait cette classe en utilisant votre code peut-être que quelqu'un la trouve utilisateur

crypteur d'objets

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class ObjectCrypter {

private Cipher deCipher;
private Cipher enCipher;
private SecretKeySpec key;
private IvParameterSpec ivSpec;


public ObjectCrypter(byte[] keyBytes,   byte[] ivBytes) {
    // wrap key data in Key/IV specs to pass to cipher


     ivSpec = new IvParameterSpec(ivBytes);
    // create the cipher with the algorithm you choose
    // see javadoc for Cipher class for more info, e.g.
    try {
         DESKeySpec dkey = new  DESKeySpec(keyBytes);
          key = new SecretKeySpec(dkey.getKey(), "DES");
         deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
         enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
public byte[] encrypt(Object obj) throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, IllegalBlockSizeException, ShortBufferException, BadPaddingException {
    byte[] input = convertToByteArray(obj);
    enCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

    return enCipher.doFinal(input);




//  cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
//  byte[] encypted = new byte[cipher.getOutputSize(input.length)];
//  int enc_len = cipher.update(input, 0, input.length, encypted, 0);
//  enc_len += cipher.doFinal(encypted, enc_len);
//  return encypted;


}
public Object decrypt( byte[]  encrypted) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException, ClassNotFoundException {
    deCipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

    return convertFromByteArray(deCipher.doFinal(encrypted));

}



private Object convertFromByteArray(byte[] byteObject) throws IOException,
        ClassNotFoundException {
    ByteArrayInputStream bais;

    ObjectInputStream in;
    bais = new ByteArrayInputStream(byteObject);
    in = new ObjectInputStream(bais);
    Object o = in.readObject();
    in.close();
    return o;

}



private byte[] convertToByteArray(Object complexObject) throws IOException {
    ByteArrayOutputStream baos;

    ObjectOutputStream out;

    baos = new ByteArrayOutputStream();

    out = new ObjectOutputStream(baos);

    out.writeObject(complexObject);

    out.close();

    return baos.toByteArray();

}


}
shérif
la source
a posté une question connexe ici !
user2023507
Ne devrait-il pas être le cas que la transmission de différentes clés pendant le chiffrement et le déchiffrement ne devrait pas renvoyer le texte? Cela ne semble pas se produire ici. PS: J'utilise différents objets de cette classe pour effectuer ce test.
instanceOfObject
6

Mise à jour le 12-DEC-2019

Contrairement à certains autres modes comme CBC, le mode GCM ne nécessite pas que l'IV soit imprévisible. La seule exigence est que l'IV doit être unique pour chaque invocation avec une clé donnée. S'il se répète une fois pour une clé donnée, la sécurité peut être compromise. Un moyen simple d'y parvenir est d'utiliser un IV aléatoire à partir d'un puissant générateur de nombres pseudo aléatoires comme indiqué ci-dessous.

L'utilisation d'une séquence ou d'un horodatage comme IV est également possible, mais ce n'est peut-être pas aussi trivial que cela puisse paraître. Par exemple, si le système ne conserve pas correctement la trace des séquences déjà utilisées comme IV dans une mémoire persistante, un appel peut répéter un IV après un redémarrage du système. De même, il n'y a pas d'horloge parfaite. Réajuster l'horloge de l'ordinateur, etc.

De plus, la clé doit être tournée tous les 2 ^ 32 appels. Pour plus de détails sur l'exigence IV, reportez-vous à cette réponse et aux recommandations du NIST .


C'est le code de cryptage et de décryptage que je viens d'écrire dans Java 8 en tenant compte des points suivants. J'espère que quelqu'un trouvera cela utile:

  1. Algorithme de chiffrement : le chiffrement par bloc AES avec une clé de 256 bits est considéré comme suffisamment sécurisé. Pour crypter un message complet, un mode doit être sélectionné. Un cryptage authentifié (qui assure à la fois confidentialité et intégrité) est recommandé. GCM, CCM et EAX sont les modes de cryptage authentifiés les plus couramment utilisés. GCM est généralement préféré et il fonctionne bien dans les architectures Intel qui fournissent des instructions dédiées pour GCM. Ces trois modes sont des modes basés sur le CTR (basés sur le compteur) et n'ont donc pas besoin de remplissage. En conséquence, ils ne sont pas vulnérables aux attaques liées au rembourrage

  2. Un vecteur d'initialisation (IV) est requis pour GCM. La IV n'est pas un secret. La seule exigence étant qu'elle doit être aléatoire ou imprévisible. En Java, la SecuredRandomclasse est destinée à produire des nombres pseudo-aléatoires cryptographiquement forts. L'algorithme de génération de nombres pseudo-aléatoires peut être spécifié dans la getInstance()méthode. Cependant, depuis Java 8, la méthode recommandée est d'utiliser la getInstanceStrong()méthode qui utilisera l'algorithme le plus puissant configuré et fourni par leProvider

  3. Le NIST recommande 96 bits IV pour GCM afin de promouvoir l'interopérabilité, l'efficacité et la simplicité de conception

  4. Pour assurer une sécurité supplémentaire, dans l'implémentation suivante SecureRandomest réamorcé après la production tous les 2 ^ 16 octets de génération d'octets pseudo aléatoires

  5. Le destinataire doit connaître l'IV pour pouvoir décrypter le texte chiffré. Par conséquent, l'IV doit être transféré avec le texte chiffré. Certaines implémentations envoient le IV comme AD (Associated Data), ce qui signifie que l'étiquette d'authentification sera calculée à la fois sur le texte chiffré et sur l'IV. Cependant, ce n'est pas obligatoire. Le IV peut être simplement pré-suspendu avec le texte chiffré car si l'IV est changé pendant la transmission en raison d'une attaque délibérée ou d'une erreur de réseau / système de fichiers, la validation de la balise d'authentification échouera de toute façon.

  6. Les chaînes ne doivent pas être utilisées pour contenir le message en texte clair ou la clé car les chaînes sont immuables et nous ne pouvons donc pas les effacer après utilisation. Ces chaînes non effacées persistent ensuite dans la mémoire et peuvent apparaître dans un vidage de tas. Pour la même raison, le client appelant ces méthodes de cryptage ou de décryptage doit effacer toutes les variables ou tableaux contenant le message ou la clé une fois qu'ils ne sont plus nécessaires.

  7. Aucun fournisseur n'est codé en dur dans le code suivant les recommandations générales

  8. Enfin, pour la transmission sur le réseau ou le stockage, la clé ou le texte chiffré doit être codé en utilisant le codage Base64. Les détails de Base64 peuvent être trouvés ici . L'approche Java 8 doit être suivie

Les tableaux d'octets peuvent être effacés en utilisant:

Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);

Cependant, à partir de Java 8, il n'y a pas de moyen simple de clarifier SecretKeyspecet SecretKeycomme les implémentations de ces deux interfaces ne semblent pas avoir implémenté la méthode destroy()de l'interface Destroyable. Dans le code suivant, une méthode distincte est écrite pour effacer SecretKeySpecet SecretKeyutiliser la réflexion.

La clé doit être générée en utilisant l'une des deux approches mentionnées ci-dessous.

Notez que les clés sont des secrets comme les mots de passe, mais contrairement aux mots de passe destinés à un usage humain, les clés sont destinées à être utilisées par des algorithmes cryptographiques et doivent donc être générées en utilisant uniquement la méthode ci-dessus.

package com.sapbasu.javastudy;

import java.lang.reflect.Field;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Crypto {

  private static final int AUTH_TAG_SIZE = 128; // bits

  // NIST recommendation: "For IVs, it is recommended that implementations
  // restrict support to the length of 96 bits, to
  // promote interoperability, efficiency, and simplicity of design."
  private static final int IV_LEN = 12; // bytes

  // number of random number bytes generated before re-seeding
  private static final double PRNG_RESEED_INTERVAL = Math.pow(2, 16);

  private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";

  private static final List<Integer> ALLOWED_KEY_SIZES = Arrays
      .asList(new Integer[] {128, 192, 256}); // bits

  private static SecureRandom prng;

  // Used to keep track of random number bytes generated by PRNG
  // (for the purpose of re-seeding)
  private static int bytesGenerated = 0;

  public byte[] encrypt(byte[] input, SecretKeySpec key) throws Exception {

    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Length of message cannot be 0");
    }

    if (!ALLOWED_KEY_SIZES.contains(key.getEncoded().length * 8)) {
      throw new IllegalArgumentException("Size of key must be 128, 192 or 256");
    }

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

    byte[] iv = getIV(IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    cipher.init(Cipher.ENCRYPT_MODE, key, gcmParamSpec);
    byte[] messageCipher = cipher.doFinal(input);

    // Prepend the IV with the message cipher
    byte[] cipherText = new byte[messageCipher.length + IV_LEN];
    System.arraycopy(iv, 0, cipherText, 0, IV_LEN);
    System.arraycopy(messageCipher, 0, cipherText, IV_LEN,
        messageCipher.length);
    return cipherText;
  }

  public byte[] decrypt(byte[] input, SecretKeySpec key) throws Exception {
    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Input array cannot be empty");
    }

    byte[] iv = new byte[IV_LEN];
    System.arraycopy(input, 0, iv, 0, IV_LEN);

    byte[] messageCipher = new byte[input.length - IV_LEN];
    System.arraycopy(input, IV_LEN, messageCipher, 0, input.length - IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
    cipher.init(Cipher.DECRYPT_MODE, key, gcmParamSpec);

    return cipher.doFinal(messageCipher);
  }

  public byte[] getIV(int bytesNum) {

    if (bytesNum < 1) throw new IllegalArgumentException(
        "Number of bytes must be greater than 0");

    byte[] iv = new byte[bytesNum];

    prng = Optional.ofNullable(prng).orElseGet(() -> {
      try {
        prng = SecureRandom.getInstanceStrong();
      } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Wrong algorithm name", e);
      }
      return prng;
    });

    if (bytesGenerated > PRNG_RESEED_INTERVAL || bytesGenerated == 0) {
      prng.setSeed(prng.generateSeed(bytesNum));
      bytesGenerated = 0;
    }

    prng.nextBytes(iv);
    bytesGenerated = bytesGenerated + bytesNum;

    return iv;
  }

  private static void clearSecret(Destroyable key)
      throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    Field keyField = key.getClass().getDeclaredField("key");
    keyField.setAccessible(true);
    byte[] encodedKey = (byte[]) keyField.get(key);
    Arrays.fill(encodedKey, Byte.MIN_VALUE);
  }
}

La clé de chiffrement peut être générée principalement de deux manières:

  • Sans aucun mot de passe

    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong());
    SecretKey secretKey = keyGen.generateKey();
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);
  • Avec mot de passe

    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[32];
    random.nextBytes(salt);
    PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterations, 
       keyLength);
    SecretKeyFactory keyFactory = 
        SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    SecretKey secretKey = keyFactory.generateSecret(keySpec);
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);

Mise à jour basée sur les commentaires

Comme l'a souligné @MaartenBodewes, ma réponse n'en a pas traité Stringcomme l'exige la question. Par conséquent, je vais essayer de combler cette lacune au cas où quelqu'un trébucherait sur cette réponse et s'interrogerait sur la manipulation String.

Comme indiqué précédemment dans la réponse, la gestion des informations sensibles dans a Stringn'est, en général, pas une bonne idée car elle Stringest immuable et donc nous ne pouvons pas l'effacer après utilisation. Et comme nous le savons, même si a Stringn'a pas de référence forte, le garbage collector ne se précipite pas immédiatement pour le supprimer du tas. Ainsi, le Stringcontinue à être présent dans la mémoire pendant une fenêtre de temps inconnue même s'il n'est pas accessible au programme. Le problème avec cela est qu'un vidage de tas pendant cette période révélerait les informations sensibles. Par conséquent, il est toujours préférable de gérer toutes les informations sensibles dans un tableau d'octets ou un tableau de caractères, puis de remplir le tableau avec des 0 une fois leur objectif atteint.

Cependant, avec toutes ces connaissances, si nous nous retrouvons toujours dans une situation où les informations sensibles à chiffrer sont dans a String, nous devons d'abord les convertir en un tableau d'octets et invoquer les fonctions encryptet decryptprésentées ci-dessus. (L'autre clé d'entrée peut être générée à l'aide de l'extrait de code fourni ci-dessus).

A Stringpeut être converti en octets de la manière suivante:

byte[] inputBytes = inputString.getBytes(StandardCharsets.UTF_8);

À partir de Java 8, Stringest stocké en interne dans le tas avec UTF-16encodage. Cependant, nous l'avons utilisé UTF-8ici car il prend généralement moins d'espace que UTF-16, en particulier pour les caractères ASCII.

De même, le tableau d'octets chiffré peut également être converti en une chaîne comme ci-dessous:

String encryptedString = new String(encryptedBytes, StandardCharsets.UTF_8);
Saptarshi Basu
la source
1
Autant je veux voter pour cette réponse car elle semble adhérer aux pratiques cryptographiques actuelles, je ne vois pas du tout de gestion de chaîne, ce qui en fait plutôt une description sur la façon d'utiliser le mode GCM. En tant que tel, il ne répond pas à la question .
Maarten Bodewes
1
@MaartenBodewes Merci beaucoup d'avoir pris le temps de revoir et de partager vos commentaires. J'ai écrit ceci en sachant que chiffrer un en Stringutilisant les fonctions créées ci-dessus serait trivial. Cependant, après avoir lu votre commentaire, je comprends que ce n'est peut-être pas évident. Je vais sûrement modifier pour ajouter ces détails.
Saptarshi Basu
5

Que dis-tu de ça:

private static byte[] xor(final byte[] input, final byte[] secret) {
    final byte[] output = new byte[input.length];
    if (secret.length == 0) {
        throw new IllegalArgumentException("empty security key");
    }
    int spos = 0;
    for (int pos = 0; pos < input.length; ++pos) {
        output[pos] = (byte) (input[pos] ^ secret[spos]);
        ++spos;
        if (spos >= secret.length) {
            spos = 0;
        }
    }
    return output;
}

Fonctionne bien pour moi et est plutôt compact.

yegor256
la source
que se passera-t-il si le paramètre d'entrée secret == null ou input == null? travailler avec des octets plutôt qu'avec des chaînes est correct, mais n'était pas pertinent dans mon cas .. la seule chose qui compte, c'est que cela doit être lisible et décodable avec n'importe quel appareil, dans n'importe quel encodage de caractères possible ...
ante.sabo
@ ante.sabo apparemment, il lancera un NPE. C'est la seule chose à faire avec les NULL.
Miha_x64
Tant que input.length <= secret.lengthtient et no secretn'est jamais réutilisé, cela est sécurisé et appelé a one-time-pad. Dans ce cas, input.length > secret.lengthil s'agit d'une variante du chiffre de Vigenère et considérée comme très faible.
trichner le
5

Vous pouvez utiliser Jasypt

Avec Jasypt, crypter et vérifier un mot de passe peut être aussi simple que ...

StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
textEncryptor.setPassword(myEncryptionPassword);

Chiffrement:

String myEncryptedText = textEncryptor.encrypt(myText);

Décryptage:

String plainText = textEncryptor.decrypt(myEncryptedText);

Gradle:

compile group: 'org.jasypt', name: 'jasypt', version: '1.9.2'

Fonctionnalités:

Jasypt vous fournit des techniques de cryptage unidirectionnel (digest) et bidirectionnel faciles.

Ouvrez l'API à utiliser avec n'importe quel fournisseur JCE, et pas seulement avec la VM Java par défaut. Jasypt peut être facilement utilisé avec des fournisseurs bien connus comme Bouncy Castle. Apprendre encore plus.

Sécurité accrue pour les mots de passe de vos utilisateurs. Apprendre encore plus.

Prise en charge du cryptage binaire. Jasypt permet le condensé et le cryptage des binaires (tableaux d'octets). Cryptez vos objets ou fichiers en cas de besoin (pour être envoyés sur le net, par exemple).

Prise en charge du cryptage des numéros. Outre les textes et les binaires, il permet le résumé et le cryptage des valeurs numériques (BigInteger et BigDecimal, d'autres types numériques sont pris en charge lors du cryptage pour la persistance Hibernate). Apprendre encore plus.

Complètement thread-safe.

Prise en charge de la mise en commun des chiffreurs / digesteurs, afin d'obtenir des performances élevées dans les systèmes multiprocesseurs / multicœurs.

Inclut une version légère («allégée») de la bibliothèque pour une meilleure gestion dans les environnements à taille restreinte comme les plates-formes mobiles.

Fournit à la fois des outils de cryptage simples et sans configuration pour les utilisateurs novices en cryptage, ainsi que des outils de cryptage standard hautement configurables, pour les utilisateurs expérimentés.

Intégration optionnelle Hibernate 3 et 4 pour les champs persistants de vos entités mappées de manière cryptée. Le chiffrement des champs est défini dans les fichiers de mappage Hibernate, et il reste transparent pour le reste de l'application (utile pour les données personnelles sensibles, les bases de données avec de nombreux utilisateurs en lecture ...). Crypter les textes, binaires, nombres, booléens, dates ... En savoir plus.

Intégration transparente dans une application Spring, avec des fonctionnalités d'intégration spécifiques pour Spring 2, Spring 3.0 et Spring 3.1. Tous les digesteurs et chiffreurs de jasypt sont conçus pour être facilement utilisés (instanciés, injectés de dépendances ...) à partir de Spring. Et, en raison de leur sécurité pour les threads, ils peuvent être utilisés sans soucis de synchronisation dans un environnement orienté singleton comme Spring. En savoir plus: Spring 2, Spring 3.0, Spring 3.1.

Intégration facultative de Spring Security (anciennement Acegi Security) pour effectuer le cryptage de mot de passe et les tâches de correspondance pour le cadre de sécurité, améliorant la sécurité des mots de passe de vos utilisateurs en utilisant des mécanismes de cryptage de mot de passe plus sûrs et vous offrant un degré plus élevé de configuration et de contrôle. Apprendre encore plus.

Fournit des fonctionnalités avancées pour crypter tout ou partie des fichiers de configuration d'une application, y compris des informations sensibles telles que les mots de passe de base de données. Intégrez de manière transparente la configuration chiffrée dans des applications simples, basées sur Spring et / ou compatibles Hibernate. Apprendre encore plus.

Fournit des outils CLI (Command Line Interface) faciles à utiliser pour permettre aux développeurs d'initialiser leurs données cryptées et d'inclure des opérations de cryptage / décryptage / digest dans les tâches ou scripts de maintenance. Apprendre encore plus.

S'intègre à Apache Wicket, pour un cryptage plus robuste des URL dans vos applications sécurisées.

Guides complets et documentation javadoc, pour permettre aux développeurs de mieux comprendre ce qu'ils font réellement à leurs données.

Prise en charge robuste des jeux de caractères, conçue pour crypter et digérer correctement les textes quel que soit le jeu de caractères d'origine. Prise en charge complète des langues comme le japonais, le coréen, l'arabe ... sans problèmes d'encodage ou de plate-forme.

Très haut niveau de capacités de configuration: le développeur peut implémenter des astuces telles que demander à un "crypteur" de demander, par exemple, à un serveur HTTPS distant le mot de passe à utiliser pour le cryptage. Il vous permet de répondre à vos besoins de sécurité.

Kamil Nekanowicz
la source
1
Mais quelle sécurité Jasyptfournit-elle? Je ne peux pas le comprendre sur leur site Web. Est-il impossible de le distinguer sous les attaques en clair choisi? Intégrité? Confidentialité?
trichner
4

Voici mon implémentation de meta64.com en tant que Spring Singleton. Si vous voulez créer une instance de ciper pour chaque appel qui fonctionnerait également, vous pouvez supprimer les appels «synchronisés», mais attention «cipher» n'est pas thread-safe.

import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("singleton")
public class Encryptor {

    @Value("${aeskey}")
    private String keyStr;

    private Key aesKey = null;
    private Cipher cipher = null;

    synchronized private void init() throws Exception {
        if (keyStr == null || keyStr.length() != 16) {
            throw new Exception("bad aes key configured");
        }
        if (aesKey == null) {
            aesKey = new SecretKeySpec(keyStr.getBytes(), "AES");
            cipher = Cipher.getInstance("AES");
        }
    }

    synchronized public String encrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);
        return toHexString(cipher.doFinal(text.getBytes()));
    }

    synchronized public String decrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.DECRYPT_MODE, aesKey);
        return new String(cipher.doFinal(toByteArray(text)));
    }

    public static String toHexString(byte[] array) {
        return DatatypeConverter.printHexBinary(array);
    }

    public static byte[] toByteArray(String s) {
        return DatatypeConverter.parseHexBinary(s);
    }

    /*
     * DO NOT DELETE
     * 
     * Use this commented code if you don't like using DatatypeConverter dependency
     */
    // public static String toHexStringOld(byte[] bytes) {
    // StringBuilder sb = new StringBuilder();
    // for (byte b : bytes) {
    // sb.append(String.format("%02X", b));
    // }
    // return sb.toString();
    // }
    //
    // public static byte[] toByteArrayOld(String s) {
    // int len = s.length();
    // byte[] data = new byte[len / 2];
    // for (int i = 0; i < len; i += 2) {
    // data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i +
    // 1), 16));
    // }
    // return data;
    // }
}
mkobit
la source
3
Cela chiffrera avec le mode ECB, ce qui est horrible. Vous devriez régler au moins le mode CBC ou le mode GCM
Konstantino Sparakis
Merci pour la suggestion Konstantinto, j'ai cherché sur Google et trouvé du code qui utilise "AES / CBC / PKCS5Padding" comme chaîne d'initialisation pour Cipher, au lieu de simplement "AES", mais je vais l'examiner plus en détail. Ou si vous le souhaitez, vous pouvez fournir le correctif réel, afin que les autres puissent voir la meilleure solution. Cependant, mis à part le détail de la SRC, je crois que ma solution est la plus simple et la plus sûre, et mérite d'être votée avant tout.
Oui pas de soucis, Crypto est un sujet compliqué. Malheureusement, toutes les implémentations de cette page sont interrompues et malheureusement, c'est la première page qui apparaît lorsque vous utilisez Google pour rechercher «comment faire le cryptage Java». Quand j'en aurai l'occasion, j'essaierai de tous les réparer.
Konstantino Sparakis
Mon exemple est le même que celui-ci: docs.oracle.com/javase/8/docs/technotes/guides/security/crypto / ... Sauf que j'avais besoin de Cipher.getInstance ("AES / ECB / PKCS5Padding"); Mon code suppose qu'il existe un fichier de propriétés avec une clé de cryptage parfaitement longue de 16 octets, mais pour crypter une chaîne à partir d'un mot de passe `` fourni par l'utilisateur '', la page oracle (liée ci-dessus) montre également la manière de le faire.
1
Le problème avec ECB est donc qu'il est extrêmement vulnérable à l'analyse de fréquence. Il y a le célèbre exemple du pingouin Linux, blog.filippo.io/the-ecb-penguin voyez comment bien que l'image soit cryptée, vous pouvez toujours dire que c'est un pingouin. Je suis allé de l'avant et j'ai écrit mes réflexions sur le sujet ci-dessous :) stackoverflow.com/a/43779197/2607972
Konstantino Sparakis
4

Voici une solution simple avec uniquement java.*et javax.crypto.*dépendances pour le cryptage des octets assurant confidentialité et intégrité . Il ne doit pas être distingué sous une attaque de texte en clair choisie pour les messages courts de l'ordre de kilo-octets.

Il utilise AESdans le GCMmode sans remplissage, une clé de 128 bits est dérivée PBKDF2avec de nombreuses itérations et un sel statique du mot de passe fourni. Cela garantit que le forçage brutal des mots de passe est difficile et distribue l'entropie sur toute la clé.

Un vecteur d'initialisation aléatoire (IV) est généré et sera ajouté au texte chiffré. De plus, l'octet statique 0x01est ajouté comme premier octet en tant que «version».

Le message entier entre dans le code d'authentification de message (MAC) généré par AES/GCM.

Voilà, aucune classe de chiffrement de dépendances externes assurant la confidentialité et l' intégrité :

package ch.n1b.tcrypt.utils;

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * This class implements AES-GCM symmetric key encryption with a PBKDF2 derived password.
 * It provides confidentiality and integrity of the plaintext.
 *
 * @author Thomas Richner
 * @created 2018-12-07
 */
public class AesGcmCryptor {

    // /crypto/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode
    private static final byte VERSION_BYTE = 0x01;
    private static final int VERSION_BYTE_LENGTH = 1;
    private static final int AES_KEY_BITS_LENGTH = 128;


    // fixed AES-GCM constants
    private static final String GCM_CRYPTO_NAME = "AES/GCM/NoPadding";
    private static final int GCM_IV_BYTES_LENGTH = 12;
    private static final int GCM_TAG_BYTES_LENGTH = 16;

    // can be tweaked, more iterations = more compute intensive to brute-force password
    private static final int PBKDF2_ITERATIONS = 1024;

    // protects against rainbow tables
    private static final byte[] PBKDF2_SALT = hexStringToByteArray("4d3fe0d71d2abd2828e7a3196ea450d4");

    public String encryptString(char[] password, String plaintext) throws CryptoException {

        byte[] encrypted = null;
        try {
            encrypted = encrypt(password, plaintext.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException //
                | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException //
                | InvalidKeySpecException e) {
            throw new CryptoException(e);
        }
        return byteArrayToHexString(encrypted);
    }

    public String decryptString(char[] password, String ciphertext)
            throws CryptoException {

        byte[] ct = hexStringToByteArray(ciphertext);
        byte[] plaintext = null;
        try {
            plaintext = decrypt(password, ct);
        } catch (AEADBadTagException e) {
            throw new CryptoException(e);
        } catch ( //
                NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeySpecException //
                        | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException //
                        | BadPaddingException e) {
            throw new CryptoException(e);
        }
        return new String(plaintext, StandardCharsets.UTF_8);
    }

    /**
     * Decrypts an AES-GCM encrypted ciphertext and is
     * the reverse operation of {@link AesGcmCryptor#encrypt(char[], byte[])}
     *
     * @param password   passphrase for decryption
     * @param ciphertext encrypted bytes
     * @return plaintext bytes
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws IllegalArgumentException           if the length or format of the ciphertext is bad
     * @throws CryptoException
     */
    public byte[] decrypt(char[] password, byte[] ciphertext)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        // input validation
        if (ciphertext == null) {
            throw new IllegalArgumentException("ciphertext cannot be null");
        }

        if (ciphertext.length <= VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH + GCM_TAG_BYTES_LENGTH) {
            throw new IllegalArgumentException("ciphertext too short");
        }

        // the version must match, we don't decrypt other versions
        if (ciphertext[0] != VERSION_BYTE) {
            throw new IllegalArgumentException("wrong version: " + ciphertext[0]);
        }

        // input seems legit, lets decrypt and check integrity

        // derive key from password
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);

        // init cipher
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec params = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8,
                ciphertext,
                VERSION_BYTE_LENGTH,
                GCM_IV_BYTES_LENGTH
        );
        cipher.init(Cipher.DECRYPT_MODE, key, params);

        final int ciphertextOffset = VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH;

        // add version and IV to MAC
        cipher.updateAAD(ciphertext, 0, ciphertextOffset);

        // decipher and check MAC
        return cipher.doFinal(ciphertext, ciphertextOffset, ciphertext.length - ciphertextOffset);
    }

    /**
     * Encrypts a plaintext with a password.
     * <p>
     * The encryption provides the following security properties:
     * Confidentiality + Integrity
     * <p>
     * This is achieved my using the AES-GCM AEAD blockmode with a randomized IV.
     * <p>
     * The tag is calculated over the version byte, the IV as well as the ciphertext.
     * <p>
     * Finally the encrypted bytes have the following structure:
     * <pre>
     *          +-------------------------------------------------------------------+
     *          |         |               |                             |           |
     *          | version | IV bytes      | ciphertext bytes            |    tag    |
     *          |         |               |                             |           |
     *          +-------------------------------------------------------------------+
     * Length:     1B        12B            len(plaintext) bytes            16B
     * </pre>
     * Note: There is no padding required for AES-GCM, but this also implies that
     * the exact plaintext length is revealed.
     *
     * @param password  password to use for encryption
     * @param plaintext plaintext to encrypt
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidKeySpecException
     */
    public byte[] encrypt(char[] password, byte[] plaintext)
            throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException,
            InvalidKeySpecException {

        // initialise random and generate IV (initialisation vector)
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);
        final byte[] iv = new byte[GCM_IV_BYTES_LENGTH];
        SecureRandom random = SecureRandom.getInstanceStrong();
        random.nextBytes(iv);

        // encrypt
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);

        // add IV to MAC
        final byte[] versionBytes = new byte[]{VERSION_BYTE};
        cipher.updateAAD(versionBytes);
        cipher.updateAAD(iv);

        // encrypt and MAC plaintext
        byte[] ciphertext = cipher.doFinal(plaintext);

        // prepend VERSION and IV to ciphertext
        byte[] encrypted = new byte[1 + GCM_IV_BYTES_LENGTH + ciphertext.length];
        int pos = 0;
        System.arraycopy(versionBytes, 0, encrypted, 0, VERSION_BYTE_LENGTH);
        pos += VERSION_BYTE_LENGTH;
        System.arraycopy(iv, 0, encrypted, pos, iv.length);
        pos += iv.length;
        System.arraycopy(ciphertext, 0, encrypted, pos, ciphertext.length);

        return encrypted;
    }

    /**
     * We derive a fixed length AES key with uniform entropy from a provided
     * passphrase. This is done with PBKDF2/HMAC256 with a fixed count
     * of iterations and a provided salt.
     *
     * @param password passphrase to derive key from
     * @param salt     salt for PBKDF2 if possible use a per-key salt, alternatively
     *                 a random constant salt is better than no salt.
     * @param keyLen   number of key bits to output
     * @return a SecretKey for AES derived from a passphrase
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    private SecretKey deriveAesKey(char[] password, byte[] salt, int keyLen)
            throws NoSuchAlgorithmException, InvalidKeySpecException {

        if (password == null || salt == null || keyLen <= 0) {
            throw new IllegalArgumentException();
        }
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, keyLen);
        SecretKey pbeKey = factory.generateSecret(spec);

        return new SecretKeySpec(pbeKey.getEncoded(), "AES");
    }

    /**
     * Helper to convert hex strings to bytes.
     * <p>
     * May be used to read bytes from constants.
     */
    private static byte[] hexStringToByteArray(String s) {

        if (s == null) {
            throw new IllegalArgumentException("Provided `null` string.");
        }

        int len = s.length();
        if (len % 2 != 0) {
            throw new IllegalArgumentException("Invalid length: " + len);
        }

        byte[] data = new byte[len / 2];
        for (int i = 0; i < len - 1; i += 2) {
            byte b = (byte) toHexDigit(s, i);
            b <<= 4;
            b |= toHexDigit(s, i + 1);
            data[i / 2] = b;
        }
        return data;
    }

    private static int toHexDigit(String s, int pos) {
        int d = Character.digit(s.charAt(pos), 16);
        if (d < 0) {
            throw new IllegalArgumentException("Cannot parse hex digit: " + s + " at " + pos);
        }
        return d;
    }

    private static String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }

    public class CryptoException extends Exception {

        public CryptoException(Throwable cause) {
            super(cause);
        }
    }
}

Voici l'ensemble du projet avec une belle CLI: https://github.com/trichner/tcrypt

Edit: maintenant avec approprié encryptStringetdecryptString

trichner
la source
C'est incroyable. Je vous remercie! J'ai beaucoup appris de votre code et après avoir créé la classe BadVersionException Exception, votre code a parfaitement fonctionné la première fois. Excellent!!
Morkus
J'aime cette tentative. Cela dit ... Le sel doit être aléatoire, pas statique. Les itérations ne devraient probablement pas non plus être statiques. GCM inclut déjà l'IV dans le calcul du tag. Il ne contient cependant pas le numéro de version. Vous ne devez pas spécifier le fournisseur pour la portabilité, celui "SunJCE" sera celui par défaut sur les plates-formes qui le supportent. Ce code ne contient aucune gestion de chaîne de message, qui est requise pour cette question particulière .
Maarten Bodewes
D'accord, je l'ai nettoyé un peu plus et ajouté le demandé encryptStringet decryptString:)
trichner
Cela a très bien fonctionné; ty pour le code. Il convient de noter que ce code nécessite l'API 19 (Kit Kat) ou supérieur pour fonctionner correctement.
PGMacDesign
3

J'envisagerais d'utiliser quelque chose comme https://www.bouncycastle.org/ C'est une bibliothèque prédéfinie qui vous permet de crypter ce que vous voulez avec un certain nombre de chiffrements différents, je comprends que vous voulez seulement vous protéger contre l'espionnage, mais si vous vraiment veulent protéger les informations, l'utilisation de Base64 ne vous protégera pas réellement.

hdost
la source
1
Le simple fait de recommander une bibliothèque cryptographique aléatoire avec des chiffrements n'est pas une réponse à la question. En plus de cela, pourquoi ne pas utiliser les chiffrements intégrés?
Maarten Bodewes
2

Voici quelques liens vous permettant de lire ce que Java prend en charge

Crypter / décrypter un flux de données.

Cet exemple montre comment chiffrer (à l'aide d'un algorithme de chiffrement symétrique tel que AES, Blowfish, RC2, 3DES, etc.) une grande quantité de données. Les données sont transmises par blocs à l'une des méthodes de chiffrement: EncryptBytes, EncryptString, EncryptBytesENC ou EncryptStringENC. (Le nom de la méthode indique le type d'entrée (chaîne ou tableau d'octets) et le type de retour (chaîne codée ou tableau d'octets). Les propriétés FirstChunk et LastChunk sont utilisées pour indiquer si un bloc est le premier, le milieu ou le dernier d'un flux à chiffrer. Par défaut, FirstChunk et LastChunk sont tous deux égaux à true, ce qui signifie que les données transmises correspondent au montant total.

JCERefGuide

Exemples de chiffrement Java

Markus Lausberg
la source
Oui, la cryptographie est prise en charge par Java. Le chiffrement d'un flux n'est pas non plus ce qui a été demandé.
Maarten Bodewes
2

Comme beaucoup de gars l'ont déjà dit, vous devriez utiliser un chiffrement standard qui est trop utilisé comme DES ou AES.

Un exemple simple de la façon dont vous pouvez crypter et décrypter une chaîne en java à l'aide d' AES .

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class EncryptorDemo {

    public static String encrypt(String key, String randomVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted text: "  + Base64.encodeBase64String(encrypted));
            return Base64.encodeBase64String(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String randomVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            byte[] originalText = cipher.doFinal(Base64.decodeBase64(encrypted));
            System.out.println("decrypted text: "  + new String(originalText));
            return new String(originalText);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        String key = "JavasEncryptDemo"; // 128 bit key
        String randomVector = "RandomJavaVector"; // 16 bytes IV
        decrypt(key, randomVector, encrypt(key, randomVector, "Anything you want to encrypt!"));

    }
}
viveknaskar
la source
CBC n'est plus un mode sécurisé. Le remplissage est vulnérable aux attaques Oracle de remplissage. De plus, la gestion de la clé et des messages dans String n'est pas sûre. Ils s'attarderont dans le pool de cordes et apparaîtront dans un dépotoir
Saptarshi Basu
2
Appréciez le commentaire. C'était un exemple simple des méthodes de chiffrement et de déchiffrement de Java, comme le demandait l'utilisateur. La question a été posée il y a environ 9 ans et on y a répondu. Merci.
viveknaskar
2
Oui, cela semble un moyen simple d'introduire chiffrer / déchiffrer. A fonctionné comme un charme pour moi ... Merci.
Codewrapper
0

Voici une solution de copier / coller. Je recommande également de lire et de voter pour la réponse de @ Konstantino même si elle ne fournit aucun code. Le vecteur d'initialisation (IV) est comme un sel - il ne doit pas être gardé secret. Je suis nouveau sur GCM et apparemment AAD est facultatif et n'est utilisé que dans certaines circonstances. Définissez la clé dans la variable d'environnement SECRET_KEY_BASE. Utilisez quelque chose comme KeePass pour générer un mot de passe de 32 caractères. Cette solution est calquée sur ma solution Ruby.

    public static String encrypt(String s) {
        try {
            byte[] input = s.getBytes("UTF-8");
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            // generate IV
            SecureRandom secureRandom = SecureRandom.getInstanceStrong();
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            secureRandom.nextBytes(ivBytes);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes); // 96 bit tag length
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
            // generate AAD
//          byte[] aadBytes = new byte[cipher.getBlockSize()];
//          secureRandom.nextBytes(aadBytes);
//          cipher.updateAAD(aadBytes);
            // encrypt
            byte[] encrypted = cipher.doFinal(input);
            byte[] returnBytes = new byte[ivBytes.length + encrypted.length];
//          byte[] returnBytes = new byte[ivBytes.length + aadBytes.length + encrypted.length];
            System.arraycopy(ivBytes, 0, returnBytes, 0, ivBytes.length);
//          System.arraycopy(aadBytes, 0, returnBytes, ivBytes.length, aadBytes.length);
            System.arraycopy(encrypted, 0, returnBytes, ivBytes.length, encrypted.length);
//          System.arraycopy(encrypted, 0, returnBytes, ivBytes.length+aadBytes.length, encrypted.length);
            String encryptedString = Base64.getEncoder().encodeToString(returnBytes);
            return encryptedString;
        } catch (UnsupportedEncodingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "encrypt()", "Could not encrypt string: " + e.getMessage());
            return null;
        }
    }

    public static String decrypt(String s) {
        if (s == null || s.length() == 0) return "";
        try {
            byte[] encrypted = Base64.getDecoder().decode(s);
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            System.arraycopy(encrypted, 0, ivBytes, 0, ivBytes.length);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
//          cipher.updateAAD(encrypted, ivBytes.length, cipher.getBlockSize());
            byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize(), encrypted.length - cipher.getBlockSize());
//          byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize()*2, encrypted.length - cipher.getBlockSize()*2);
            String decryptedString = new String(decrypted, "UTF-8");
            return decryptedString;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedEncodingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "decrypt()", "Could not decrypt string: " + e.getMessage());
            return null;
        }
    }

Voici un exemple:

    String s = "This is a test.";
    String enc = Utils.encrypt(s);
    System.out.println(enc);
    // fQHfYjbD+xAuN5XzH2ojk/EWNeKXUrKRSfx8LU+5dpuKkM/pueCMBjKCZw==
    String dec = Utils.decrypt(enc);
    System.out.println(dec);
    // This is a test.
Chloe
la source
-4

Vous voudrez peut-être envisager un outil automatisé pour faire la génération de code de cryptage / décryptage, par exemple. https://www.stringencrypt.com/java-encryption/

Il peut générer différents codes de cryptage et de décryptage à chaque fois pour le cryptage de la chaîne ou du fichier.

C'est assez pratique pour le cryptage rapide des chaînes sans utiliser RSA, AES, etc.

Exemples de résultats:

// encrypted with https://www.stringencrypt.com (v1.1.0) [Java]
// szTest = "Encryption in Java!"
String szTest = "\u9E3F\uA60F\uAE07\uB61B\uBE1F\uC62B\uCE2D\uD611" +
                "\uDE03\uE5FF\uEEED\uF699\uFE3D\u071C\u0ED2\u1692" +
                "\u1E06\u26AE\u2EDC";

for (int iatwS = 0, qUJQG = 0; iatwS < 19; iatwS++)
{
        qUJQG = szTest.charAt(iatwS);
        qUJQG ++;
        qUJQG = ((qUJQG << 5) | ( (qUJQG & 0xFFFF) >> 11)) & 0xFFFF;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 6) | (qUJQG << 10)) & 0xFFFF;
        qUJQG ^= iatwS;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 3) | (qUJQG << 13)) & 0xFFFF;
        qUJQG ^= 0xFFFF;
        qUJQG ^= 0xB6EC;
        qUJQG = ((qUJQG << 8) | ( (qUJQG & 0xFFFF) >> 8)) & 0xFFFF;
        qUJQG --;
        qUJQG = (((qUJQG & 0xFFFF) >> 5) | (qUJQG << 11)) & 0xFFFF;
        qUJQG ++;
        qUJQG ^= 0xFFFF;
        qUJQG += iatwS;
        szTest = szTest.substring(0, iatwS) + (char)(qUJQG & 0xFFFF) + szTest.substring(iatwS + 1);
}

System.out.println(szTest);

Nous l'utilisons tout le temps dans notre entreprise.

Bartosz Wójcik
la source
C'est la sécurité par l'obscurité et ce n'est pas vraiment sécurisé.
Chloe
Cette question demande un véritable cryptage moderne de la force cryptographique comme AES, pas seulement une obfuscation pour rendre les chaînes plus difficiles à extraire statiquement. Cela ne semble même pas conserver un état entre les caractères, ce qui le rend sensible à l'analyse de fréquence. (Chiffre de substitution à un seul alphabet , sauf sur les points de code UTF-16 au lieu de l'alphabet latin. Mais si vous l'utilisez sur du texte ASCII anglais, vous n'obtiendrez que quelques valeurs de caractères 16 bits uniques, à moins que je ne me trompe)
Peter Cordes
-4
String s1="arshad"; 
char[] s2=s1.toCharArray(); 
int s3= s2.length; 

  System.out.println(s3);
 int i=0; 

// for(int j=0;j<s3;j++) 
// System.out.println(s2[j]); 

for(i=0;i<((s3)/2);i++) { 

char z,f=10; 
z=(char) (s2[i] * f); 
s2[i]=s2[(s3-1)-i]; 
s2[(s3-1)-i]=z; 

String b=new String(s2);

 print(b);  }
Arshad shaik
la source
Formellement, il crypte les données dans un format illisible. Pour déchiffrer, utilisez le même code. Et changez s [i] * f en s [I] / f.
Arshad shaik
C'est la sécurité par l'obscurité et ce n'est pas vraiment sécurisé.
Chloe
-5
public static String encryptParams(String myTextInput) {

        String myKey = "40674244454045cb9a70040a30e1c007";
        String myVector = "@1B2c3D4e5F6g7H8";

        String encData = "";

        try{
            JavaEncryprtionUtil encUtil = new JavaEncryprtionUtil();
            encData = Base64.encodeToString(encUtil.encrypt(myTextInput.getBytes("UTF-8"), myKey.getBytes("UTF-8"), myVector.getBytes("UTF-8")),Base64.DEFAULT);
            System.out.println(encData);
        }catch(NoSuchAlgorithmException ex){
            ex.printStackTrace();
        }catch(NoSuchPaddingException ex){
            ex.printStackTrace();
        }catch(InvalidKeyException ex){
            ex.printStackTrace();
        }catch(InvalidAlgorithmParameterException ex){
            ex.printStackTrace();
        }catch(IllegalBlockSizeException ex){
            ex.printStackTrace();
        }catch(BadPaddingException ex){
            ex.printStackTrace();
        }catch(UnsupportedEncodingException ex){
            ex.printStackTrace();
        }

        return encData;
    }
Rishikesh
la source
1
JavaEncryprtionUtil fait-il partie de l'API JDK? sinon, vous devez épeler le nom de la bibliothèque.
Fermat's Little Student
4
Je ne trouve pas cette classe. On a l'impression que la réponse est inventée.
james.garriss