前端AES + RSA加密

前言

在数据传输过程中,可能存在数据被窃取的安全隐患(主要http)。
对于安全需求较高的项目而言,加密是保证数据安全的最直接的方式。

  • 加密是指将明文通过加密算法加密密钥转为密文
  • 解密是指将密文通过解密算法解密密钥转为明文

分类

常见加密方式分为以下两类:

  • 对称加密:采用对称密码编码技术,加/解密使用相同密钥进行,效率较高。
  • 非对称加密:基于密钥交换协议,拥有公开密钥私有密钥,使用公钥加密后需使用对应私钥才能进行解密。

Tips:

MD5、SHA256等一般称为数据摘要算法(采用哈希算法),即将数据块映射为定长的摘要信息,过程是单向的(无法反推原数据),一般用于数据验签(校验数据是否完整或校验数据是否被篡改)。

  • 常见对称加密有AES、DES、3DES等,这里选用AES实现。
  • 常见非对称加密有RSA、ECC等,这里选用RSA实现。

构思

以中后台管理项目为例:

  • 对于简单项目(如单一管理员项目等),可以简单使用md5/SHA256等哈希算法实现对密码的保存(即数据库存储用户输入密码的摘要信息),并传递提交参数的哈希算法后的信息摘要(防止信息中途被篡改)。
  • 一般项目,前后端通过协商好的密钥(或统一约定公私钥),使用AES或RSA对提交数据(或关键数据)进行加密,后台接收后解密后获得数据。

需完善点:

  • AES密钥或RSA私钥、密钥如何传递给前后端
  • 密钥何时更新
  • 若使用RSA加密方式,对于大量数据加、解密操作较浪费性能和时间

设想:
鉴于AES较高的性能及RSA公私钥方式的易用性,能否结合这两者的优点实现对数据安全的保障?

以下是构思的业务流程:

  • RSA公、私钥在每次服务器接收到预登录请求时生成,预登录成功后将RSA公钥传递给前端
  • AES密钥在正式登录时由前端生成,通过服务器返回的RSA公钥进行加密,并使用AES密钥对密码等重要信息进行加密,统一传给服务器
  • 服务器使用RSA密钥解密即可获得AES密钥,使用该AES密钥解密前端提交内容,若验证成功登录,则存储该AES密钥直至Session过期(退出、重复登录、长时间无操作等)
  • 成功登录后,前端也保存该AES密钥。在后续请求中,前端使用AES密钥加密重要数据提交给服务器即可
简易时序图

实现

这里结合Vue实现
Tip:

  • 这里仅展示实现思路,具体预登录/登录验证方式可根据实际需求进行修改
  • axios请求、拦截器等封装思路,后续会单独介绍
  • RSA依赖包:node-rsa 填充模式:pkcs1
  • AES依赖包:crypto-js 填充模式:pkcs7 采用cbc模式(密钥16位 128字节)

预登录

// 预登录 获取后台公钥
preLogin (formName) {
  // 校验 预登录表单(此处为element-ui,其他ui框架类似)
  this.$refs[formName].validate(async (valid) => {
    if (valid) {
      const res = await adminPreLogin({
        name: this.form.username, // 用户名
        verifyCode: this.form.verificationCode, // 验证码(通过get请求+tag参数获取验证码图片)
        tag: this.uuid // 获取验证码时的tag(可使用uuid或时间戳+随机数等方式生成)
      })
      if (res.data.res === 0) {
        // 正式登录 传入后台返回的公钥及后台生成此次预登录的preSessionId
        // 正式登录后,后台可根据此preSessionId查找对应的RSA私钥进行解密
        // 同一时间内可能存在多人同时登录账号(引入preSessionId解决)
        this.login(res.data.publicKey, res.data.preSessionId)
      } else {
        // 预登录失败,则刷新验证码,重置验证码输入框
        this.uuid = uuidv4()
        this.$set(this.form, 'verificationCode', '')
      }
    }
  })
}

正式登录

async login (publicKey, preSessionId) {
  // 随机生成 构成key iv的字符串(这里都采用AES密钥长度16位)
  let keyString = randomGenerate(16) // 这里使用工具类随机生成(详细实现见下文)
  let ivString = randomGenerate(16)
  // 生成AES 密钥和向量
  let key = CryptoJS.enc.Utf8.parse(keyString) // AES密钥
  let iv = CryptoJS.enc.Utf8.parse(ivString) // AES向量
  // RSA公钥加密
  let pubKey = new NodeRSA(publicKey)
  pubKey.setOptions({ encryptionScheme: 'pkcs1' }) // 设置填充方式,前、后端保持统一即可
  // 这里拆分出
  let pwd = CryptoJS.AES.encrypt(
    this.form.password, // 加密原文
    key, // AES密钥
    {
      iv: iv, // AES向量
      mode: CryptoJS.mode.CBC, // 指定CBC模式
      padding: CryptoJS.pad.Pkcs7 // 指定pkcs7填充模式
    })
  // 发送正式登录请求
  const res = await adminLogin({
    name: this.form.username,
    password: pwd,
    authKey: pubKey.encrypt(keyString, 'base64'), // 加密后base64编码
    authIv: pubKey.encrypt(ivString, 'base64'),
    preSessionId: preSessionId // 预登录返回的preSessionId
  })
  if (res.data.res === 0) {
    // 验证成功后,可存储AES密钥和向量,后续请求关键信息使用AES加密后上传即可
    sessionStorage.setItem('aesKey', keyString)
    sessionStorage.setItem('aesIv', ivString)
    this.$router.push({ name: 'xxx-home' }) // 进入管理主页面
    // 还可以存储token、session等信息,并提示用户登录成功 这里不做展示
  } else {
    // 正式登录失败,则刷新验证码,重置验证码输入框
    this.uuid = uuidv4()
    this.$set(this.form, 'verificationCode', '')
  }
}

附:简单生成随机字符串工具类

// 生成随机字符串
function randomGenerate (length) {
  const chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
    'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
  if (!Number.isInteger(length) || length <= 0) { // 合法性校验
    console.error('请检查输入随机字符串长度是否为正整数!')
    return 'Error'
  }
  let randomString = ''
  for (let i = 0; i < length; i++) {
    randomString += chars[Math.floor(Math.random() * chars.length)]
  }
  return randomString
}

export { randomGenerate }

总结

结合两种加密方式使用,可以满足大多数的使用场景,发挥出各自的优点。
理解AES + RSA的结合加密思路后,实现方式可根据具体情况修改。

————加密定义及分类摘自百度百科

欢迎大家积极讨论,共同进步。
我的掘金

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,454评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,553评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,921评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,648评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,770评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,950评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,090评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,817评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,275评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,592评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,724评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,409评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,052评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,815评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,043评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,503评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,627评论 2 350

推荐阅读更多精彩内容