我有使用RC4算法加密文件的代码. 我被强烈建议使用一种更可靠的算法:AES. 从CryptoJS文档中,我了解到它的工作方式与RC4相同.也就是说,第一个参数是要加密的字符串,第二个参数是密码字符串.

但简单地用AES取代RC4方法是没有帮助的,我不知道从哪里寻找必要的信息.

谢谢!

以下是我的工作代码(针对RC4):

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js" integrity="sha512-E8QSvWZ0eCLGk4km3hxSsNmGWbLtSCSUcewDQPQWZF6pEU8GlT8a5fF32wOl1i8ftdMhssTrF/OhyGWwonTcXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<div>
  <h1>encrypt/decrypt file</h1>
  <ol>
    <li>Set password</li>
    <li>Pick a file</li>
    <li>Download decrypted/encrypted file</li>
  </ol>
  <div>
    <input type="text" id="pass" placeholder="pass">
    <button id="encrypt">encrypt file</button>
    <button id="decrypt">decrypt file</button>
    <button id="test">test</button>
  </div>
</div>

<script>
  // support
  const download = (data, filename, type) => {
    const file = new Blob([data], {
      type: type
    });
    const a = document.createElement('a');
    const url = URL.createObjectURL(file);

    a.href = url;
    a.download = filename;
    document.body.appendChild(a);

    a.click();

    setTimeout(function() {
      document.body.removeChild(a);
      window.URL.revokeObjectURL(url);
    }, 0);
  };
  const pickAFile = (getText = true) => {
    return new Promise((resolve, reject) => {
      const input = document.createElement('input');
      input.type = 'file';
      input.onchange = (e) => {
        const file = e.target.files[0];
        const reader = new FileReader();
        if (!getText) {
          resolve(file);
        } else {
          reader.onload = (e) => resolve(e.target.result);
          reader.onerror = (e) => reject(e);
          reader.readAsText(file);
        }
      };
      input.click();
    });
  };
  const convertWordArrayToUint8Array = (wordArray) => {
    const arrayOfWords = wordArray.hasOwnProperty('words') ? wordArray.words : [];

    const length = wordArray.hasOwnProperty('sigBytes') ?
      wordArray.sigBytes :
      arrayOfWords.length * 4;

    const uInt8Array = new Uint8Array(length);
    let index = 0;
    let word;
    let i;

    for (i = 0; i < length; i++) {
      word = arrayOfWords[i];
      uInt8Array[index++] = word >> 24;
      uInt8Array[index++] = (word >> 16) & 0xff;
      uInt8Array[index++] = (word >> 8) & 0xff;
      uInt8Array[index++] = word & 0xff;
    }
    return uInt8Array;
  };
  // /support

  function app() {
    const passNode = document.querySelector('input#pass');
    const encryptNode = document.querySelector('#encrypt');
    const decryptNode = document.querySelector('#decrypt');

    encryptNode.addEventListener('click', () => {
      if (!passNode.value) return alert('Password input is empty! Aborting.');
      const pass = CryptoJS.SHA3(passNode.value);
      pickAFile(false).then((file) => {
        const reader = new FileReader();

        reader.onload = (e) => {
          const wordArray = CryptoJS.lib.WordArray.create(e.target.result);
          const encrypted = CryptoJS.RC4.encrypt(wordArray, pass).toString();
          download(encrypted, `encrypted-${file.name}`, file.type);
        };

        reader.readAsArrayBuffer(file);
      });
    });

    decryptNode.addEventListener('click', () => {
      if (!passNode.value) return alert('Password input is empty! Aborting.');
      const pass = CryptoJS.SHA3(passNode.value);
      pickAFile(false).then((file) => {
        const reader = new FileReader();

        reader.onload = (e) => {
          try {
            const decrypted = CryptoJS.RC4.decrypt(e.target.result, pass);
            const typedArray = convertWordArrayToUint8Array(decrypted);
            download(typedArray, `decrypted-${file.name}`, file.type);
          } catch (error) {
            console.log('wrong password!');
          }
        };

        reader.readAsText(file);
      });
    });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', app);
  } else {
    app();
  }
