Error : Failed to decrypt key as session key has changed | shinyapps.io | R

微笑、不失礼 提交于 2020-03-21 06:55:43

问题


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 shinyitself, 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!