JavaScript字符串加密和解密?


152

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

它不一定是超级duper安全的,但是我想使用当前不间断的算法。

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

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

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

var sensical = decrypt(gibberish, key);

稍后再解码。

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

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




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

Answers:


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>


8
加密实际上是一个对象,但是您可以调用crypto.toString()来获取字符串。您稍后可以解密该字符串:jsbin.com/kofiqokoku/1
Tomas Kirda,2015年

9
但是,我们如何确保秘密密码短语呢?
duykhoa 2015年

9
似乎crypto js是一个归档项目。github上有一个克隆:github.com/sytelus/CryptoJS,但两年来没有更新。这仍然是js加密的最佳选择吗?
syonip

2
我会选择这个:github.com/brix/crypto-js它也可以通过NPM获得
Tomas Kirda

1
@stom由您决定如何以及在何处存储它。我不知道是否有真正安全的方法将其存储在浏览器中。从服务器请求它们并将其存储在内存中。
Tomas Kirda '19

62

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下的汇总和组件之间有什么区别?
卡纳加维卢·苏古玛

播放一会儿之后,组件便成为分离的部分。您将需要知道采用哪些组件(以什么顺序)才能使其正常工作。汇总文件仅包含一个脚本引用即可包含使它工作所需的一切(因为辛苦了,因此更好)。
shahar eldad '18

2
但是,我们如何确保Secret密码短语呢?
shaijut

@shaijut你不知道。加密/解密纯文本时,您甚至没有将其保存在RAM之外的任何位置。密码仅应存储在用户的大脑(或密码管理器)中
slebetman

39

我创建了一个不安全但简单的文本密码解密工具。不依赖任何外部库。

这些是功能

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:
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'

4
let myDecipher = decipher('CartelSystem')-此盐也会解密字符串。您不必知道确切的单词“ mySecretSalt”
Dror Bar

另外,是否不使用解密中的saltChars?
Dror Bar

1
有人盲目使用的另一个帖子let。😒︎–
约翰(John)

1
这不是a)超级破碎且不安全,b)“盐”实际上是您的“秘密钥匙”,因为盐不应该是私有的吗?我认为发布这样的代码而不带任何注释说明此有趣的代码不适合任何实际用途非常危险。投票的数量令人担忧。crypto.stackexchange.com/questions/11466/...
lschmierer

1
好吧,至少他们使用声音加密。您要做的基本上是一个Caesar Chipher(将相同的键应用于每个字符)en.wikipedia.org/wiki/Caesar_cipher#Breaking_the_cipher关于其他答案……我希望很明显,所谓的“秘密”是预计将被保密(用户)
lschmierer

18

利用SJCL,CryptoJS和/或WebCrypto的现有答案不一定是错误的,但是它们并不像您最初怀疑的那样安全。通常,您要使用libsodium。首先,我将解释原因,然后再解释。

为什么不选择SJCL,CryptoJS,WebCrypto等?

简短的答案:为了使您的加密实际上是安全的,这些库希望您做出太多选择,例如,块密码模式(CBC,CTR,GCM;如果无法确定我刚刚列出的三个加密方法中的哪一个是安全的),使用和在什么样的约束,你不应该有这种选择的负担在所有)。

除非您的职位是密码学工程师,否则您将无法安全地实施它。

为什么要避免使用CryptoJS?

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

为什么CBC模式不好?

阅读有关AES-CBC漏洞的文章

为什么要避免使用WebCrypto?

WebCrypto是委员会制定的杂烩标准,目的是与密码学工程正交。具体来说,WebCrypto旨在取代Flash,而不是提供安全性

为什么要避免SJCL?

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

此外:其默认的PBKDF2舍入计数大约是您希望的86倍。AES-128-CCM可能还不错。

在以上三个选项中,SJCL哭泣的可能性最小。但是有更好的选择。

为什么Libsodium更好?

您无需在密码模式,哈希函数和其他不必要选项的菜单之间进行选择。您永远不会冒险搞砸参数并从协议中删除所有安全性

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

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

另外,libsodium具有数十种流行编程语言的绑定,因此libsodium很可能在尝试与另一个编程堆栈进行互操作时才可以工作。同样,libsodium往往非常快而不会牺牲安全性。

如何在JavaScript中使用Libsodium?

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

  1. 您是否只想加密/解密数据(也许仍然以某种方式安全地使用数据库查询中的纯文本),而不必担心细节?要么...
  2. 您需要实施特定协议吗?

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

该文档可在线获得EncryptedField在大多数情况下就足够了,但是如果您要加密很多不同的字段,则EncryptedRowEncryptedMultiRowsAPI可能会更容易。

使用CipherSweet,您甚至不需要知道安全使用nonce / IV的意思

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

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

要安装sodium-plus,只需运行...

npm install sodium-plus

当前没有用于浏览器支持的公共CDN。这将很快改变。但是,如果需要,可以sodium-plus.min.js最新的Github版本中获取。

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文章将为您提供所需的内容。



4

在实施上述任何一项之前,请参阅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)}`)

从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



1

使用SimpleCrypto

使用crypto()和Delete()

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

要加密和解密数据,只需使用实例中的crypto()和crypto()函数。这将使用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.");

3
SimpleCrypto使用未经身份验证的AES-CBC,因此容易受到选择密文攻击。
Scott Arciszewski

-6

简单的功能,


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

4
尽管此代码段可能是解决方案,但包括说明确实有助于提高帖子的质量。请记住,您将来会为读者回答这个问题,而这些人可能不知道您提出代码建议的原因。
约翰(Johan)

1
这不是一个安全的算法(请注意,Encrypt没有采用关键参数),并且可以很容易地进行反向工程。OP要求提供具有安全性的东西。
Mike S
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.