Cryptage et décryptage de chaînes JavaScript?

153

Je suis intéressé par la création d'une petite application à usage personnel qui cryptera et décryptera les informations côté client à l'aide de JavaScript. Les informations chiffrées seront stockées dans une base de données sur un serveur, mais jamais la version déchiffrée.

Il n'est pas nécessaire que ce soit super sécurisé, mais j'aimerais utiliser un algorithme actuellement ininterrompu.

Idéalement, je serais capable de faire quelque chose comme

var gibberish = encrypt(string, salt, key);

pour générer la chaîne encodée, et quelque chose comme

var sensical = decrypt(gibberish, key);

pour le décoder plus tard.

Jusqu'à présent, j'ai vu ceci: http://bitwiseshiftleft.github.io/sjcl/

Y a-t-il d'autres bibliothèques que je devrais consulter?

Jérémie
la source
2
Jetez un œil au cryptage Javascript AES
kevinji
10
Une partie de la terminologie ici est désactivée, voici une version simple 1. Des sels sont ajoutés aux informations (généralement des mots de passe) en cours de hachage. Leur but est de rendre le hachage différent de ce qu'il serait sans le sel. Ceci est utile car il crée des hachages pré-générés si votre base de données est piratée et que les mots de passe des utilisateurs hachés sortent. 2. Le hachage est une opération unidirectionnelle qui convertit l'entrée en sortie. Il ne peut pas être facilement inversé ou annulé. 3. L'encodage n'est pas un cryptage. base64_encode, urlencode, etc.
des

Réponses:

160

 var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");
//U2FsdGVkX18ZUVvShFSES21qHsQEqZXMxQ9zgHy+bu0=

var decrypted = CryptoJS.AES.decrypt(encrypted, "Secret Passphrase");
//4d657373616765


document.getElementById("demo1").innerHTML = encrypted;
document.getElementById("demo2").innerHTML = decrypted;
document.getElementById("demo3").innerHTML = decrypted.toString(CryptoJS.enc.Utf8);
Full working sample actually is:

    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js" integrity="sha256-/H4YS+7aYb9kJ5OKhFYPUjSJdrtV6AeyJOtTkw6X72o=" crossorigin="anonymous"></script>

<br><br>
<label>encrypted</label>
<div id="demo1"></div>
<br>

<label>decrypted</label>
<div id="demo2"></div>

<br>
<label>Actual Message</label>
<div id="demo3"></div>

Tomas Kirda
la source
8
Encrypted est en fait un objet, mais vous pouvez appeler encrypted.toString () pour obtenir la chaîne. Vous pourrez déchiffrer cette chaîne plus tard: jsbin.com/kofiqokoku/1
Tomas Kirda
9
Mais comment pouvons-nous sécuriser la phrase secrète?
duykhoa
9
Il semble que crypto js soit un projet archivé. Il existe un clone sur github: github.com/sytelus/CryptoJS mais il n'a pas été mis à jour depuis deux ans. Est-ce toujours la meilleure option pour le cryptage js?
syonip
2
J'irais avec celui-ci: github.com/brix/crypto-js il est également disponible via NPM
Tomas Kirda
1
@stom, c'est à vous de décider comment et où vous le stockez. Je ne sais pas s'il existe un moyen vraiment sécurisé de le stocker dans un navigateur. Demandez-les au serveur et stockez-les en mémoire.
Tomas Kirda le
62

Que diriez-vous CryptoJS ?

C'est une bibliothèque cryptographique solide, avec beaucoup de fonctionnalités. Il implémente des hachages, HMAC, PBKDF2 et des chiffrements. Dans ce cas, vous avez besoin de chiffrements. Consultez le guide de démarrage rapide sur la page d'accueil du projet.

Vous pouvez faire quelque chose comme avec l'AES:

<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>

<script>
    var encryptedAES = CryptoJS.AES.encrypt("Message", "My Secret Passphrase");
    var decryptedBytes = CryptoJS.AES.decrypt(encryptedAES, "My Secret Passphrase");
    var plaintext = decryptedBytes.toString(CryptoJS.enc.Utf8);
</script>

En ce qui concerne la sécurité, au moment de mon écriture, l'algorithme AES est considéré comme ininterrompu

