You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js REST API加密数据安全传输架构问题咨询

REST API加密数据传输的架构疑问与改进方案

我的场景与代码

数据发送端(前端)

加密逻辑:

const crypto = require('crypto')
const algorithm = 'aes-256-ctr'
const secretKey = 'somesecret'
const iv = crypto.randomBytes(16)
const encrypt = (t) => {
    const cipher = crypto.createCipheriv(algorithm, secretKey, iv)
    const encrypted = Buffer.concat([cipher.update(t), cipher.final()])
    return {
        iv: iv.toString('hex'),
        content: encrypted.toString('hex')
    }
}
const regdata = encrypt(password) // 修正原代码笔误
// 后续发送数据

API调用逻辑:

async function apicall(data, Method) {
  data = Object.assign(data, {
    apisecret: process.env.REACT_APP_APISECRET,
    cryptokey: process.env.REACT_APP_CRYPTOKEY
  })
  const mydata = await fetch(`https://somepublic.url:4400/defined/query/${JSON.stringify(data)}`, {
    method: Method
  })
  const tdata = await mydata.text()
  const jdata = JSON.parse(tdata)
  // 后续处理
  return jdata
}

API服务端(后端)

const express = require('express')
const app = express()

app.use(express.json())
app.use(function(req, res, next) {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Access-Control-Allow-Headers');
  next();
});

// 查询接口
app.get('/defined/query/:data', (req, res) => {
  try {
    const data = JSON.parse(req.params.data)
    if ((data.apisecret !== process.env.NODE_APP_APISECRET) || (data.cryptokey !== process.env.NODE_APP_CRYPTOKEY)) {
      return res.status(401).send("Authentication Required")
    }
    // 处理加密数据的逻辑
    // ...
    res.status(200).send(response);
  } catch (error) {
    res.status(500).send(error);
  }
})

我的疑问

当前代码能正常运行,但我有以下困惑:

  • 如果数据通过URL传输,加密还有意义吗?
  • 攻击者可以通过抓包工具(比如Wireshark)获取apisecret,进而获得API访问权限;
  • 传输过程中iv会被暴露,但我原本想避免iv泄露,可解密又必须用到iv,这该怎么处理?
  • 我的架构是不是存在错误,或者对API接口的理解有偏差?

问题分析与改进方案

1. 核心架构问题:URL传输敏感数据是大忌

你当前把所有敏感数据(加密内容、iv、apisecret、cryptokey)都放在URL里,这是严重的安全问题:

  • URL会被浏览器、服务器日志、代理服务器完整记录,敏感数据会被持久化泄露;
  • 即使是HTTPS,URL的路径部分虽然在传输中加密,但服务器日志、浏览器历史里还是明文存储这些数据。

必须改用POST/PUT请求,把数据放在请求体(body)里,请求体在HTTPS传输中是加密的,且不会被轻易记录到日志。

2. iv的暴露问题:AES-CTR模式下iv不需要保密

AES-CTR属于流加密模式,iv的设计就是可以公开的,它的作用是保证相同密钥加密相同明文时得到不同的密文,防止重复模式泄露信息。所以iv暴露是正常的,不需要担心,你当前每次加密用随机iv的做法是正确的。

3. apisecret和cryptokey的传输问题:前端硬编码密钥完全不安全

你在前端用process.env.REACT_APP_APISECRET这类环境变量,本质还是把密钥硬编码在前端代码里,攻击者很容易通过反编译前端代码拿到这些密钥,等于直接把API权限和加密密钥拱手相让。

正确的做法是:

  • 前端不要直接持有加密密钥和API密钥,而是先通过身份认证流程(比如账号密码登录、OAuth2)获取后端颁发的短期令牌(比如JWT);
  • 后续请求用这个令牌做身份验证,后端验证令牌合法性后再处理请求;
  • 加密操作如果需要在前后端进行,应该用会话密钥:比如前端登录后,后端生成一个临时的对称密钥,通过HTTPS安全传输给前端,或者用非对称加密(前端生成公钥,后端用公钥加密会话密钥后传给前端),避免长期密钥暴露。

4. 加密的意义:HTTPS不是万能的

HTTPS只是保证传输过程中数据不被窃听篡改,而如果你的服务器被入侵,或者日志泄露,明文数据还是会有风险。额外加密敏感数据(比如用户密码、银行卡号)可以做到端到端加密,只有前端和后端能解密,中间环节(包括服务器日志)拿到的都是密文,进一步提升安全性。

修正后的架构示例

前端(发送端)

// 先登录获取JWT令牌和会话密钥
async function login(username, password) {
  const res = await fetch('https://somepublic.url:4400/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  })
  const { token, sessionKey } = await res.json()
  return { token, sessionKey }
}

// 加密并发送敏感数据
async function sendSensitiveData(password, token, sessionKey) {
  const iv = crypto.randomBytes(16)
  const cipher = crypto.createCipheriv('aes-256-ctr', Buffer.from(sessionKey, 'hex'), iv)
  const encryptedPassword = Buffer.concat([cipher.update(password), cipher.final()])
  
  const res = await fetch('https://somepublic.url:4400/defined/query', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}` // 用JWT做身份验证
    },
    body: JSON.stringify({
      iv: iv.toString('hex'),
      content: encryptedPassword.toString('hex')
    })
  })
  
  return res.json()
}

后端(服务端)

const express = require('express')
const jwt = require('jsonwebtoken')
const crypto = require('crypto')
const app = express()

app.use(express.json())
// CORS设置新增Authorization头支持
app.use(function(req, res, next) {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});

// 登录接口:验证账号密码,颁发JWT和会话密钥
app.post('/login', (req, res) => {
  const { username, password } = req.body
  // 替换为实际的用户验证逻辑
  if (username === 'validUser' && password === 'validPass') {
    const token = jwt.sign({ userId: 123 }, process.env.JWT_SECRET, { expiresIn: '1h' })
    const sessionKey = crypto.randomBytes(32).toString('hex') // 生成256位会话密钥
    res.json({ token, sessionKey })
  } else {
    res.status(401).send('Invalid credentials')
  }
})

// 验证JWT的中间件
const authenticateToken = (req, res, next) => {
  const authHeader = req.headers['authorization']
  const token = authHeader && authHeader.split(' ')[1]
  
  if (!token) return res.status(401).send('Token required')
  
  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) return res.status(403).send('Invalid token')
    req.user = user
    next()
  })
}

// 处理敏感数据的接口
app.post('/defined/query', authenticateToken, (req, res) => {
  try {
    const { iv, content } = req.body
    // 实际场景中应从会话存储(如Redis)中获取对应用户的sessionKey
    const sessionKey = process.env.SESSION_KEY 
    const decipher = crypto.createDecipheriv('aes-256-ctr', Buffer.from(sessionKey, 'hex'), Buffer.from(iv, 'hex'))
    const decrypted = Buffer.concat([decipher.update(Buffer.from(content, 'hex')), decipher.final()])
    
    // 处理解密后的数据
    res.status(200).send({ decryptedData: decrypted.toString() })
  } catch (error) {
    res.status(500).send(error.message)
  }
})

内容的提问来源于stack exchange,提问作者Ralf Steffenfried

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 05:15:37