前端生成密钥并加解密防止内存读取密钥

149 阅读5分钟

1. 后端,web和小程序统一加密协议(都使用AES-GCM)

// 统一加密协议规范
const CRYPTO_PROTOCOL = {
  version: '1.0',
  algorithm: 'AES-256-GCM',
  keyDerivation: 'PBKDF2',
  keyLength: 256,
  ivLength: 96,
  tagLength: 128,
  iterations: 100000
}

2. 后端统一加密服务(java等后端略)


// Node.js 后端实现
const crypto = require('crypto')

class UnifiedCryptoService {
  constructor() {
    this.sessions = new Map()
  }
  
  // 生成会话密钥
  generateSessionKey(userId, deviceId, platform) {
    const sessionId = crypto.randomUUID()
    const sessionKey = crypto.randomBytes(32)
    
    const sessionInfo = {
      sessionId,
      userId,
      deviceId,
      platform, // 标识平台:web/miniprogram/backend
      key: sessionKey,
      createdAt: Date.now(),
      expiresAt: Date.now() + (24 * 60 * 60 * 1000),
      lastUsed: Date.now()
    }
    
    this.sessions.set(sessionId, sessionInfo)
    
    return {
      sessionId,
      expiresAt: sessionInfo.expiresAt
    }
  }
  
  // 统一的服务端加密(AES-256-GCM)
  encrypt(sessionId, plaintext) {
    const session = this.sessions.get(sessionId)
    if (!session || session.expiresAt < Date.now()) {
      throw new Error('会话不存在或已过期')
    }
    
    session.lastUsed = Date.now()
    
    const iv = crypto.randomBytes(12) // 96位IV
    const cipher = crypto.createCipherGCM('aes-256-gcm', session.key)
    cipher.setIVLength(12)
    cipher.update(iv)
    
    let encrypted = cipher.update(plaintext, 'utf8', 'hex')
    encrypted += cipher.final('hex')
    
    const tag = cipher.getAuthTag()
    
    return {
      iv: iv.toString('hex'),
      data: encrypted,
      tag: tag.toString('hex'),
      algorithm: CRYPTO_PROTOCOL.algorithm
    }
  }
  
  // 统一的服务端解密
  decrypt(sessionId, encryptedPacket) {
    const session = this.sessions.get(sessionId)
    if (!session || session.expiresAt < Date.now()) {
      throw new Error('会话不存在或已过期')
    }
    
    session.lastUsed = Date.now()
    
    const { iv, data, tag } = encryptedPacket
    
    const decipher = crypto.createDecipherGCM('aes-256-gcm', session.key)
    decipher.setIVLength(12)
    decipher.update(Buffer.from(iv, 'hex'))
    decipher.setAuthTag(Buffer.from(tag, 'hex'))
    
    let decrypted = decipher.update(data, 'hex', 'utf8')
    decrypted += decipher.final('utf8')
    
    return decrypted
  }
}

3. Web 浏览器实现(Web Crypto API 隔离)

// Web浏览器版本 - 仅使用Web Crypto API的密钥隔离
class WebCryptoClient {
  constructor() {
    this.localKey = null // Web Crypto API密钥(不可导出)
    this.keyId = null
  }
  
  // 生成本地Web Crypto密钥(不可导出)
  async generateKey() {
    try {
      this.localKey = await window.crypto.subtle.generateKey(
        {
          name: 'AES-GCM',
          length: 256
        },
        false, // 不可导出!这是关键的隔离特性
        ['encrypt', 'decrypt']
      )
      
      // 生成密钥ID用于标识
      this.keyId = await this.generateKeyId()
      
      return this.keyId
    } catch (error) {
      throw new Error('本地密钥生成失败: ' + error.message)
    }
  }
  
  // 生成密钥ID
  async generateKeyId() {
    const randomData = window.crypto.getRandomValues(new Uint8Array(32))
    const hashBuffer = await window.crypto.subtle.digest('SHA-256', randomData)
    const hashArray = Array.from(new Uint8Array(hashBuffer))
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
  }
  
