我对构建一个个人使用的小型应用程序感兴趣,该应用程序将使用JavaScript在客户端加密和解密信息。加密的信息将存储在服务器上的数据库中,但绝不会存储解密的版本。

它不必是超级duper安全,但我想使用目前未被破坏的算法。

理想情况下,我可以做一些

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

来生成编码的字符串,诸如此类

var sensical = decrypt(gibberish, key);

稍后再解码。

到目前为止,我看到的是: http://bitwiseshiftleft.github.io/sjcl/

我还应该看看其他的图书馆吗?


当前回答

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>

其他回答

现有的利用SJCL、CryptoJS和/或WebCrypto的答案不一定是错误的,但它们并不像你最初怀疑的那样安全。通常你要用libsodium。首先我将解释为什么,然后如何。

为什么不是SJCL, CryptoJS, WebCrypto等?

简单的回答:为了让你的加密实际上是安全的,这些库希望你做出太多的选择,例如分组密码模式(CBC, CTR, GCM;如果你不知道我列出的三种方法中哪一种是安全的,在什么限制下使用,你根本不应该被这种选择所困扰)。

除非你的职位是密码学工程师,否则你很难安全地实现它。

为什么要避免使用CryptoJS?

CryptoJS提供了一些构建块,并希望您知道如何安全地使用它们。它甚至默认为CBC模式(存档)。

为什么CBC模式不好?

阅读这篇关于AES-CBC漏洞的文章。

为什么要避免WebCrypto?

WebCrypto是一种家常便饭的标准,由委员会设计,目的与密码学工程正交。具体来说,WebCrypto旨在取代Flash,而不是提供安全性。

为什么要避免SJCL?

SJCL的公共API和文档要求用户使用人类记住的密码加密数据。这几乎不是你在现实世界中想要做的事情。

此外:它的默认PBKDF2轮计数大约是您想要的86倍。AES-128-CCM可能没问题。

在上述三种选择中,SJCL是最不可能以悲剧收场的。但也有更好的选择。

为什么利布纳更好?

您不需要在密码模式、散列函数和其他不必要的选项之间进行选择。您永远不会冒险搞砸参数并从协议中移除所有安全性。

相反,libsodium只是为您提供了一些简单的选项,以实现最大的安全性和最少的api。

Crypto_box () / crypto_box_open()提供经过身份验证的公钥加密。 所讨论的算法结合了X25519 (ECDH over Curve25519)和XSalsa20-Poly1305,但是您不需要知道(甚至不需要关心)就可以安全地使用它 Crypto_secretbox () / crypto_secretbox_open()提供共享密钥认证加密。 这里讨论的算法是XSalsa20-Poly1305,但您不需要知道/关心

此外,libsodium在许多流行的编程语言中都有绑定,因此很可能只有在尝试与另一种编程堆栈进行互操作时,libsodium才能工作。而且,libsodium的速度非常快,而且不会牺牲安全性。

如何在JavaScript中使用Libsodium ?

首先,你需要决定一件事:

您只是想加密/解密数据(可能仍然以某种方式在数据库查询中安全地使用明文),而不担心细节吗?还是…… 您是否需要实现特定的协议?

如果您选择了第一个选项,请获取CipherSweet.js。

相关文档可以在网上找到。EncryptedField对于大多数用例来说已经足够了,但是如果有很多不同的字段需要加密,那么EncryptedRow和EncryptedMultiRows api可能会更容易一些。

有了CipherSweet,你甚至不需要知道什么是nonce/IV就可以安全地使用它。

此外,它处理int/float加密,而不会通过密文大小泄露有关内容的事实。

否则,您将需要钠加,这是一个用户友好的前端,以各种libsodium包装。Sodium-Plus允许您编写易于审核和推理的高性能、异步、跨平台代码。

要安装钠加,只需运行…

npm install sodium-plus

目前没有浏览器支持的公共CDN。这种情况很快就会改变。但是,如果你需要的话,你可以从最新的Github版本中获取sodium-plus.min.js。

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()); })();

关于sodium-plus的文档可以在Github上找到。

如果您想要一个循序渐进的教程,这篇dev.to文章可以满足您的需要。

我创建了一个不安全但简单的文本密码/解密实用程序。不依赖任何外部库。

这些是函数:

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(''); } // To create a cipher const myCipher = cipher('mySecretSalt') //Then cipher any text: console.log(myCipher('the secret string')) //To decipher, you need to create a decipher and use it: const myDecipher = decipher('mySecretSalt') console.log(myDecipher("7c606d287b6d6b7a6d7c287b7c7a61666f"))

现代浏览器现在支持加密。微妙的API,它提供本地加密和解密功能(异步!)使用这些方法之一:AES-CBC, AES-CTR, AES-GCM,或RSA-OAEP。

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

在实现这些之前,请参阅Scott Arciszewski的回答。

我希望你对我将要分享的内容非常小心,因为我几乎没有安全知识(我很有可能误用了下面的API),所以我非常欢迎在社区的帮助下更新这个答案。

正如@richardtallent在他的回答中提到的,有对Web Crypto API的支持,因此本例使用了该标准。在撰写本文时,全球浏览器的支持率为95.88%。

我将分享一个使用Web Crypto API的示例

在我们继续之前,请注意(引用自MDN):

This API provides a number of low-level cryptographic primitives. It's very easy to misuse them, and the pitfalls involved can be very subtle. Even assuming you use the basic cryptographic functions correctly, secure key management and overall security system design are extremely hard to get right and are generally the domain of specialist security experts. Errors in security system design and implementation can make the security of the system completely ineffective. If you're not sure you know what you are doing, you probably shouldn't be using this API.

我非常尊重安全性,我甚至加粗了MDN的附加部分……我已经警告过你了 现在来看看实际的例子……


JSFiddle:

在这里找到:https://jsfiddle.net/superjose/rm4e0gqa/5/

注意:

注意await关键字的使用。在异步函数中使用它或使用.then()和.catch()。

生成密钥:

// 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.

加密:

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


解密

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

从字符串中来回转换ArrayBuffer(在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;
  }

你可以在这里找到更多的例子(我不是所有者): / / https://github.com/diafygi/webcrypto-examples

不再支持CryptoJS。如果你想继续使用它,你可以切换到这个url:

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