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

如何在Shiny应用中实现敏感数据的加密与解密

问题描述

作为数据加密解密领域新手,我在SQL表中存储人员信息,其中某列包含敏感银行数据。该表通过Shiny应用向授权用户展示,用户需通过点击「添加」或「编辑」按钮,以个案形式增改、查看该敏感列。

已完成Shiny应用开发及SQL读写功能,现需实现:

  • 用户输入银行信息时,将加密后的值写入SQL表存储
  • 用户在Shiny应用中查看记录时,展示解密后的明文
  • 源数据必须以加密值存储,而非仅在前端隐藏
现有模拟代码
library(dplyr)
library(shiny)
library(DT)

ui <- fluidPage(
  # 展示所有记录的响应式表格
  DT::DTOutput("allrecords"),
  
  # 添加新记录按钮
  actionButton("add_record_bttn", "Add Record", class = "btn-primary"),
  hr(),
  
  # 新增/编辑记录的表单
  uiOutput("name"),
  uiOutput("bank"),
  uiOutput("save")
  
)

server <- function(input, output, session){
  reactive_val <- 
    reactiveValues(
      df_all_records = NULL
    )
  
  
  # 模拟SQL表数据(实际场景为SQL查询)
  name_col <- 
    c("Bob", "Jane", "Charlie")
  bank_col <- 
    c("ABC", "EDG", "HIJ")
  all_records <- data.frame(name_col, bank_col)
  reactive_val$df_all_records <- 
    all_records
  
  # 渲染表格
  output$allrecords <- 
    DT::renderDT(
    datatable(reactive_val$df_all_records, selection = 'single', rownames = FALSE)
  )
  
  # 点击添加按钮显示表单
  observeEvent(input$add_record_bttn, {
    
    output$name <- 
      renderUI(textInput("nameGiven", "Enter Name:"))
    output$bank <- 
      renderUI(textInput("bankGiven", "Enter Bank:"))
    output$save <- 
      renderUI(actionButton("save", "Save Record", class = "btn-success"))
  })
  
  # 新增记录(实际场景为写入SQL表)
  observeEvent(input$save, {
    
    reactive_val$df_all_records[nrow(reactive_val$df_all_records) + 1,] <- c(input$nameGiven, input$bankGiven)
    print(reactive_val$df_all_records)
  })
  
  # 点击现有记录显示编辑表单
  observeEvent(input$allrecords_rows_selected, {
    print(input$allrecords_rows_selected)
    output$name <- 
      renderUI(textInput("nameGiven", "Enter Name:", value = reactive_val$df_all_records[input$allrecords_rows_selected, "name_col"]))
    output$bank <- 
      renderUI(textInput("bankGiven", "Enter Bank:", value = reactive_val$df_all_records[input$allrecords_rows_selected, "bank_col"]))
    output$save <- 
      renderUI(actionButton("save", "Save As New Record", class = "btn-success"))
  })
}

shinyApp(ui = ui, server = server)
效果对比
  • 当前表格展示效果:
    当前表格
  • 期望表格展示效果:
    期望表格
  • 用户添加/点击记录时的期望效果:
    操作时展示效果
解决方案

使用openssl包实现AES对称加密,以下是整合加密解密逻辑后的完整代码:

1. 安装并加载依赖包

install.packages("openssl")
library(openssl)
library(dplyr)
library(shiny)
library(DT)

2. 加密解密核心函数

# AES加密函数:明文转加密字符串
encrypt_text <- function(text, key) {
  if (is.na(text) || text == "") return(NA)
  iv <- rand_bytes(16) # 生成随机初始化向量
  cipher <- aes_cbc_encrypt(charToRaw(text), key, iv)
  rawToChar(c(iv, cipher)) # 拼接IV与密文,方便解密提取
}

# AES解密函数:加密字符串转明文
decrypt_text <- function(encrypted_text, key) {
  if (is.na(encrypted_text) || encrypted_text == "") return(NA)
  raw_data <- charToRaw(encrypted_text)
  iv <- raw_data[1:16] # 提取前16位作为初始化向量
  cipher <- raw_data[17:length(raw_data)] # 剩余部分为密文
  rawToChar(aes_cbc_decrypt(cipher, key, iv))
}

3. 修改后的Shiny应用代码

ui <- fluidPage(
  DT::DTOutput("allrecords"),
  actionButton("add_record_bttn", "Add Record", class = "btn-primary"),
  hr(),
  uiOutput("name"),
  uiOutput("bank"),
  uiOutput("save")
)

server <- function(input, output, session){
  reactive_val <- reactiveValues(
    df_all_records = NULL,
    # 实际生产环境请从安全渠道读取密钥(如环境变量、密钥管理服务),禁止硬编码
    encryption_key = rand_bytes(32) 
  )
  
  # 初始化模拟SQL数据(存储加密后的银行信息)
  name_col <- c("Bob", "Jane", "Charlie")
  bank_col_plain <- c("ABC", "EDG", "HIJ")
  bank_col_encrypted <- sapply(bank_col_plain, encrypt_text, key = reactive_val$encryption_key)
  all_records <- data.frame(name_col, bank_col = bank_col_encrypted)
  reactive_val$df_all_records <- all_records
  
  # 渲染表格:解密银行列后展示明文
  output$allrecords <- DT::renderDT({
    display_df <- reactive_val$df_all_records
    display_df$bank_col <- sapply(display_df$bank_col, decrypt_text, key = reactive_val$encryption_key)
    datatable(display_df, selection = 'single', rownames = FALSE,
              colnames = c("Name", "Bank")) # 匹配期望的列名
  })
  
  # 点击添加按钮显示空表单
  observeEvent(input$add_record_bttn, {
    output$name <- renderUI(textInput("nameGiven", "Enter Name:"))
    output$bank <- renderUI(textInput("bankGiven", "Enter Bank:"))
    output$save <- renderUI(actionButton("save", "Save Record", class = "btn-success"))
  })
  
  # 保存新记录:加密银行数据后存储
  observeEvent(input$save, {
    encrypted_bank <- encrypt_text(input$bankGiven, key = reactive_val$encryption_key)
    new_row <- data.frame(name_col = input$nameGiven, bank_col = encrypted_bank)
    reactive_val$df_all_records <- rbind(reactive_val$df_all_records, new_row)
    # 实际场景中此处为写入SQL表,存储encrypted_bank而非明文
    print(reactive_val$df_all_records)
  })
  
  # 点击现有记录显示编辑表单:解密后填充明文
  observeEvent(input$allrecords_rows_selected, {
    selected_row <- reactive_val$df_all_records[input$allrecords_rows_selected, ]
    decrypted_bank <- decrypt_text(selected_row$bank_col, key = reactive_val$encryption_key)
    output$name <- renderUI(textInput("nameGiven", "Enter Name:", value = selected_row$name_col))
    output$bank <- renderUI(textInput("bankGiven", "Enter Bank:", value = decrypted_bank))
    output$save <- renderUI(actionButton("save", "Save As New Record", class = "btn-success"))
  })
}

shinyApp(ui = ui, server = server)

关键注意事项

  • 密钥安全:绝对禁止将密钥硬编码在代码中,生产环境建议使用环境变量(Sys.getenv("ENCRYPTION_KEY"))或专业密钥管理服务(如AWS KMS)。
  • 数据持久化:写入SQL时必须存储加密后的字符串,而非明文。
  • 权限控制:确保Shiny应用仅对授权用户开放,避免未授权的解密操作。
  • 初始化向量(IV):每次加密生成独立IV,避免相同明文产生相同密文,提升安全性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 17:55:22