Éditer :

Il semble que l'URL en ligne est en panne et vous pouvez utiliser les fichiers téléchargés pour le cryptage à partir du lien ci-dessous et placer les fichiers respectifs dans votre dossier racine de l'application.

https://code.google.com/archive/p/crypto-js/downloads

ou utilisé un autre CDN comme https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/components/aes-min.js

ovidb
la source
Quelle est la différence entre les rollups et les composants sous le dossier 3.1.2?
Kanagavelu Sugumar
Après avoir joué un peu, les composants sont les parties séparées. Vous aurez besoin de savoir quels composants prendre (et dans quel ordre) pour que cela fonctionne. Les fichiers de cumul contiennent tout ce dont vous avez besoin pour le faire fonctionner avec une seule référence de script (bien mieux car le travail est déjà fait).
shahar eldad
2
Mais comment pouvons-nous sécuriser la phrase secrète?
shaijut le
@shaijut Vous ne le faites pas. Vous ne l'enregistrez même nulle part sauf dans la RAM lors du cryptage / décryptage du texte en clair. La phrase de passe ne doit être stockée que dans le cerveau de l'utilisateur (ou dans un gestionnaire de mots de passe)
slebetman
39

J'ai créé un utilitaire de chiffrement / déchiffrement de texte non sécurisé mais simple. Aucune dépendance avec une bibliothèque externe.

Ce sont les fonctions

const cipher = salt => {
    const textToChars = text => text.split('').map(c => c.charCodeAt(0));
    const byteHex = n => ("0" + Number(n).toString(16)).substr(-2);
    const applySaltToChar = code => textToChars(salt).reduce((a,b) => a ^ b, code);

    return text => text.split('')
        .map(textToChars)
        .map(applySaltToChar)
        .map(byteHex)
        .join('');
}

const decipher = salt => {
    const textToChars = text => text.split('').map(c => c.charCodeAt(0));
    const applySaltToChar = code => textToChars(salt).reduce((a,b) => a ^ b, code);
    return encoded => encoded.match(/.{1,2}/g)
        .map(hex => parseInt(hex, 16))
        .map(applySaltToChar)
        .map(charCode => String.fromCharCode(charCode))
        .join('');
}

Et vous pouvez les utiliser comme suit:

// To create a cipher
const myCipher = cipher('mySecretSalt')

//Then cipher any text:
myCipher('the secret string')   // --> "7c606d287b6d6b7a6d7c287b7c7a61666f"

//To decipher, you need to create a decipher and use it:
const myDecipher = decipher('mySecretSalt')
myDecipher("7c606d287b6d6b7a6d7c287b7c7a61666f")    // --> 'the secret string'
Jorgeblom
la source
4
let myDecipher = decipher ('CartelSystem') - Ce sel déchiffrera également la chaîne. Vous n'avez pas besoin de connaître le mot exact 'mySecretSalt'
Dror Bar
De plus, les saltChars dans le déchiffrement ne sont-ils pas utilisés?
Dror Bar
1
Encore un autre article où quelqu'un utilise aveuglément let. 😒︎
John
1
N'est-ce pas a) super cassé et peu sûr et b) le «sel» est en fait votre «clé secrète» car on ne s'attend pas à ce que les sels soient privés? Je pense que c'est très dangereux de publier du code comme celui-ci sans aucun commentaire que ce code amusant n'est pas destiné à une utilisation dans le monde réel. Le montant des votes positifs est inquiétant. crypto.stackexchange.com/questions/11466/…
lschmierer
1
Au moins, ils utilisent la cryptographie sonore. Ce que vous faites est fondamentalement un César Chipher (appliquant la même clé à chaque caractère) en.wikipedia.org/wiki/Caesar_cipher#Breaking_the_cipher En ce qui concerne les autres réponses ... je m'attends à ce qu'il soit évident que quelque chose appelé un "secret" est devrait être gardé secret (par l'utilisateur)
lschmierer
19

Les réponses existantes qui tirent parti de SJCL, CryptoJS et / ou WebCrypto ne sont pas nécessairement fausses, mais elles ne sont pas aussi sûres que vous pourriez le soupçonner au départ. Généralement, vous souhaitez utiliser libsodium . Je vais d'abord expliquer pourquoi, puis comment.

