web端也支持Crypto加密对象了

11709次浏览

前言

之前文章有写关于加密的javascript用户密码加密,js密码加密,是很久之前写的了,有点过时。其实web支持前端加密了,喜大普奔,可以不用引入js进行前端加密。

Crypto对象

我们看下Crypto对象的兼容性

enter image description here

crypto.subtle对象

crypto 对象还支持一个名为 subtle 的属性,目前唯一的属性,返回的属性值是一个对象,称为 SubtleCrypto 对象,可以用来生成各种签名和密钥,所有方法均返回 Promise,包括:

SubtleCrypto.encrypt()
SubtleCrypto.decrypt()
SubtleCrypto.sign()
SubtleCrypto.verify()
SubtleCrypto.digest()
SubtleCrypto.generateKey()
SubtleCrypto.deriveKey()
SubtleCrypto.deriveBits()
SubtleCrypto.importKey()
SubtleCrypto.exportKey()
SubtleCrypto.wrapKey()
SubtleCrypto.unwrapKey()

SubtleCrypto.encrypt()

语法:

var result = crypto.encrypt(algo, key, cleartext);

algo 是一个使用加密函数的对象或者 DOMString,后者是 {"name": algo} 的缩写。支持的值是:

{"name": "AES-CBC", iv} iv 是具有16个随机字节的 ArrayBuffer 或 ArrayBufferView (这些应该由 RandomSource.getRandomValues() 生成)。

{"name": "AES-CTR", counter, length}

{"name": "AES-GCM", iv, additionalData, tagLength} (additionalData 和 tagLength 是可选的)

{"name": "RSA-OAEP", label} (label 是可选的)

key 是一个包含签名密钥的 CryptoKey。

cleartext 是一个包含需要加密的明文 ArrayBuffer 或者 ArrayBufferView 对象。

案例:

(() => {

  /*
  Store the calculated ciphertext and counter here, so we can decrypt the message later.
  */
  let ciphertext;
  let counter;

  /*
  Fetch the contents of the "message" textbox, and encode it
  in a form we can use for the encrypt operation.
  */
  function getMessageEncoding() {
    const messageBox = document.querySelector("#aes-ctr-message");
    let message = messageBox.value;
    let enc = new TextEncoder();
    return enc.encode(message);
  }

  /*
  Get the encoded message, encrypt it and display a representation
  of the ciphertext in the "Ciphertext" element.
  */
  async function encryptMessage(key) {
    let encoded = getMessageEncoding();
    // The counter block value must never be reused with a given key.
    counter = window.crypto.getRandomValues(new Uint8Array(16)),
    ciphertext = await window.crypto.subtle.encrypt(
      {
        name: "AES-CTR",
        counter,
        length: 64
      },
      key,
      encoded
    );

    let buffer = new Uint8Array(ciphertext, 0, 5);
    const ciphertextValue = document.querySelector(".aes-ctr .ciphertext-value");
    ciphertextValue.classList.add('fade-in');
    ciphertextValue.addEventListener('animationend', () => {
      ciphertextValue.classList.remove('fade-in');
    });
    ciphertextValue.textContent = `${buffer}...[${ciphertext.byteLength} bytes total]`;
  }

  /*
  Fetch the ciphertext and decrypt it.
  Write the decrypted message into the "Decrypted" box.
  */
  async function decryptMessage(key) {
    let decrypted = await window.crypto.subtle.decrypt(
      {
        name: "AES-CTR",
        counter,
        length: 64
      },
      key,
      ciphertext
    );

    let dec = new TextDecoder();
    const decryptedValue = document.querySelector(".aes-ctr .decrypted-value");
    decryptedValue.classList.add('fade-in');
    decryptedValue.addEventListener('animationend', () => {
      decryptedValue.classList.remove('fade-in');
    });
    decryptedValue.textContent = dec.decode(decrypted);
  }

  /*
  Generate an encryption key, then set up event listeners
  on the "Encrypt" and "Decrypt" buttons.
  */
  window.crypto.subtle.generateKey(
    {
        name: "AES-CTR",
        length: 256
    },
    true,
    ["encrypt", "decrypt"]
  ).then((key) => {
    const encryptButton = document.querySelector(".aes-ctr .encrypt-button");
    encryptButton.addEventListener("click", () => {
      encryptMessage(key);
    });

    const decryptButton = document.querySelector(".aes-ctr .decrypt-button");
    decryptButton.addEventListener("click", () => {
      decryptMessage(key);
    });
  });
})();

SubtleCrypto.sign()

//sign

signature = await window.crypto.subtle.sign(
  {
    name: "RSA-PSS",
    saltLength: 32,
  },
  privateKey,
  '加密数据data'
);

详细请看:https://developer.mozilla.org/zh-CN/docs/Web/API/Crypto

getRandomValues

上文有提到getRandomValues和 Math.random() 方法的区别在于,getRandomValues() 方法的随机种子生成器更加的无序,例如系统层面的无序源(有些硬件自带随机种子)。

然后不同浏览器下 getRandomValues() 方法生成的随机数可能是有区别的。

以及 getRandomValues() 方法的底层实现是没有缓存的,随机数都是实时生成的,因此,性能上是要比 Math.random() 差的,因此,如果是高并发的场景,同时随机数仅仅是用做随机,与安全和金钱不相关,请使用 Math.random() 而不是 getRandomValues()。

let randNumber = self.crypto.getRandomValues(new Uint32Array(1))[0];
// 一串随机整数,通常10位
console.log(randNumber);

UUID生成

除了生成随机数,Crypto对象还可以用来生成字符长度为36的 UUID (Universally Unique Identifier的缩写,表示唯一通用标识符)

let uuid = self.crypto.randomUUID();
console.log(uuid);
// 示意输出:2433df46-d77f-4eb9-bbdd-4cd99361fe08

不过这个 API 只有Chrome 92+,才支持。

关于uuid的生产,我前面有文章介绍 https://www.haorooms.com/post/nanoid_uuid

感兴趣的可以对比看一看。

相关文章: