如何在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
相关产品推荐
相关产品推荐

