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

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

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

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

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

var sensical = decrypt(gibberish, key);

稍后再解码。

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

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


当前回答

你可以使用这些函数,它很简单,第一个用于加密,你只需要调用这个函数,发送你想要加密的文本,然后从encryptWithAES函数中获取结果,然后将其发送到解密函数,就像这样:

const CryptoJS = require("crypto-js");


   //The Function Below To Encrypt Text
   const encryptWithAES = (text) => {
      const passphrase = "My Secret Passphrase";
      return CryptoJS.AES.encrypt(text, passphrase).toString();
    };
    //The Function Below To Decrypt Text
    const decryptWithAES = (ciphertext) => {
      const passphrase = "My Secret Passphrase";
      const bytes = CryptoJS.AES.decrypt(ciphertext, passphrase);
      const originalText = bytes.toString(CryptoJS.enc.Utf8);
      return originalText;
    };

  let encryptText = encryptWithAES("YAZAN"); 
  //EncryptedText==>  //U2FsdGVkX19GgWeS66m0xxRUVxfpI60uVkWRedyU15I= 

  let decryptText = decryptWithAES(encryptText);
  //decryptText==>  //YAZAN 

其他回答

在实现这些之前,请参阅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

简单的函数:

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

这段代码是基于上面@Jorgeblom的回答。


@Jorgeblom我的朋友,这是一个很棒的小加密库:D 我有点碰它,因为我不喜欢我必须分配盐,然后再调用它但总的来说,对于我的需求是绝对完美的。

const crypt = (salt, text) => {
  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
    .split("")
    .map(textToChars)
    .map(applySaltToChar)
    .map(byteHex)
    .join("");
};

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

你使用它

// encrypting
const encrypted_text = crypt("salt", "Hello"); // -> 426f666665

// decrypting
const decrypted_string = decrypt("salt", "426f666665"); // -> Hello

现有的利用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文章可以满足您的需要。

CryptoJS怎么样?

这是一个可靠的加密库,有很多功能。它实现哈希器,HMAC, PBKDF2和密码。在这种情况下,你需要的是密码。在项目主页上查看快速启动quide。

你可以用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>

至于安全性,在我写这篇文章的时候,AES算法被认为是不可破解的

编辑:

似乎在线URL下降&你可以使用下载的文件从下面给定的链接加密&把各自的文件放在应用程序的根文件夹中。

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

或使用其他CDN,如https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/components/aes-min.js