前端加密

日常开发偶尔会涉及到加解密的需求,比如对登录密码加密、解密接口返回的敏感数据,这里简单总结 CryptoJS、JSEncrypt 等第三方库、crypto 和 Web Crypto API 使用。

CryptoJS (crypto-js)

CryptoJS (crypto-js),实现了 DES/AES/SHA256/HMAC-SHA512 等算法(没有实现 RSA 算法),支持浏览器和 Node.js,作者 Jeff Mott,最初开源在 Google Code https://code.google.com/p/crypto-js/,最后一版为 v3.1.2

目前 npm 上的 crypto-js 包由 Evan Vosberg 维护,GitHub 地址为 https://github.com/brix/crypto-js,文档地址 https://cryptojs.gitbook.io/docs/

使用 crypto-js 进行简单的 3DES 加解密:

import Base64 from 'crypto-js/enc-base64';
import Hex from 'crypto-js/enc-hex';
import Utf8 from 'crypto-js/enc-utf8';
import TripleDES from 'crypto-js/tripledes';
import ECB from 'crypto-js/mode-ecb';
import Pkcs7Pad from 'crypto-js/pad-pkcs7';
import HexFormat from 'crypto-js/format-hex';

/**
 * 3DES 加密
 * @param content 明文
 * @param key 密钥
 * @returns
 */
const encrypt = (content: string, key: string) => {
  return TripleDES.encrypt(content, Utf8.parse(key), {
    mode: ECB,
    padding: Pkcs7Pad
  }).toString(HexFormat);
};

/**
 * 3DES 解密
 * @param content 密文
 * @param key 密钥
 * @returns
 */
const decrypt = (content: string, key: string) => {
  const encrypted = Base64.stringify(Hex.parse(content));
  return TripleDES.decrypt(encrypted, Utf8.parse(key), {
    mode: ECB,
    padding: Pkcs7Pad
  }).toString(Utf8);
};

const privateKey = '123456789012345678901234';
const plaintext = 'keqingrong@outlook.com';
const ciphertext = 'f5acc6d5a16c4ed1409c1d802809078f7632e10aa8b98aba';

const encrypted = encrypt(plaintext, privateKey);
console.assert(encrypted === ciphertext);

const decrypted = decrypt(ciphertext, privateKey);
console.assert(decrypted === plaintext);

JSEncrypt (jsencrypt)

JSEncrypt 实现了 RSA 加密算法,由 Travis Tidwell 在 Tom Wu 的 jsbn http://www-cs-students.stanford.edu/~tjw/jsbn/ 基础上开发。JSEncrypt 目前只支持浏览器,不支持 Node,见 https://github.com/travist/jsencrypt/issues/56

在使用 RSA 加密算法之前,需要使用 OpenSSL 生成私钥,再根据私钥生成公钥。

openssl genrsa -out rsa_1024_priv.pem 1024
openssl rsa -pubout -in rsa_1024_priv.pem -out rsa_1024_pub.pem

这样我们便得到了私钥 rsa_1024_priv.pem 和公钥 rsa_1024_pub.pem

使用公钥加密:

import JSEncrypt from 'jsencrypt';