Pourquoi pas SJCL, CryptoJS, WebCrypto, etc.?

Réponse courte: pour que votre cryptage soit réellement sécurisé, ces bibliothèques s'attendent à ce que vous fassiez trop de choix, par exemple le mode de chiffrement par bloc (CBC, CTR, GCM; si vous ne pouvez pas dire lequel des trois que je viens d'énumérer est sécurisé utilisation et sous quelles contraintes, vous ne devriez pas être accablés par ce genre de choix du tout ).

À moins que votre titre de poste ne soit ingénieur en cryptographie , les chances sont contre vous de l'implémenter en toute sécurité.

Pourquoi éviter CryptoJS?

CryptoJS propose une poignée de blocs de construction et s'attend à ce que vous sachiez comment les utiliser en toute sécurité. Il passe même par défaut en mode CBC ( archivé ).

Pourquoi le mode CBC est-il mauvais?

Lisez cet article sur les vulnérabilités AES-CBC .

Pourquoi éviter WebCrypto?

WebCrypto est un standard potluck, conçu par un comité, à des fins orthogonales à l'ingénierie cryptographique. Plus précisément, WebCrypto était censé remplacer Flash et non assurer la sécurité .

Pourquoi éviter SJCL?

L'API publique et la documentation de SJCL demandent aux utilisateurs de crypter les données avec un mot de passe mémorisé par l'homme. C'est rarement, voire jamais, ce que vous voulez faire dans le monde réel.

De plus: son nombre d'arrondis PBKDF2 par défaut est environ 86 fois plus petit que vous le souhaitez . AES-128-CCM est probablement bien.

Sur les trois options ci-dessus, SJCL est la moins susceptible de se terminer en larmes. Mais il existe de meilleures options disponibles.

Pourquoi Libsodium est-il meilleur?

Vous n'avez pas besoin de choisir entre un menu de modes de chiffrement, de fonctions de hachage et d'autres options inutiles. Vous ne risquerez jamais de bousiller vos paramètres et de supprimer toute sécurité de votre protocole .

Au lieu de cela, libsodium vous propose simplement des options simples optimisées pour une sécurité maximale et des API minimalistes.

  • crypto_box() / crypto_box_open() offre un cryptage à clé publique authentifié.
    • L'algorithme en question combine X25519 (ECDH sur Curve25519) et XSalsa20-Poly1305, mais vous n'avez pas besoin de savoir (ni même de vous en soucier) pour l'utiliser en toute sécurité
  • crypto_secretbox() / crypto_secretbox_open() offre un chiffrement authentifié par clé partagée.
    • L'algorithme en question est XSalsa20-Poly1305, mais vous n'avez pas besoin de savoir / soins

De plus, libsodium a des liaisons dans des dizaines de langages de programmation populaires , il est donc très probable que libsodium fonctionnera simplement en essayant d'interagir avec une autre pile de programmation. De plus, libsodium a tendance à être très rapide sans sacrifier la sécurité.

Comment utiliser Libsodium en JavaScript?

Tout d'abord, vous devez décider d'une chose:

  1. Voulez-vous simplement crypter / décrypter les données (et peut-être encore utiliser le texte brut dans les requêtes de base de données en toute sécurité) et ne pas vous soucier des détails? Ou...
  2. Avez-vous besoin de mettre en œuvre un protocole spécifique?

Si vous avez sélectionné la première option , récupérez CipherSweet.js .

La documentation est disponible en ligne . EncryptedFieldest suffisant pour la plupart des cas d'utilisation, mais les API EncryptedRowet EncryptedMultiRowspeuvent être plus faciles si vous avez beaucoup de champs distincts à chiffrer.

Avec CipherSweet, vous n'avez même pas besoin de savoir ce qu'est un nonce / IV pour l'utiliser en toute sécurité.

De plus, cela gère int/ floatcryptage sans divulguer des informations sur le contenu via la taille du texte chiffré.

Sinon, vous aurez besoin de sodium-plus , qui est une interface conviviale pour divers wrappers libsodium. Sodium-Plus vous permet d'écrire un code multiplateforme performant, asynchrone, facile à auditer et à raisonner.

Pour installer sodium-plus, exécutez simplement ...