</script>

推荐答案

从RC4转换为AES时,请考虑以下事项:

  • AES定义的密钥大小为16(AES-128)、24(AES-192)和32字节(AES-256).密钥越大,就越安全(尽管现在所有的变体都被认为是安全的).在下文中,使用32字节的密钥.

    您应用的SHA3 CryptoJS实现(实际上是Keccak,请参见Hashing/SHA-3)的默认输出大小为64字节,因此不能直接用作AES密钥.因此,为简单起见,我使用输出大小为32字节的SHA-256.您也可以使用SHA-3,并且只获取例如前32个字节.

    但是,通过摘要派生密钥是一个漏洞.更安全的是使用像Argon2或PBKDF2这样的密钥派生.后者受CryptoJS支持,因此我建议改用PBKDF2.由于我在这里将重点放在RC4到AES的转换上,所以我将把这一更改留给您.

  • 在CBC模式(CryptoJS默认使用该模式;默认填充为PKCS#7 btw)下,AES需要一个随机IV(其长度等于块大小,因此对于AES为16字节).出于安全原因,这不能是静态的,而必须为每次加密随机生成(CSPRNG).

    IV不是秘密的,需要解密.因此,它通常与密文连接:IV|密文.

    在解密之前,根据已知的IV的长度来分离IV和密文.

考虑到上述几点,从RC4到AES的可能转换是(另请参阅代码中的注释以了解更改的解释):

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js" integrity="sha512-E8QSvWZ0eCLGk4km3hxSsNmGWbLtSCSUcewDQPQWZF6pEU8GlT8a5fF32wOl1i8ftdMhssTrF/OhyGWwonTcXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<div>
  <h1>encrypt/decrypt file</h1>
  <ol>
    <li>Set password</li>
    <li>Pick a file</li>
    <li>Download decrypted/encrypted file</li>
  </ol>
  <div>
    <input type="text" id="pass" placeholder="pass">
    <button id="encrypt">encrypt file</button>
    <button id="decrypt">decrypt file</button>
    <button id="test">test</button>
  </div>
</div>

<script>
  // support
  const download = (data, filename, type) => {
    const file = new Blob([data], {
      type: type
    });
    const a = document.createElement('a');
    const url = URL.createObjectURL(file);

    a.href = url;
    a.download = filename;
    document.body.appendChild(a);

    a.click();

    setTimeout(function() {
      document.body.removeChild(a);
      window.URL.revokeObjectURL(url);
    }, 0);
  };
  const pickAFile = (getText = true) => {
    return new Promise((resolve, reject) => {
      const input = document.createElement('input');
      input.type = 'file';
      input.onchange = (e) => {
        const file = e.target.files[0];
        const reader = new FileReader();
        if (!getText) {
          resolve(file);
        } else {
          reader.onload = (e) => resolve(e.target.result);
          reader.onerror = (e) => reject(e);
          reader.readAsText(file);
        }
      };
      input.click();
    });
  };
  const convertWordArrayToUint8Array = (wordArray) => {
    const arrayOfWords = wordArray.hasOwnProperty('words') ? wordArray.words : [];

    const length = wordArray.hasOwnProperty('sigBytes') ?
      wordArray.sigBytes :
      arrayOfWords.length * 4;

    const uInt8Array = new Uint8Array(length);
    let index = 0;
    let word;
    let i;

    for (i = 0; i < length; i++) {
      word = arrayOfWords[i];
      uInt8Array[index++] = word >> 24;
      uInt8Array[index++] = (word >> 16) & 0xff;
      uInt8Array[index++] = (word >> 8) & 0xff;
      uInt8Array[index++] = word & 0xff;
    }
    return uInt8Array;
  };
  // /support

  function app() {
    const passNode = document.querySelector('input#pass');
    const encryptNode = document.querySelector('#encrypt');
    const decryptNode = document.querySelector('#decrypt');

    encryptNode.addEventListener('click', () => {
      if (!passNode.value) return alert('Password input is empty! Aborting.');
      const key = CryptoJS.SHA256(passNode.value); // Fix 1: Derive 32 bytes key
      pickAFile(false).then((file) => {
        const reader = new FileReader();

        reader.onload = (e) => {
          const iv = CryptoJS.lib.WordArray.random(16); // Fix 2: Create random 16 bytes IV
          const wordArray = CryptoJS.lib.WordArray.create(e.target.result);
          const encrypted = CryptoJS.AES.encrypt(wordArray, key, {iv: iv}); // Fix 3: Encrypt with AES using the above key and IV
          const ivCiphertext = iv.clone().concat(encrypted.ciphertext).toString(CryptoJS.enc.Base64); // Fix 4: Concatenate IV and ciphertext
         download(ivCiphertext, `encrypted-${file.name}`, file.type);
        };

        reader.readAsArrayBuffer(file);
      });
    });

    decryptNode.addEventListener('click', () => {
      if (!passNode.value) return alert('Password input is empty! Aborting.');
      const key = CryptoJS.SHA256(passNode.value); // Fix 5: Derive 32 bytes key
      pickAFile(false).then((file) => {
        const reader = new FileReader();

        reader.onload = (e) => {
          try {
            const ivCiphertext = CryptoJS.enc.Base64.parse(e.target.result); // Fix 6: Separate IV and ciphertext
            const iv = CryptoJS.lib.WordArray.create(ivCiphertext.words.slice(0, 4)); 
            const ciphertext = CryptoJS.lib.WordArray.create(ivCiphertext.words.slice(4)); 
            const decrypted = CryptoJS.AES.decrypt({ciphertext: ciphertext}, key, {iv: iv}); // Fix 7: Decrypt
            const typedArray = convertWordArrayToUint8Array(decrypted);
            download(typedArray, `decrypted-${file.name}`, file.type);
          } catch (error) {
            console.log('wrong password!');
          }
        };

        reader.readAsText(file);
      });
    });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', app);
  } else {
    app();
  }
</script>

请注意,代码仍然可以优化(这也适用于RC4变体):在加密期间,数据(即连接的IV和密文)是Base64编码的.Base64是一种二进制到文本的编码,它将数据大小增加了约33%.当任意二进制数据要表示为文本时使用.
然而,由于数据在这里以文件形式存储,这样的转换并不是真正必要的(当然,可能有一些从POST中看不出来的原因).相反,可以存储原始数据(即非Base64编码的数据),这将相应地减小文件大小.

Javascript相关问答推荐

在JavaScript中逐一播放随机生成的音频文件数组

我的YouTube视频没有以html形式显示,以获取免费加密信号

使用JavaScript重命名对象数组中的键

如何使用React渲染器放置根dis?

如何在JavaScript中在文本内容中添加新行

有条件的悲剧

当在select中 Select 选项时,我必须禁用不匹配的 Select

Google图表时间轴—更改hAxis文本 colored颜色

如何修复我的js构建表每当我添加一个额外的列作为它的第一列?

在拖放时阻止文件打开

JSDoc创建并从另一个文件导入类型

如何将Cookie从服务器发送到用户浏览器

当使用';字母而不是与';var#39;一起使用时,访问窗口为什么返回未定义的?

如何创建返回不带`new`关键字的实例的类

基于props 类型的不同props ,根据来自接口的值扩展类型

当代码另有说明时,随机放置的圆圈有时会从画布上消失

Phaserjs-创建带有层纹理的精灵层以自定义外观

Next.js中的服务器端组件列表筛选

如何组合Multer上传?

与在编剧中具有动态价值的定位器交互