  // 加密数据
  async encrypt(data) {
    if (!this.localKey) {
      throw new Error('密钥未初始化')
    }
    
    const iv = window.crypto.getRandomValues(new Uint8Array(12))
    const encoder = new TextEncoder()
    const encodedData = encoder.encode(JSON.stringify(data))
    
    const encrypted = await window.crypto.subtle.encrypt(
      {
        name: 'AES-GCM',
        iv: iv
      },
      this.localKey, // 使用不可导出的密钥
      encodedData
    )
    
    return {
      keyId: this.keyId,
      iv: Array.from(iv).map(b => b.toString(16).padStart(2, '0')).join(''),
      data: Array.from(new Uint8Array(encrypted)).map(b => b.toString(16).padStart(2, '0')).join(''),
      algorithm: CRYPTO_PROTOCOL.algorithm,
      platform: 'web'
    }
  }
  
  // 解密数据
  async decrypt(encryptedPacket) {
    if (!this.localKey) {
      throw new Error('密钥未初始化')
    }
    
    if (encryptedPacket.keyId !== this.keyId) {
      throw new Error('密钥ID不匹配')
    }
    
    const { iv, data } = encryptedPacket
    
    const ivArray = new Uint8Array(iv.match(/.{2}/g).map(byte => parseInt(byte, 16)))
    const dataArray = new Uint8Array(data.match(/.{2}/g).map(byte => parseInt(byte, 16)))
    
    const decrypted = await window.crypto.subtle.decrypt(
      {
        name: 'AES-GCM',
        iv: ivArray
      },
      this.localKey, // 使用不可导出的密钥
      dataArray
    )
    
    const decoder = new TextDecoder()
    const decryptedText = decoder.decode(decrypted)
    return JSON.parse(decryptedText)
  }
  
  // 检查是否支持Web Crypto API
  static isSupported() {
    return typeof window !== 'undefined' && 
           window.crypto && 
           window.crypto.subtle &&
           typeof window.crypto.subtle.generateKey === 'function'
  }
  
  // 销毁密钥(虽然密钥不可导出,但可以失去引用)
  destroy() {
    this.localKey = null
    this.keyId = null
  }
}

4. 小程序实现(基于用户身份和设备特征的密钥隔离)

// 小程序版本 - 仅使用平台隔离机制
class MiniProgramCryptoClient {
  constructor() {
    this.platformKeyHandle = null
    this.keyId = null
  }
  
  // 生成平台隔离密钥
  async generateKey() {
    try {
      // 获取用户授权数据和设备指纹
      const userAuthData = await this.getUserAuthData()
      const deviceFingerprint = await this.getDeviceFingerprint()
      
      // 创建平台密钥句柄
      this.platformKeyHandle = await this.createPlatformKeyHandle(
        userAuthData, 
        deviceFingerprint
      )
      
      // 生成密钥ID
      this.keyId = this.generateKeyId()
      
      return this.keyId
    } catch (error) {
      throw new Error('平台密钥生成失败: ' + error.message)
    }
  }
  
  // 获取用户授权数据(微信提供的加密数据)
  async getUserAuthData() {
    return new Promise((resolve) => {
      wx.getUserProfile({
        desc: '用于安全加密',
        success: (res) => {
          resolve({
            encryptedData: res.encryptedData,
            iv: res.iv,
            signature: res.signature
          })
        },
        fail: () => {
          // 如果用户拒绝授权,使用设备信息
          console.warn('用户授权失败,使用设备标识')
          resolve(null)
        }
      })
    })
  }
  
  // 获取设备指纹
  async getDeviceFingerprint() {
    const systemInfo = wx.getSystemInfoSync()
    const networkInfo = await this.getNetworkInfo()
    
    return {
      model: systemInfo.model,
      system: systemInfo.system,
      version: systemInfo.version,
      platform: systemInfo.platform,
      brand: systemInfo.brand,
      screenWidth: systemInfo.screenWidth,
      screenHeight: systemInfo.screenHeight,
      pixelRatio: systemInfo.pixelRatio,
      networkType: networkInfo.networkType,
      appId: wx.getAccountInfoSync().miniProgram.appId,
      sessionStart: Date.now()
    }
  }
  