npm install sodium-plus

Il n'existe actuellement aucun CDN public pour la prise en charge des navigateurs. Cela va bientôt changer. Cependant, vous pouvez récupérer sodium-plus.min.jsde la dernière version Github si vous en avez besoin.

const { SodiumPlus } = require('sodium-plus');
let sodium;

(async function () {
    if (!sodium) sodium = await SodiumPlus.auto();
    let plaintext = 'Your message goes here';
    let key = await sodium.crypto_secretbox_keygen();
    let nonce = await sodium.randombytes_buf(24);
    let ciphertext = await sodium.crypto_secretbox(
        plaintext,
        nonce,
        key    
    );
    console.log(ciphertext.toString('hex'));

    let decrypted = await sodium.crypto_secretbox_open(
        ciphertext,
        nonce,
        key
    );

    console.log(decrypted.toString());
})();

La documentation de sodium-plus est disponible sur Github.

Si vous souhaitez un didacticiel pas à pas, cet article de dev.to a ce que vous recherchez.

Scott Arciszewski
la source
16

Les navigateurs modernes prennent désormais en charge l' crypto.subtleAPI, qui fournit des fonctions de cryptage et de décryptage natives (async pas moins!) En utilisant l'une de ces méthodes: AES-CBC, AES-CTR, AES-GCM ou RSA-OAEP.

https://www.w3.org/TR/WebCryptoAPI/#dfn-Crypto