const publicKey = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKKqgnPzneyl2kz7G3yyBgeIKk
UPk17DHfvU0ElcZt1tJpqFgG8L6X4Xb3QyDagxqa/HKMSDrlpRakbzgJTs6kfFCo
fIvNhzQwQNfEnpKdWK7Im7dizPIzIi58ogFHToDAEWfOtpaXMiqY6pknvtVSJHMN
lbDJ3WDkPKj8KOUBGQIDAQAB
-----END PUBLIC KEY-----`;

const encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);
const plaintext = 'keqingrong@outlook.com';
const encrypted = encrypt.encrypt(plaintext);
console.log(encrypted);

使用私钥解密:

import JSEncrypt from 'jsencrypt';

const privateKey = `-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDKKqgnPzneyl2kz7G3yyBgeIKkUPk17DHfvU0ElcZt1tJpqFgG
8L6X4Xb3QyDagxqa/HKMSDrlpRakbzgJTs6kfFCofIvNhzQwQNfEnpKdWK7Im7di
zPIzIi58ogFHToDAEWfOtpaXMiqY6pknvtVSJHMNlbDJ3WDkPKj8KOUBGQIDAQAB
AoGAYuj73DfS2G2p4zi6enGnJYvQXxQ+2WL2A8FaLSQaMSMpTwhOCRdAKI7m5ZKy
QDZkje91G607I5/htBG2GNe1wWUNMgJmuPMuI738wYFRVj/VhFgfoB2XiKgzvA3q
CFyrOPt1d4H8XXMdAFEYpqMCc2bgcFrKWB+kZMQ715pboAECQQDxqXdbMFaIhB4m
Tg+wWnrUVUtWq1RrKxkZaf46hMKLPpdVrWjzgMY9DvMob8s3wTLcDXFmtBUHD5Zb
SVLgCaqJAkEA1ilOpHsuvXtByIWffB7oo65HQKwN5KegbG/kljE6Gz7rnjtEneFy
DCV02PTL9Jxf2NhlCTeSxwQcgInZpne+EQJADvHxDLWnlFe/WZUYSUq/L+R6fUip
Ntt6eOTiMRJGyb+8MjNAO1bqa5pCFW0cfz02fP9j1PssFby0Cr81Hd/bKQJBALzu
RAqnAVz319jmyQPe4K1FmmZbYwZNOyFutOIrG2/d2k8FSkteEBbXFHYxv5xUN9o9
TSUMedhIsDxVYEWTbYECQFF4KoYq2iBud98xNZoaMUToUNaF9WJGNYqCXyOUMk3A
Hohxi0kf+Nx1/+AVGFEPR1+FJgqFHEAaimgEL53mfU8=
-----END RSA PRIVATE KEY-----`;

const encrypt = new JSEncrypt();
encrypt.setPrivateKey(privateKey);
const decrypted = encrypt.decrypt(encrypted);
console.log(decrypted === plaintext);

Node-RSA (node-rsa)

Node-RSA 是 RSA 加密算法的 Node 实现,同样基于 jsbn 开发。

使用公钥加密:

const NodeRSA = require('node-rsa');
const publicKey = `省略,同上`;
const publicKeyObj = new NodeRSA(publicKey);
const plaintext = 'keqingrong@outlook.com';
const encrypted = publicKeyObj.encrypt(plaintext, 'base64');
console.log(encrypted);

使用私钥解密:

const NodeRSA = require('node-rsa');
const privateKey = `省略,同上`;
const privateKeyObj = new NodeRSA(privateKey);
const decrypted = privateKeyObj.decrypt(encrypted, 'utf8');
console.log(decrypted);

Node crypto

除了使用第三方库,Node 环境可以直接使用内置的 crypto 模块,基于 OpenSSL 封装。

使用公钥进行 RSA 加密:

const crypto = require('crypto');
const publicKey = `省略,同上`;
const plaintext = 'keqingrong@outlook.com';
const encrypted = crypto
  .publicEncrypt(
    {
      key: publicKey,
      padding: crypto.constants.RSA_PKCS1_PADDING
    },
    Buffer.from(plaintext)
  )
  .toString('base64');
console.log(encrypted);

使用私钥进行 RSA 解密:

const crypto = require('crypto');
const privateKey = `省略,同上`;
const decrypted = crypto
  .privateDecrypt(
    {
      key: privateKey,
      padding: crypto.constants.RSA_PKCS1_PADDING
    },
    Buffer.from(encrypted, 'base64')
  )
  .toString();

console.log(decrypted === plaintext);

crypto 模块包含的类、方法、属性:

    • Certificate
    • Cipher
    • Decipher
    • DiffieHellman
    • DiffieHellmanGroup
    • ECDH
    • Hash
    • Hmac
    • KeyObject
    • Sign
    • Verify
    • X509Certificate
  • 方法和属性
    • crypto.constants
    • crypto.createPrivateKey()
    • crypto.createPublicKey()
    • crypto.privateDecrypt()
    • crypto.privateEncrypt()
    • crypto.publicDecrypt()
    • crypto.publicEncrypt()
    • crypto.webcrypto

Web Cryptography API

Web Cryptography API 是 W3C 下属 Web 密码学工作组(Web Cryptography Working Group)为 Web 制定的密码学领域规范。包含以下 API:

  • Crypto
    • crypto.subtle
    • crypto.getRandomValues()
    • crypto.randomUUID()
  • CryptoKey
    • cryptoKey.type
    • cryptoKey.extractable
    • cryptoKey.algorithm
    • cryptoKey.usages
  • CryptoKeyPair
    • cryptoKeyPair.privateKey
    • cryptoKeyPair.publicKey
  • SubtleCrypto
    • subtle.encrypt()
    • subtle.decrypt()
    • subtle.sign()
    • subtle.verify()
    • subtle.digest()
    • subtle.generateKey()
    • subtle.deriveKey()
    • subtle.deriveBits()
    • subtle.importKey()
    • subtle.exportKey()
    • subtle.wrapKey()
    • subtle.unwrapKey()

Chrome、Safari、Firefox 等现代浏览器最新版和 Node.js v15 都已经实现 Web Cryptography 大部分 API,可以参考 Can I use

相关链接