  // 获取网络信息
  async getNetworkInfo() {
    return new Promise((resolve) => {
      wx.getNetworkType({
        success: resolve,
        fail: () => resolve({ networkType: 'unknown' })
      })
    })
  }
  
  // 生成密钥ID
  generateKeyId() {
    const CryptoJS = require('crypto-js')
    const randomData = Date.now().toString() + Math.random().toString()
    return CryptoJS.SHA256(randomData).toString()
  }
  
  // 创建平台密钥句柄
  async createPlatformKeyHandle(userAuthData, deviceFingerprint) {
    const CryptoJS = require('crypto-js')
    
    // 生成基于平台的密钥材料
    const keyMaterial = JSON.stringify({
      userAuth: userAuthData,
      device: deviceFingerprint,
      timestamp: Date.now()
    })
    
    const keyHandle = CryptoJS.SHA256(keyMaterial + Math.random()).toString()
    
    // 将密钥材料进行分片和混淆存储
    await this.storeKeyMaterialSecurely(keyHandle, keyMaterial)
    
    return keyHandle
  }
  
  // 安全存储密钥材料(多层加密和分片)
  async storeKeyMaterialSecurely(keyHandle, keyMaterial) {
    const CryptoJS = require('crypto-js')
    
    try {
      // 第一层:使用设备相关信息加密
      const deviceKey = this.getDeviceSpecificKey()
      const layer1 = CryptoJS.AES.encrypt(keyMaterial, deviceKey).toString()
      
      // 第二层:使用时间戳和随机数加密
      const timeKey = Date.now().toString() + Math.random().toString()
      const layer2 = CryptoJS.AES.encrypt(layer1, timeKey).toString()
      
      // 分片存储(增加攻击难度)
      const fragment1 = layer2.substring(0, Math.floor(layer2.length / 2))
      const fragment2 = layer2.substring(Math.floor(layer2.length / 2))
      
      // 存储到不同位置
      wx.setStorageSync(`key_frag1_${keyHandle}`, {
        data: fragment1,
        timestamp: Date.now(),
        expires: Date.now() + (24 * 60 * 60 * 1000)
      })
      
      wx.setStorageSync(`key_frag2_${keyHandle}`, {
        data: fragment2,
        timeKey: timeKey,
        timestamp: Date.now(),
        expires: Date.now() + (24 * 60 * 60 * 1000)
      })
      
    } catch (error) {
      throw new Error('密钥存储失败')
    }
  }
  
  // 获取设备特定密钥
  getDeviceSpecificKey() {
    const systemInfo = wx.getSystemInfoSync()
    const CryptoJS = require('crypto-js')
    
    return CryptoJS.SHA256(
      systemInfo.model + 
      systemInfo.system + 
      systemInfo.brand +
      wx.getAccountInfoSync().miniProgram.appId
    ).toString().substring(0, 32)
  }
  
  // 重构密钥材料(临时)
  async reconstructKeyMaterial(keyHandle) {
    const CryptoJS = require('crypto-js')
    
    try {
      // 获取分片
      const frag1 = wx.getStorageSync(`key_frag1_${keyHandle}`)
      const frag2 = wx.getStorageSync(`key_frag2_${keyHandle}`)
      
      if (!frag1 || !frag2 || frag1.expires < Date.now() || frag2.expires < Date.now()) {
        throw new Error('密钥已过期或不存在')
      }
      
      // 重组加密数据
      const layer2 = frag1.data + frag2.data
      
      // 解密第二层
      const layer1 = CryptoJS.AES.decrypt(layer2, frag2.timeKey).toString(CryptoJS.enc.Utf8)
      
      // 解密第一层
      const deviceKey = this.getDeviceSpecificKey()
      const keyMaterial = CryptoJS.AES.decrypt(layer1, deviceKey).toString(CryptoJS.enc.Utf8)
      
      return keyMaterial
    } catch (error) {
      throw new Error('密钥重构失败')
    }
  }
  
