问题
Note: This (lengthy) question is a follow-up to my previous post.
I would like to achieve the encryption of data locally (local RStudio) and decrypt the encrypted data remotely (application hosted on shinyapps.io).
The first part of the code intrinsically encrypts a data-frame using a key
. The second part of the code is a shiny application that decrypts the data-frame using the same key
and thereby using this data-frame for authentication purposes within the application. The code works just alright on my machine.
However, it throws an error when published to shinyapps.io (cloud-based hosting service) as shown below:
1.Code for encrypting the data-frame.
library(sodium)
#> Warning: package 'sodium' was built under R version 3.5.3
library(encryptr)
#> Warning: package 'encryptr' was built under R version 3.5.3
library(cyphr)
#> Warning: package 'cyphr' was built under R version 3.5.3
#>
#> Attaching package: 'cyphr'
#> The following objects are masked from 'package:encryptr':
#>
#> decrypt, decrypt_file, encrypt, encrypt_file
#setting local working directory
#setwd("D://Work/03Mar20/")
df = data.frame(
user = c("user1", "user2", "user3", "user4", "user5"),
password = c("pass1", "pass2", "pass3", "pass4", "pass5"),
permissions = c("admin","admin","admin","admin","admin"),
name = c("user one", "user two", "user three", "user four", "user five"),
stringsAsFactors = FALSE
)
#generating a key and encrypting the desired dataframe using cyphr and sodium packages
key <- cyphr::key_sodium(sodium::keygen())
cyphr::encrypt(saveRDS(df, "auth_base.rds"), key)
#saving the key as a .rds file and removing from R environment
saveRDS(key, "key.rds")
rm(key)
Created on 2020-03-06 by the reprex package (v0.3.0)
2.Code for shiny application (decrypting the data-frame and authorizing users).
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyauthr)
library(shinyjs)
library(sodium)
library(encryptr)
library(cyphr)
library(glue)
library(knitr)
library(rsconnect)
library(ggplot2)
library(DT)
#setting local working directory
#setwd("D://Work Related/03Mar20")
key <- readRDS("key.rds")
df = cyphr::decrypt(readRDS("auth_base.rds"), key)
#Dataframe that holds usernames, passwords and other user data
credentials = data.frame(
username = df$user,
password = sapply(df$password, sodium::password_store),
permission = df$permissions,
name = df$name,
stringsAsFactors = FALSE
)
# Main login screen
loginpage <- div(id = "loginpage", style = "width: 500px; max-width: 100%; margin: 0 auto; padding: 20px;",
wellPanel(
tags$h2("LOG IN", class = "text-center", style = "padding-top: 0;color:#333; font-weight:600;"),
textInput("userName", placeholder="Username", label = tagList(icon("user"), "Username")),
passwordInput("passwd", placeholder="Password", label = tagList(icon("unlock-alt"), "Password")),
br(),
div(
style = "text-align: center;",
actionButton("login", "SIGN IN", style = "color: white; background-color:#3c8dbc;
padding: 10px 15px; width: 150px; cursor: pointer;
font-size: 18px; font-weight: 600;"),
shinyjs::hidden(
div(id = "nomatch",
tags$p("Incorrect username or password!",
style = "color: red; font-weight: 600;
padding-top: 5px;font-size:16px;",
class = "text-center"))),
br()
))
)
header <- dashboardHeader( title = "Template", uiOutput("logoutbtn"))
sidebar <- dashboardSidebar(collapsed = FALSE, uiOutput("sidebarpanel"))
body <- dashboardBody(shinyjs::useShinyjs(), uiOutput("body"))
ui<-dashboardPage(header, sidebar, body, skin = "blue")
server <- function(input, output, session) {
login = FALSE
USER <- reactiveValues(login = login)
observe({
if (USER$login == FALSE) {
if (!is.null(input$login)) {
if (input$login > 0) {
Username <- isolate(input$userName)
Password <- isolate(input$passwd)
if(length(which(credentials$username==Username))==1) {
pasmatch <- credentials["password"][which(credentials$username==Username),]
pasverify <- password_verify(pasmatch, Password)
if(pasverify) {
USER$login <- TRUE
} else {
shinyjs::toggle(id = "nomatch", anim = TRUE, time = 1, animType = "fade")
shinyjs::delay(3000, shinyjs::toggle(id = "nomatch", anim = TRUE, time = 1, animType = "fade"))
}
} else {
shinyjs::toggle(id = "nomatch", anim = TRUE, time = 1, animType = "fade")
shinyjs::delay(3000, shinyjs::toggle(id = "nomatch", anim = TRUE, time = 1, animType = "fade"))
}
}
}
}
})
output$logoutbtn <- renderUI({
req(USER$login)
tags$li(a(icon("fa fa-sign-out"), "Logout",
href="javascript:window.location.reload(true)"),
class = "dropdown",
style = "background-color: #eee !important; border: 0;
font-weight: bold; margin:5px; padding: 10px;")
})
output$sidebarpanel <- renderUI({
if (USER$login == TRUE ){
if (credentials[,"permission"][which(credentials$username==input$userName)]=="admin") {
sidebarMenu(
div(textOutput("permission"), style = "padding: 20px"),
menuItem("Data", tabName = "dashboard", icon = icon("table"))
)
}
}
})
output$body <- renderUI({
if (USER$login == TRUE ) {
if (credentials[,"permission"][which(credentials$username==input$userName)]=="admin") {
tabItems(
tabItem(
tabName ="dashboard", class = "active",
fluidRow(
box(width = 12, dataTableOutput('results'))
))
)
}
}
else {
loginpage
}
})
output$permission <- renderText({
if (USER$login == TRUE ) {
paste("Permission: ", credentials[,"permission"][which(credentials$username==input$userName)])
}
})
output$results <- DT::renderDataTable({
datatable(mtcars, options = list(autoWidth = TRUE,
searching = FALSE))
})
}
shinyApp(ui, server)
I learn from the error that the key
's session ID at the time of encryption isn't matching when I publish it to the cloud for decryption. As a rookie in the security domain, is there's any workaround to achieve the decryption on the cloud?
Any suggestions are much appreciated.
回答1:
Reason for the issue
The problem is by design as cyphr::key_sodium
creates a key which is valid, well, only for the current session. That is, it is not possible to share it across different session let alone different systems.
Hence, the problem is not at all related to shiny
itself, but to the fact that you are trying to use cyphr
keys across different sessions.
From the vignette:
When using key_openssl, keypair_openssl, key_sodium, or keypair_sodium we generate something that can decrypt data. The objects that are returned by these functions can encrypt and decrypt data and so it is reasonable to be concerned that if these objects were themselves saved to disk your data would be compromised.
To avoid this, cyphr does not store private or symmetric keys directly in these objects but instead encrypts the sensitive keys with a cyphr-specific session key that is regenerated each time the package is loaded. This means that the objects are practically only useful within one session, and if saved with save.image (perhaps automatically at the end of a session) the keys cannot be used to decrypt data.
Reprex of the issue
library(cyphr)
file <- "encr.rds"
df <- data.frame(a = 1)
## cyphr workflow won't work across sessions / systems
key <- key_sodium(sodium::keygen())
encrypt(saveRDS(df, file), key)
## works within the same session
decrypt(readRDS(file), key)
## simulate session change (restart or other system)
session_key_refresh()
## won't work
decrypt(readRDS(file), key)
unlink(file)
Solution
Note. Updated the code as it is not needed to save and store the nonce
.
Thus, you need to use a different library for doing the work. You can use for example the library sodium
itself:
library(sodium)
key_file <- "key.rds"
file <- "encr.rds"
key <- keygen()
df <- data.frame(a = 1)
msg <- serialize(df, NULL)
cipher <- data_encrypt(msg, key)
saveRDS(cipher, file)
## store key
saveRDS(key, key_file)
You can share now key.rds
(or put it to your shiny server). To simulate that simply restart your R and run:
library(sodium)
key_file <- "key.rds"
file <- "encr.rds"
key <- readRDS(key_file)
# Decrypt with same stored key and nonce
decipher <- readRDS(file)
unserialize(data_decrypt(decipher, key))
# a
# 1 1
unlink(key_file)
unlink(file)
Security Concerns
Using a symmetric encryption (that is one key for de-/encrypting, like in your example) and storing the key on top on the server does not sound like a good idea. Anybody getting his hands on your key file will be able to decrypt your secrets.
I am not a security expert myself, but I would re-consider your design.
来源:https://stackoverflow.com/questions/60561129/error-failed-to-decrypt-key-as-session-key-has-changed-shinyapps-io-r