【问题标题】:JavaScript string encryption and decryption?JavaScript 字符串加解密?
【发布时间】:2013-08-19 04:53:47
【问题描述】:

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

它不必是超级安全的,但我想使用当前未破解的算法。

理想情况下,我可以做类似的事情

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

生成编码字符串,类似

var sensical = decrypt(gibberish, key);

稍后解码。

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

我应该看看其他库吗?

【问题讨论】:

  • 这里的一些术语是关闭的,这是一个简单的版本 1。盐被添加到被散列的信息(通常是密码)中。他们的目的是使散列与没有盐的散列不同。这很有用,因为如果您的数据库被黑客入侵并且散列的用户密码被泄露,它会预先生成散列。 2. 散列是一种将输入转化为输出的单向操作。它不能轻易逆转或撤消。 3. 编码不是加密。 base64_encode、urlencode等

标签: encryption javascript


【解决方案1】:

 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>

【讨论】:

  • Encrypted 其实是一个对象,但是你可以调用 encrypted.toString() 来获取字符串。稍后您将能够解密该字符串:jsbin.com/kofiqokoku/1
  • 但是我们如何才能保护秘密密码?
  • 看来crypto js是一个存档项目。 github上有一个clone:github.com/sytelus/CryptoJS但是两年没更新了。这仍然是 js 加密的最佳选择吗?
  • 我会选择这个:github.com/brix/crypto-js 它也可以通过 NPM 获得
  • @stom 取决于您如何以及在何处存储它。我不知道是否有真正安全的方式将其存储在浏览器中。从服务器请求它们并存储在内存中。
【解决方案2】:

CryptoJS 怎么样?

这是一个可靠的加密库,具有很多功能。它实现了散列器、HMAC、PBKDF2 和密码。在这种情况下,您需要的是密码。查看项目主页上的快速入门指南。

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

【讨论】:

  • 3.1.2文件夹下rollups和components有什么区别?
  • 播放一点后,组件是分离的部分。您将需要知道要使用哪些组件(以及以什么顺序)以使其工作。汇总文件包含您需要的所有内容,只需一个脚本引用即可使用(因为已经完成了艰巨的工作,所以效果要好得多)。
  • 但是我们如何保护秘密密码?
  • @shaijut 你没有。在加密/解密明文时,您甚至不会将其保存在 RAM 之外的任何地方。密码只能存储在用户的大脑(或密码管理器)中
【解决方案3】:

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

这些是函数:

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

【讨论】:

  • let myDecipher = decipher('CartelSystem') - 这个盐也将破译字符串。您不必知道确切的单词“mySecretSalt”
  • 又一个帖子有人盲目使用let。 ?︎
  • byteHex 的长度应该扩展到4,因为charCode 的最大值为2^16 - 1(i.e. 65535),编码为基数16 时为ffff。修复:const byteHex = n =&gt; ("000" + Number(n).toString(16)).substr(-4);return encoded =&gt; encoded.match(/.{1,4}/g)
  • 这不是 a) 超级损坏和不安全 b) 'salt' 实际上是你的'密钥',因为 salt 不应该是私有的?我认为在没有任何 cmets 的情况下发布这样的代码是非常危险的,因为这个有趣的代码不适合任何现实世界使用。赞成票的数量令人担忧。 crypto.stackexchange.com/questions/11466/…
  • 好吧,至少他们使用的是健全的加密货币。你正在做的基本上是一个凯撒密码(对每个角色应用相同的密钥)en.wikipedia.org/wiki/Caesar_cipher#Breaking_the_cipher 关于其他答案......我希望很明显,所谓的“秘密”应该是保密的(由用户)
【解决方案4】:

此代码基于上述@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

【讨论】:

  • 你做得很好
  • 谢谢,Hamza,但尽管我很想称赞,@Jorgeblom 做了真正的工作:)
  • PHP 的等价物是什么?
【解决方案5】:

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

为什么不使用 SJCL、CryptoJS、WebCrypto 等?

简短回答:为了使您的加密真正安全,这些库希望您做出太多选择,例如分组密码模式(CBC、CTR、GCM;如果您不知道我刚刚列出的三个中的哪一个可以安全使用以及在什么限制下使用,那么您根本不应该被这种选择所困扰 /em>)。

除非您的职位是密码学工程师,否则您安全实施它的可能性很大。

为什么要避免 CryptoJS?

CryptoJS 提供了一些构建块,并希望您知道如何安全地使用它们。 It even defaults to CBC mode (archived)。

为什么 CBC 模式不好?

阅读this write-up on AES-CBC vulnerabilities

为什么要避免使用 WebCrypto?

WebCrypto 是一个由委员会设计的便饭标准,用于与密码学工程正交的目的。具体来说,WebCrypto was meant to replace Flash, not provide security