richardtallent
la source
3
Parmi les options ci-dessus, seuls AES-GCM et RSA-OAEP sont raisonnables. :(
Scott Arciszewski
5

Avant de mettre en œuvre tout cela, veuillez consulter la réponse de Scott Arciszewski .

Je veux que vous soyez très prudent avec ce que je suis sur le point de partager car je n'ai que peu ou pas de connaissances en matière de sécurité (il y a de fortes chances que j'utilise mal l'API ci-dessous), donc je serais plus que bienvenu pour mettre à jour cette réponse avec l'aide de la communauté .

Comme @richardtallent l'a mentionné dans sa réponse , il existe un support pour l'API Web Crypto, donc cet exemple utilise le standard. Au moment d'écrire ces lignes, 95,88% de la prise en charge mondiale des navigateurs .

Je vais partager un exemple en utilisant l'API Web Crypto

Avant de continuer, veuillez noter ( Citations de MDN ):

Cette API fournit un certain nombre de primitives cryptographiques de bas niveau. Il est très facile de les utiliser à mauvais escient , et les écueils impliqués peuvent être très subtils .

Même en supposant que vous utilisiez correctement les fonctions cryptographiques de base, la gestion sécurisée des clés et la conception globale du système de sécurité sont extrêmement difficiles à obtenir. et sont généralement le domaine des experts en sécurité spécialisés.

Des erreurs dans la conception et la mise en œuvre du système de sécurité peuvent rendre la sécurité du système totalement inefficace.

Si vous n'êtes pas sûr de savoir ce que vous faites, vous ne devriez probablement pas utiliser cette API .

Je respecte beaucoup la sécurité, et j'ai même mis en gras des parties supplémentaires de MDN ... Vous avez été prévenu

Maintenant, à l'exemple réel ...


JSFiddle:

Trouvé ici: https://jsfiddle.net/superjose/rm4e0gqa/5/

Remarque:

Notez l'utilisation de awaitmots - clés. Utilisez-le dans une asyncfonction ou utilisez .then()et .catch().

Générez la clé:

// https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey
// https://developer.mozilla.org/en-US/docs/Web/API/RsaHashedKeyGenParams
// https://github.com/diafygi/webcrypto-examples#rsa-oaep---generatekey
    const stringToEncrypt = 'https://localhost:3001';
    // https://github.com/diafygi/webcrypto-examples#rsa-oaep---generatekey
    // The resultant publicKey will be used to encrypt
    // and the privateKey will be used to decrypt. 
    // Note: This will generate new keys each time, you must store both of them in order for 
    // you to keep encrypting and decrypting.
    //
    // I warn you that storing them in the localStorage may be a bad idea, and it gets out of the scope
    // of this post. 
    const key = await crypto.subtle.generateKey({
      name: 'RSA-OAEP',
      modulusLength: 4096,
      publicExponent:  new Uint8Array([0x01, 0x00, 0x01]),
      hash: {name: 'SHA-512'},
      
    }, true,
    // This depends a lot on the algorithm used
    // Go to https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto
    // and scroll down to see the table. Since we're using RSA-OAEP we have encrypt and decrypt available
    ['encrypt', 'decrypt']);

    // key will yield a key.publicKey and key.privateKey property.

Crypter:

    const encryptedUri = await crypto.subtle.encrypt({
      name: 'RSA-OAEP'
    }, key.publicKey, stringToArrayBuffer(stringToEncrypt))
    
    console.log('The encrypted string is', encryptedUri);

Décrypter

   const msg = await  crypto.subtle.decrypt({
      name: 'RSA-OAEP',
    }, key.privateKey, encryptedUri);
    console.log(`Derypted Uri is ${arrayBufferToString(msg)}`)

Conversion de ArrayBuffer d'avant en arrière à partir de String (fait dans TypeScript):

  private arrayBufferToString(buff: ArrayBuffer) {
    return String.fromCharCode.apply(null, new Uint16Array(buff) as unknown as number[]);
  }

  private stringToArrayBuffer(str: string) {
    const buff = new ArrayBuffer(str.length*2) // Because there are 2 bytes for each char.
    const buffView = new Uint16Array(buff);
    for(let i = 0, strLen = str.length; i < strLen; i++) {
      buffView[i] = str.charCodeAt(i);
    }
    return buff;
  }

Vous pouvez trouver plus d'exemples ici (je ne suis pas le propriétaire): // https://github.com/diafygi/webcrypto-examples

José A
la source
2

CryptoJS n'est plus pris en charge. Si vous souhaitez continuer à l'utiliser, vous pouvez passer à cette URL:

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js"></script>

Dmytro Lopushanskyy
la source
Quelle est la différence entre les rollups et les composants sous le dossier 3.1.2?
Kanagavelu Sugumar
1
Crypto recommande la bibliothèque de forge lorsque vous entrez sur leur site.
Dror Bar
1

Utilisez SimpleCrypto

Utilisation de encrypt () et decrypt ()

Pour utiliser SimpleCrypto, créez d'abord une instance SimpleCrypto avec une clé secrète (mot de passe). Le paramètre de clé secrète DOIT être défini lors de la création d'une instance SimpleCrypto.

Pour crypter et décrypter des données, utilisez simplement les fonctions encrypt () et decrypt () à partir d'une instance. Cela utilisera l'algorithme de cryptage AES-CBC.

var _secretKey = "some-unique-key";

var simpleCrypto = new SimpleCrypto(_secretKey);

var plainText = "Hello World!";
var chiperText = simpleCrypto.encrypt(plainText);
console.log("Encryption process...");
console.log("Plain Text    : " + plainText);
console.log("Cipher Text   : " + cipherText);
var decipherText = simpleCrypto.decrypt(cipherText);
console.log("... and then decryption...");
console.log("Decipher Text : " + decipherText);
console.log("... done.");
Murtaza Hussain
la source
3
SimpleCrypto utilise un AES-CBC non authentifié et est donc vulnérable aux attaques par texte chiffré choisi.
Scott Arciszewski
-6

Fonctions simples,


function Encrypt(value) 
{
  var result="";
  for(i=0;i<value.length;i++)
  {
    if(i<value.length-1)
    {
        result+=value.charCodeAt(i)+10;
        result+="-";
    }
    else
    {
        result+=value.charCodeAt(i)+10;
    }
  }
  return result;
}
function Decrypt(value)
{
  var result="";
  var array = value.split("-");

  for(i=0;i<array.length;i++)
  {
    result+=String.fromCharCode(array[i]-10);
  }
  return result;
} 
Omi
la source
4
Bien que cet extrait de code puisse être la solution, inclure une explication contribue vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondez à la question aux lecteurs à l'avenir, et que ces personnes pourraient ne pas connaître les raisons de votre suggestion de code.
Johan
1
Ce n'est pas un algorithme sécurisé (notez que Encrypt ne prend pas un paramètre clé) et peut être facilement inversé. Le PO a demandé quelque chose qui avait une sécurité là-dessus.
Mike S