  // 派生实际加密密钥
  async deriveEncryptionKey(keyHandle) {
    const CryptoJS = require('crypto-js')
    const keyMaterial = await this.reconstructKeyMaterial(keyHandle)
    
    // 使用PBKDF2派生密钥
    const salt = CryptoJS.SHA256(keyMaterial).toString().substring(0, 16)
    const key = CryptoJS.PBKDF2(keyMaterial, salt, {
      keySize: 256/32,
      iterations: CRYPTO_PROTOCOL.iterations
    })
    
    return key.toString()
  }
  
  // 加密数据
  async encrypt(data) {
    if (!this.platformKeyHandle) {
      throw new Error('密钥未初始化')
    }
    
    const CryptoJS = require('crypto-js')
    
    try {
      // 派生加密密钥
      const encryptionKey = await this.deriveEncryptionKey(this.platformKeyHandle)
      
      // 生成随机IV
      const iv = CryptoJS.lib.WordArray.random(96/8) // 96位IV
      
      // AES-CBC模式加密(crypto-js标准支持)
      const encrypted = CryptoJS.AES.encrypt(
        JSON.stringify(data), 
        encryptionKey, 
        {
          iv: iv,
          mode: CryptoJS.mode.CBC,
          padding: CryptoJS.pad.Pkcs7
        }
      )
      
      return {
        keyId: this.keyId,
        iv: iv.toString(),
        data: encrypted.ciphertext.toString(),
        algorithm: 'AES-256-CBC', // 实际使用CBC模式
        platform: 'miniprogram'
      }
      
    } catch (error) {
      throw new Error('加密失败: ' + error.message)
    } finally {
      // 尝试触发垃圾回收清理临时密钥
      if (typeof wx.triggerGC === 'function') {
        wx.triggerGC()
      }
    }
  }
  
  // 解密数据
  async decrypt(encryptedPacket) {
    if (!this.platformKeyHandle) {
      throw new Error('密钥未初始化')
    }
    
    if (encryptedPacket.keyId !== this.keyId) {
      throw new Error('密钥ID不匹配')
    }
    
    const CryptoJS = require('crypto-js')
    
    try {
      const { iv, data } = encryptedPacket
      
      // 派生解密密钥
      const decryptionKey = await this.deriveEncryptionKey(this.platformKeyHandle)
      
      // 解密数据
      const decrypted = CryptoJS.AES.decrypt(
        data,
        decryptionKey,
        {
          iv: CryptoJS.enc.Hex.parse(iv),
          mode: CryptoJS.mode.CBC,
          padding: CryptoJS.pad.Pkcs7
        }
      )
      
      const decryptedText = decrypted.toString(CryptoJS.enc.Utf8)
      return JSON.parse(decryptedText)
      
    } catch (error) {
      throw new Error('解密失败: ' + error.message)
    } finally {
      // 尝试触发垃圾回收清理临时密钥
      if (typeof wx.triggerGC === 'function') {
        wx.triggerGC()
      }
    }
  }
  
  // 检查是否支持必要的小程序API
  static isSupported() {
    return typeof wx !== 'undefined' && 
           wx.getSystemInfoSync &&
           wx.setStorageSync &&
           wx.getStorageSync
  }
  
  // 销毁密钥和清理存储
  destroy() {
    if (this.platformKeyHandle) {
      try {
        // 清理存储的密钥片段
        wx.removeStorageSync(`key_frag1_${this.platformKeyHandle}`)
        wx.removeStorageSync(`key_frag2_${this.platformKeyHandle}`)
      } catch (error) {
        console.warn('清理本地密钥存储失败:', error)
      }
    }
    
    this.platformKeyHandle = null
    this.keyId = null
    
    // 触发垃圾回收
    if (typeof wx.triggerGC === 'function') {
      wx.triggerGC()
    }
  }
  
  // 刷新密钥(重新生成以增强安全性)
  async refreshKey() {
    // 先销毁旧密钥
    this.destroy()
    
    // 生成新密钥
    return await this.generateKey()
  }
  
  // 验证密钥是否有效
  async validateKey() {
    if (!this.platformKeyHandle || !this.keyId) {
      return false
    }
    
    try {
      // 尝试重构密钥材料来验证
      await this.reconstructKeyMaterial(this.platformKeyHandle)
      return true
    } catch (error) {
      return false
    }
  }
}