为什么要避免 SJCL?

SJCL 的公共 API 和文档要求用户使用人工记住的密码来加密数据。这很少,如果有的话,你想在现实世界中做的事情。

另外:它的默认 PBKDF2 轮数大约是86 times as small as you want it to be。 AES-128-CCM 可能没问题。

在上述三个选项中,SJCL 最不可能以眼泪告终。但是还有更好的选择。

为什么 Libsodium 更好?

您无需在密码模式、哈希函数和其他不必要的选项之间进行选择。你永远不会risk screwing up your parameters and removing all security from your protocol

相反,libsodium 只是为您提供了一些简单的选项,这些选项针对最大的安全性和极简 API 进行了调整。

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

此外,libsodium 具有bindings in dozens of popular programming languages,因此当尝试与另一个编程堆栈进行互操作时,libsodium 很可能正常工作。此外,libsodium 的速度往往非常快,而且不会牺牲安全性。

如何在 JavaScript 中使用 Libsodium?

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

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

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

文档是available onlineEncryptedField 对于大多数用例来说已经足够了,但如果您有很多不同的字段要加密,EncryptedRowEncryptedMultiRows API 可能会更容易。

使用 CipherSweet,您无需甚至知道什么是 nonce/IV 即可安全地使用它。

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

否则,您将需要sodium-plus,它是各种 libsodium 包装器的用户友好型前端。 Sodium-Plus 允许您编写易于审计和推理的高性能、异步、跨平台代码。

要安装钠加,只需运行...

npm install sodium-plus

目前没有用于浏览器支持的公共 CDN。这将很快改变。但是,如果需要,您可以从the latest Github release 中获取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 上找到。

如果您想要分步教程,this dev.to article 有您想要的。

【讨论】:

  • 您与钠项目有什么关系?
  • 对 WebCrypto 进行更持久的争论会更好 - 似乎您只是链接了匿名评论,称 WebCrypto 的设计没有考虑到安全性
  • @MaciejUrbański 是的,他很支持。
  • 投反对票,因为零指示/承认他是 GitHub 上项目的维护者(可能是负责人)(偏见)...
  • @MaciejUrbański 我为 libsodium 做出了贡献,并领导了将其纳入 PHP 标准库的倡议。 wiki.php.net/rfc/libsodium 我与钠的关系是 a) 用户,b) 专门研究应用密码学的独立安全专家。
【解决方案6】:

现代浏览器现在支持crypto.subtle API,它使用以下方法之一提供本机加密和解密功能(不亚于异步!):AES-CBC、AES-CTR、AES-GCM 或 RSA-OAEP。

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

【讨论】:

【解决方案7】:

在实施任何这些之前,请参阅Scott Arciszewski's answer

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

正如@richardtallent 在他的answer 中提到的那样,支持Web Crypto API,所以这个例子使用了标准。在撰写本文时,有一个95.88% of global browser support

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

在我们继续之前,请注意 (Quoting from 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)}`)

从 String 来回转换 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

【讨论】:

    【解决方案8】:

    您可以使用这些函数,第一个加密非常简单,因此您只需调用该函数并发送您想要加密的文本,然后从 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 
    

    【讨论】:

      【解决方案9】:

      crypt.subtle AES-GCM,自包含,经过测试:

      async function aesGcmEncrypt(plaintext, password)
      
      async function aesGcmDecrypt(ciphertext, password) 
      

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

      【讨论】:

      • 谢谢!很难找到使用本机浏览器 Crypto API 的简单易用示例。唯一的限制是 IE11...
      【解决方案10】:

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

      &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js"&gt;&lt;/script&gt;

      【讨论】:

      • 3.1.2文件夹下rollups和components有什么区别?
      • Crypto 在你进入他们的网站时推荐forge 库。
      【解决方案11】:

      2021 年 12 月更新

      使用MDN Web Docs提供的crypto api:

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


      旧答案

      使用SimpleCrypto

      使用 encrypt() 和 decrypt()

      要使用 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.");
      

      【讨论】:

      【解决方案12】:

      简单的功能:

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

      【讨论】:

      • 虽然此代码 sn-p 可能是解决方案,但 including an explanation 确实有助于提高您的帖子质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因。
      • 这不是一种安全算法(请注意,Encrypt 不采用密钥参数)并且可以轻松进行逆向工程。 OP 要求提供具有安全性的东西。
      • 这不是完全加密和解密。它更像是编码和解码......
      猜你喜欢
      • 2019-05-20
      • 2018-10-31
      • 2011-12-22
      • 1970-01-01
      • 2012-07-01
      • 2018-08-26
      • 1970-01-01
      相关资源
      最近更新 更多