JavaScript字符串加密和解密?

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

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

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

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

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

var sensical = decrypt(gibberish, key);

稍后再解码。

到目前为止,我已经看到了这个: http://bitwiseshiftleft.github.io/sjcl/ < / p >

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

600266 次浏览

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算法被认为是不可破解的

编辑:

似乎在线网址已经崩溃了。您可以使用下载的文件进行加密从下面给出的链接&将各自的文件放在应用程序的根文件夹中。

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

 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>

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

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

现代浏览器现在支持crypto.subtle API,它使用以下方法之一提供本地加密和解密函数:AES-CBC、AES-CTR、AES-GCM或RSA-OAEP。

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

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

这些是函数:

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"))

2021年12月更新

使用MDN Web文档提供的加密api:

https://developer.mozilla.org/en-US/docs/Web/API/Crypto


旧的答案

使用SimpleCrypto

使用encrypt()和decryption ()

要使用SimpleCrypto,首先创建一个SimpleCrypto实例 密钥(密码)。时必须定义密钥参数

.创建一个SimpleCrypto实例 要加密和解密数据,只需使用encrypt()和decrypt() 函数。这将使用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.");

简单的函数:

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

现有的利用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,但是您不需要知道(甚至不需要关心)就可以安全地使用它
    • 李< / ul > < / >
    • crypto_secretbox() / crypto_secretbox_open()提供共享密钥认证加密。
      • 这里讨论的算法是XSalsa20-Poly1305,但您不需要知道/关心
      • 李< / ul > < / >

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

      如何在JavaScript中使用Libsodium ?

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

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

      如果你选择了第一个选项,得到CipherSweet.js

      文档是网上EncryptedField对于大多数用例已经足够了,但是如果你有很多不同的字段想要加密,EncryptedRowEncryptedMultiRows 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());
      })();

      钠加上的文档可以在Github上找到。

      如果你想要一个循序渐进的教程,这篇dev.to文章有你正在寻找的东西。

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

我希望你是非常小心与我即将分享的内容,因为我几乎没有安全知识(有很高的机会,我滥用下面的API),所以我将非常欢迎在社区的帮助下更新这个答案

正如@richardtallent在他的回答中提到的,有对Web Crypto API的支持,所以这个例子使用了这个标准。在撰写本文时,有一个95.88%的全球浏览器支持

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

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

该API提供了许多低级加密原语。它是很容易被误用,所涉及的陷阱可以是非常微妙的

即使你正确地使用了基本的密码功能,安全密钥管理和整个安全系统设计都是很难做到正确,通常是专业安全专家的领域。

安全系统设计和实现中的错误会使系统的安全性完全失效。

如果你不确定你在做什么,你可能不应该使用这个API

我非常尊重安全性,我甚至加粗了MDN的额外部分…我已经警告过你了


JSFiddle:

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

注意:

注意await关键字的使用。在async函数中使用它或使用.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 < / p >

你可以使用这些函数,它很简单,第一个用于加密,你只需要调用这个函数,发送你想要加密的文本,然后从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

地下室。精细AES-GCM,独立,已测试:

async function aesGcmEncrypt(plaintext, password)


async function aesGcmDecrypt(ciphertext, password)

https://gist.github.com/chrisveness/43bcda93af9f646d083fad678071b90a

这段代码是基于上面@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