Introduction

This package tries to smooth over some of the differences in encryption approaches (symmetric vs. asymmetric, sodium vs. openssl) to provide a simple interface for users who just want to encrypt or decrypt things.

The scope of the package is to protect data that has been saved to disk. It is not designed to stop an attacker targeting the R process itself to determine the contents of sensitive data. The package does try to prevent you accidentally saving to disk the contents of sensitive information, including the keys that could decrypt such information.

This vignette works through the basic functionality of the package. It does not offer much in the way of an introduction to encryption itself; for that see the excellent vignettes in the openssl and sodium packages (see vignette("crypto101") and vignette("bignum") for information about how encryption works). This package is a wrapper around those packages in order to make them more accessible.

Keys and the like

To encrypt anything we need a key. There are two sorts of key “types” we will concern ourselves with here “symmetric” and “asymmetric”.

  • “symmetric” keys are used for storing secrets that multiple people need to access. Everyone has the same key (which is just a bunch of bytes) and with that we can either encrypt data or decrypt it.

  • a “key pair” is a public and a private key; this is used in communication. You hold a private key that nobody else ever sees and a public key that you can copy around all over the show. These can be used for a couple of different patterns of communication (see below).

We support symmetric keys and asymmetric key pairs from the openssl and sodium packages (which wrap around industry-standard cryptographic libraries) - this vignette will show how to create and load keys of different types as they’re used.

The openssl keys have the advantage of a standard key format, and that many people (especially on Linux and macOS) have a keypair already (see below if you’re not sure if you do). The sodium keys have the advantage of being a new library, starting from a clean slate rather than carrying with it accumulated ideas from the last 20 years of development.

The idea in cyphr is that we can abstract away some differences in the types of keys and the functions that go with them to create a standardised interface to encrypting and decrypting strings, R objects, files and raw vectors. With that, we can then create wrappers around functions that create files and simplify the process of adding encryption into a data workflow.

Below, I’ll describe the sorts of keys that cyphr supports and in the sections following describe how these can be used to actually do some encryption.

Symmetric encryption

Illustration of Symmetric Encryption
Illustration of Symmetric Encryption

This is the simplest form of encryption because everyone has the same key (like a key to your house or a single password). This raises issues (like how do you store the key without other people reading it) but we can deal with that below.

openssl

To generate a key with openssl, you can use:

k <- openssl::aes_keygen()

which generates a raw vector

k
## aes 4f:9b:01:6f:7c:df:86:dc:de:38:37:17:12:c3:cf:ec

(this prints nicely but it really is stored as a 16 byte raw vector).

The encryption functions that this key supports are openssl::aes_cbc_encrypt, openssl::aes_ctr_encrypt and openssl::aes_gcm_encrypt (along with the corresponding decryption functions). The cyphr package tries to abstract this away by using a wrapper `cyphr::key_openssl

key <- cyphr::key_openssl(k)
key
## <cyphr_key: openssl>

With this key, one can encrypt a string with cyphr::encrypt_string:

secret <- cyphr::encrypt_string("my secret string", key)

and decrypt it again with cyphr::decrypt_string:

cyphr::decrypt_string(secret, key)
## [1] "my secret string"

See below for more functions that use these key objects.

sodium

The interface is almost identical using sodium symmetric keys. To generate a symmetric key with libsodium you would use sodium::keygen

k <- sodium::keygen()

This is really just a raw vector of length 32, without even any class attribute!

The encryption functions that this key supports are sodium::data_encrypt and sodium::data_decrypt. To create a key for use with cyphr that knows this, use:

key <- cyphr::key_sodium(k)
key
## <cyphr_key: sodium>

This key can then be used with the high-level cyphr encryption functions described below.

Asymmetric encryption (“key pairs”)

Illustration of Asymmetric Encryption
Illustration of Asymmetric Encryption

With asymmetric encryption everybody has two keys that differ from everyone else’s key. One key is public and can be shared freely with anyone you would like to communicate with and the other is private and must never be disclosed.

In the sodium package there is a vignette (vignette("crypto101")) that gives a gentle introduction to how this all works. In practice, you end up creating a pair of keys for yourself. Then to encrypt or decrypt something you encrypt messages with the recipient’s public key and they (and only they) can decrypt it with their private key.

One use for asymmetric encryption is to encrypt a shared secret (such as a symmetric key) - with this you can then safely store or communicate a symmetric key without disclosing it.

openssl

Let’s suppose that we have two parties “Alice” and “Bob” who want to talk with one another. For demonstration purposes we need to generate SSH keys (with no password) in temporary directories (to comply with CRAN policies). In a real situation these would be on different machines (Alice has no access to Bob’s key!) and these keys would be password protected.

path_key_alice <- cyphr::ssh_keygen(password = FALSE)
path_key_bob <- cyphr::ssh_keygen(password = FALSE)

Note that each directory contains a public key (id_rsa.pub) and a private key (id_rsa).

dir(path_key_alice)
## [1] "id_rsa"     "id_rsa.pub"
dir(path_key_bob)
## [1] "id_rsa"     "id_rsa.pub"

Below, the full path to the key (e.g., .../id_rsa) could be used in place of the directory name if you prefer.

If Alice wants to send a message to Bob she needs to use her private key and his public key

pair_a <- cyphr::keypair_openssl(path_key_bob, path_key_alice)
pair_a
## <cyphr_keypair: openssl>

with this pair she can write a message to “bob”:

secret <- cyphr::encrypt_string("secret message", pair_a)

The secret is now just a big pile of bytes

secret
##   [1] 58 0a 00 00 00 03 00 04 04 01 00 03 05 00 00 00 00 05 55 54 46 2d 38 00 00
##  [26] 02 13 00 00 00 04 00 00 00 18 00 00 00 10 03 6c 3c c9 40 fd 15 4a d8 ec b8
##  [51] fc 32 cd 08 eb 00 00 00 18 00 00 01 00 33 31 ae c5 6e 52 d8 1c 76 71 bb af
##  [76] f3 05 4e 0a f4 2c db 1b c3 7d ca 01 7f 56 7c 74 6d 59 e5 83 b1 ac 3c f0 28
## [101] d5 54 68 ba d8 77 16 fb cb 79 55 5a e2 84 0b bb ce b2 d0 b9 d0 3c 58 ba af
## [126] bc 5a 06 88 28 fe 87 46 3e 19 a9 40 ce c0 93 a9 b5 7e ca 57 c8 ae dc 09 8c
## [151] 22 88 e0 93 62 7c 1e 56 15 3b f1 d7 3c fa 7b ee 6c 5a 5c 33 51 aa 24 fa db
## [176] 20 d5 29 ca f2 8c 2f a1 da 57 b2 31 3f 82 b9 c7 c8 22 cd 86 63 40 f1 22 d0
## [201] e0 35 f2 52 f3 88 5c 91 49 2b fe 3e 52 a9 fe c3 43 1f 0a b7 eb da 95 38 ca
## [226] 1a 81 11 d8 2e 81 f7 17 b5 09 c3 03 78 06 7b 0c a6 c5 5d ac 5c f8 85 e5 d7
## [251] 6d d5 f5 20 a3 4b ed 59 0e 78 f1 73 87 00 61 ff b3 73 93 a3 c0 8c 01 9c d1
## [276] d4 ee e9 d4 19 d5 33 73 60 bf 76 94 33 f3 f8 56 8f 9f b5 cd fd 51 2c 02 13
## [301] 96 3e ff 42 7a 48 48 11 ba 49 3a 0e a3 59 a0 bd 25 9f d9 00 00 00 18 00 00
## [326] 00 10 a7 d8 32 05 26 13 1d 34 82 6a be 9e e9 be c2 14 00 00 00 18 00 00 01
## [351] 00 2f 61 31 8d eb 22 88 13 09 fd ba d3 90 86 c3 cf aa 39 95 d7 49 7d c5 f4
## [376] 7c f7 28 ae 16 73 b7 dc 51 68 10 f6 54 10 47 b7 32 1b 50 cc 9b d5 86 7a ae
## [401] 51 9a 9b ce 24 ea 46 3d f9 e2 31 73 d3 ac e0 10 85 f5 d1 3f 59 ff d4 47 c1
## [426] b7 61 0d 64 bf b9 02 99 95 0d 5c 0e 67 f1 ff 0f f3 af d0 6f fd 88 1b d8 c1
## [451] 14 b8 2c 06 be 14 11 46 3f 5e 6e 9f 72 8e a4 fc 96 fa cb bf 90 05 e9 69 fb
## [476] 0b 76 f6 c3 0a fe fc 86 2d 90 24 34 5d 60 cc 76 cd 18 8f ee 66 38 d7 f7 0a
## [501] 7e ea df 88 46 a8 cb 8a 29 3f 9f 83 74 37 35 c7 69 29 7b 34 92 15 aa 97 e5
## [526] 2c c6 ed f4 46 b0 7f 9c 0d 41 8a c5 56 b3 b4 60 f0 f8 5e 59 8f 50 b0 0d 61
## [551] 01 11 e2 fb d5 61 02 d7 ae e5 4b 75 74 d8 46 96 d2 2b bd ab 0f 02 99 df f6
## [576] fc 2b 2f d3 f4 af 29 16 85 c9 0e 9b f7 a0 22 80 11 71 d3 2f e2 24 43 14 e8
## [601] 4f 5a 01 a2 92 70 f0 00 00 04 02 00 00 00 01 00 04 00 09 00 00 00 05 6e 61
## [626] 6d 65 73 00 00 00 10 00 00 00 04 00 04 00 09 00 00 00 02 69 76 00 04 00 09
## [651] 00 00 00 07 73 65 73 73 69 6f 6e 00 04 00 09 00 00 00 04 64 61 74 61 00 04
## [676] 00 09 00 00 00 09 73 69 67 6e 61 74 75 72 65 00 00 00 fe

Note that unlike symmetric encryption above, Alice cannot decrypt her own message:

cyphr::decrypt_string(secret, pair_a)
## Error in openssl::decrypt_envelope(x$data, x$iv, x$session, key): OpenSSL error: 00E851861E7F0000:error:03000082:digital envelope routines:EVP_CIPHER_CTX_set_key_length:invalid key length:../crypto/evp/evp_enc.c:1046:

For Bob to read the message, he uses his private key and Alice’s public key (which she has transmitted to him previously).

pair_b <- cyphr::keypair_openssl(path_key_alice, path_key_bob)

With this keypair, Bob can decrypt Alice’s message

cyphr::decrypt_string(secret, pair_b)
## [1] "secret message"

And send one back of his own:

secret2 <- cyphr::encrypt_string("another message", pair_b)
secret2
##   [1] 58 0a 00 00 00 03 00 04 04 01 00 03 05 00 00 00 00 05 55 54 46 2d 38 00 00
##  [26] 02 13 00 00 00 04 00 00 00 18 00 00 00 10 a2 ce 73 54 c0 9b 31 2d 5c 4f 04
##  [51] b6 ae 1e 2d 51 00 00 00 18 00 00 01 00 5b 42 9a b7 b2 b1 a1 0d 88 fc 46 6d
##  [76] ad ec 2b ac 26 3c 9f 9f 17 49 dd a7 37 d6 77 38 b1 0f 26 39 84 b5 9f 40 62
## [101] 68 a7 81 93 dc a5 f0 3b 58 3b 96 96 61 40 75 c8 84 c2 6a 11 58 1f c9 7c 17
## [126] e9 91 56 ab a7 19 a0 ce b6 81 c1 c4 3e bd 12 bc 04 48 b5 ee f1 f9 35 1f 95
## [151] 73 7b ad 77 e5 c4 02 72 b8 c6 75 2a 99 05 2d f0 f8 8c 2a fd fe 5b 37 cf 46
## [176] d2 05 41 e5 5d dd 2f 51 1a 76 76 94 fd b9 15 e7 f9 cd 03 d0 34 6a 93 9d 15
## [201] 82 4e 1b 5e b9 36 29 6b 89 14 21 5b 12 be 7c 05 ea 64 19 5b 0a e1 28 14 30
## [226] d0 f1 22 6f da cb 13 e0 e1 4e d8 85 3f 91 73 bb 5e 73 2e dd 6a db db fa cd
## [251] 76 bc 11 d0 32 0a a3 75 51 d4 9f b8 3f b0 98 ac 09 b7 0a 95 68 1a f4 22 b6
## [276] c7 d6 eb b8 d4 cd c7 6e 0f 15 92 90 01 b3 77 df 4d fb ab 00 c6 86 cb af b4
## [301] f2 24 5e 15 0f 53 b1 f1 da 4c 35 fb ea c2 20 d6 18 2d 41 00 00 00 18 00 00
## [326] 00 10 3b 80 62 c3 b4 4b be f8 84 f7 9c 18 04 96 3e 33 00 00 00 18 00 00 01
## [351] 00 50 89 8b 0a ca 64 9b 46 87 54 27 e3 16 13 4e 00 a4 6f e1 03 cd 7f 68 f2
## [376] 6c 7c a4 84 67 02 8c de 0a 5e 5c e3 fb 57 e8 81 9d b7 ab 83 7f 47 2b 3c 01
## [401] a1 cd c3 2c 81 02 c6 d1 66 e5 7b 96 9e b2 f0 81 b7 58 8b a4 3e 01 a7 5b a4
## [426] c2 e9 a1 ba 1f 4b ca 7e f5 73 02 f0 42 a0 e0 6e 16 29 57 29 08 eb 59 8e 0b
## [451] dd 38 cf 78 2e d6 ab 2c ad a7 da 45 1a 3e ab b7 9b 3a 19 10 67 30 35 58 e9
## [476] bb 2c 9a d5 1a 1c e1 b4 68 9d 8d a3 70 41 31 f3 d4 9e 16 10 15 6c c1 46 8b
## [501] 65 aa aa 08 02 29 91 59 b3 df b4 63 02 6c cd e7 82 65 04 b7 36 83 5d 51 59
## [526] b8 d6 19 dd 94 5d fe 58 36 59 70 7f a1 97 6e 8d 3c 01 5a 80 87 d6 b2 d3 7e
## [551] 0c e2 1b 9e 0b 42 76 3a e9 ce 1d c8 37 19 8a 1e c5 74 4a 5d e4 71 6a 54 ef
## [576] 06 c9 2e 62 14 f8 e8 2d 81 34 7a d7 f8 cf 9b b0 10 9a c1 f5 31 a2 d3 f3 db
## [601] 7d 84 45 27 b3 7b 16 00 00 04 02 00 00 00 01 00 04 00 09 00 00 00 05 6e 61
## [626] 6d 65 73 00 00 00 10 00 00 00 04 00 04 00 09 00 00 00 02 69 76 00 04 00 09
## [651] 00 00 00 07 73 65 73 73 69 6f 6e 00 04 00 09 00 00 00 04 64 61 74 61 00 04
## [676] 00 09 00 00 00 09 73 69 67 6e 61 74 75 72 65 00 00 00 fe

which she can decrypt

cyphr::decrypt_string(secret2, pair_a)
## [1] "another message"

Chances are, you have an openssl keypair in your .ssh/ directory. If so, you would pass NULL as the path for the private (or less usefully, the public) key pair part. So to send a message to Bob, we’d include the path to Bob’s public key.

pair_us <- cyphr::keypair_openssl(path_key_bob, NULL)

This all skips over how Alice and Bob will exchange this secret information. Because the secret is bytes, it’s a bit odd to work with. Alice could save the secret to disk with

secret <- cyphr::encrypt_string("secret message", pair_a)
path_for_bob <- file.path(tempdir(), "for_bob_only")
writeBin(secret, path_for_bob)

And then send Bob the file for_bob_only (over email or any other insecure medium).

and bob could read the secret in with:

secret <- readBin(path_for_bob, raw(), file.size(path_for_bob))
cyphr::decrypt_string(secret, pair_b)
## [1] "secret message"

As an alternative, you can “base64 encode” the bytes into something that you can just email around:

secret_base64 <- openssl::base64_encode(secret)
secret_base64
## [1] "WAoAAAADAAQEAQADBQAAAAAFVVRGLTgAAAITAAAABAAAABgAAAAQlSc6c4pZ6/fvfh73wstfEQAAABgAAAEALBL4kuScS1+Am2H1hfz9ikmkmE1Ac4foH4LWAsQalreP4MY+DVb7/vUdPKYc1A1oLC9uwOMJmrzmrq4i4Sg2FavDHv1EQyX3llI47zVd2vPM40zxeX75AOJUuJorEvWFdyg1dHrUvNAJn1ryynTLqz6TbVEsZofH876yKgb8npuFCUIztNMMv+3OASW96IH222DJYci7wh6KwbTS3x0TekSTDk/ZvWom4qlx4U+XFB4p1HovpwWaCgPYo2Ab1SkQaH3V8xLQMEBMJTj0Yuiv5YgDOEyI+bvtwJ1oXaA5A9FzIM+fyf10KNFG0313bwuxZ+1XRV9oz80heYLOopcB5gAAABgAAAAQvanLTBTp9Ohg8aR4F32JMwAAABgAAAEAL2ExjesiiBMJ/brTkIbDz6o5lddJfcX0fPcorhZzt9xRaBD2VBBHtzIbUMyb1YZ6rlGam84k6kY9+eIxc9Os4BCF9dE/Wf/UR8G3YQ1kv7kCmZUNXA5n8f8P86/Qb/2IG9jBFLgsBr4UEUY/Xm6fco6k/Jb6y7+QBelp+wt29sMK/vyGLZAkNF1gzHbNGI/uZjjX9wp+6t+IRqjLiik/n4N0NzXHaSl7NJIVqpflLMbt9Eawf5wNQYrFVrO0YPD4XlmPULANYQER4vvVYQLXruVLdXTYRpbSK72rDwKZ3/b8Ky/T9K8pFoXJDpv3oCKAEXHTL+IkQxToT1oBopJw8AAABAIAAAABAAQACQAAAAVuYW1lcwAAABAAAAAEAAQACQAAAAJpdgAEAAkAAAAHc2Vzc2lvbgAEAAkAAAAEZGF0YQAEAAkAAAAJc2lnbmF0dXJlAAAA/g=="

This can be converted back with openssl::base64_decode:

identical(openssl::base64_decode(secret_base64), secret)
## [1] TRUE

Or, less compactly but also suitable for email, you might just convert the bytes into their hex representation:

secret_hex <- sodium::bin2hex(secret)
secret_hex
## [1] "580a000000030004040100030500000000055554462d380000021300000004000000180000001095273a738a59ebf7ef7e1ef7c2cb5f1100000018000001002c12f892e49c4b5f809b61f585fcfd8a49a4984d407387e81f82d602c41a96b78fe0c63e0d56fbfef51d3ca61cd40d682c2f6ec0e3099abce6aeae22e1283615abc31efd444325f7965238ef355ddaf3cce34cf1797ef900e254b89a2b12f585772835747ad4bcd0099f5af2ca74cbab3e936d512c6687c7f3beb22a06fc9e9b85094233b4d30cbfedce0125bde881f6db60c961c8bbc21e8ac1b4d2df1d137a44930e4fd9bd6a26e2a971e14f97141e29d47a2fa7059a0a03d8a3601bd52910687dd5f312d030404c2538f462e8afe58803384c88f9bbedc09d685da03903d17320cf9fc9fd7428d146d37d776f0bb167ed57455f68cfcd217982cea29701e60000001800000010bda9cb4c14e9f4e860f1a478177d893300000018000001002f61318deb22881309fdbad39086c3cfaa3995d7497dc5f47cf728ae1673b7dc516810f6541047b7321b50cc9bd5867aae519a9bce24ea463df9e23173d3ace01085f5d13f59ffd447c1b7610d64bfb90299950d5c0e67f1ff0ff3afd06ffd881bd8c114b82c06be1411463f5e6e9f728ea4fc96facbbf9005e969fb0b76f6c30afefc862d9024345d60cc76cd188fee6638d7f70a7eeadf8846a8cb8a293f9f83743735c769297b349215aa97e52cc6edf446b07f9c0d418ac556b3b460f0f85e598f50b00d610111e2fbd56102d7aee54b7574d84696d22bbdab0f0299dff6fc2b2fd3f4af291685c90e9bf7a022801171d32fe2244314e84f5a01a29270f0000004020000000100040009000000056e616d6573000000100000000400040009000000026976000400090000000773657373696f6e00040009000000046461746100040009000000097369676e6174757265000000fe"

and the reverse with sodium::hex2bin:

identical(sodium::hex2bin(secret_hex), secret)
## [1] TRUE

(this is somewhat less space efficient than base64 encoding.

As a final option, you can just save the secret with saveRDS and read it in with readRDS like any other option. This will be the best route if the secret is saved into a more complicated R object (e.g., a list or data.frame).

See the other cyphr vignette (vignette("data", package = "cyphr")) for a suggested workflow for exchanging secrets within a team, and the wrapper functions below for more convenient ways of working with encrypted data.

Do you already have an ssh keypair? To find out, run

cyphr::keypair_openssl(NULL, NULL)

One of three things will happen:

  1. you will be prompted for your password to decrypt your private key, and then after entering it an object <cyphr_keypair: openssl> will be returned - you’re good to go!

  2. you were not prompted for your password, but got a <cyphr_keypair: openssl> object. You should consider whether this is appropriate and consider generating a new keypair with the private key encrypted. If you don’t then anyone who can read your private key can decrypt any message intended for you.

  3. you get an error like Did not find default ssh public key at ~/.ssh/id_rsa.pub. You need to create a keypair.

To create a keypair, you can use the cyphr::ssh_keygen() function as

cyphr::ssh_keygen("~/.ssh")

This will create the keypair as ~/.ssh/id_rsa and ~/.ssh/id_rsa.pub, which is where cyphr will look for your keys by default. See ?ssh_keygen for more information. (On Linux and macOS you might use the ssh-keygen command line utility. On windows, PuTTY` has a utility for creating keys.)

sodium

With sodium, things are largely the same with the exception that there is no standard format for saving sodium keys. The bits below use an in-memory key (which is just a collection of bytes) but these can also be filenames, each of which contains the contents of the key written out with writeBin.

First, generate keys for Alice:

key_a <- sodium::keygen()
pub_a <- sodium::pubkey(key_a)

the public key is derived from the private key, and Alice can share that with Bob. We next generate Bob’s keys

key_b <- sodium::keygen()
pub_b <- sodium::pubkey(key_b)

Bob would now share is public key with Alice.

If Alice wants to send a message to Bob she again uses her private key and Bob’s public key:

pair_a <- cyphr::keypair_sodium(pub_b, key_a)

As above, she can now send a message:

secret <- cyphr::encrypt_string("secret message", pair_a)
secret
##  [1] ae 2c 00 c8 03 f7 a2 a0 74 e1 97 a0 53 6c 8b a2 a4 b0 9f 8c 13 1f c4 65 62
## [26] 98 2f c4 cb 7d 81 f8 b8 87 c7 d3 b9 9e b1 a3 b2 30 36 7b 10 ae 12 92 2c 72
## [51] 2e 29 3a e7

Note how this line is identical to the one in the openssl section.

To decrypt this message, Bob would use Alice’s public key and his private key:

pair_b <- cyphr::keypair_sodium(pub_a, key_b)
cyphr::decrypt_string(secret, pair_b)
## [1] "secret message"

Encrypting things

Above, we used cyphr::encrypt_string and cyphr::decrypt_string to encrypt and decrypt a string. There are several such functions in the package that encrypt and decrypt

  • R objects encrypt_object / decrypt_object (using serialization and deserialization)
  • strings: encrypt_string / decrypt_string
  • raw vectors: encrypt_data / decrypt_data
  • files: encrypt_file / decrypt_file

For this section we will just use a sodium symmetric encryption key

key <- cyphr::key_sodium(sodium::keygen())

For the examples below, in the case of asymmetric encryption (using either cyphr::keypair_openssl or cyphr::keypair_sodium) the sender would use their private key and the recipient’s public key and the recipient would use the complementary key pair.

Objects

Here’s an object to encrypt:

obj <- list(x = 1:10, y = "secret")

This creates a bunch of raw bytes corresponding to the data (it’s not really possible to print this as anything nicer than bytes).

secret <- cyphr::encrypt_object(obj, key)
secret
##   [1] ca 98 0b 24 5e da 6f 33 70 8f b9 73 36 c7 31 b1 77 3f bb be f4 44 0a 3f cc
##  [26] 19 ca a0 84 89 06 8c 48 83 06 6d 98 37 e5 e9 8d dd 73 df 5a 26 16 24 89 cd
##  [51] e7 ed f5 66 ed 42 26 2d 09 eb 72 97 a4 81 7b 4f 23 9a 16 7d cb 7f 8f c3 4a
##  [76] 2f 03 e5 5c 95 ba 3b ba d5 b2 f5 f5 22 7e a7 3c 0b 4e a9 84 40 82 7a d1 72
## [101] 8b ec a3 68 93 72 3f a4 4b 6b cd be 9e 15 1f d8 56 b9 cc d8 6f 06 4b da 27
## [126] 97 24 ee 2d eb 5d 97 fa b3 74 5d e8 ca 54 c8 e8 ad 52 b2 f3 0a d7 27 3c f8
## [151] a0 6d ac 77 95 67 8a 5f 50 b4 d3 45 14 5c bc 9e ef 9e 5e 04 ed d1 96 82 0a
## [176] 2c ac 8b 25 4c 62 15 e3 bf 94 9e be e0 fe 3c 9b d5 2b db 20 8c 04 c4 5c 4b
## [201] d2 37 c9 82 4f 62 51 8d 6e 21 53 9f cd 12 ab f0 49 14 34 98 ae e2 6d ce 31
## [226] 28 a7 15 cd 7e 2e 4e d4 7a 31 53 99 22 e3 6a d1 38 58 9d bb e0 0c 8a 48 f7
## [251] 5b 57 f9 6e

The data can be decrypted with the decrypt_object function:

cyphr::decrypt_object(secret, key)
## $x
##  [1]  1  2  3  4  5  6  7  8  9 10
## 
## $y
## [1] "secret"

Optionally, this process can go via a file, using a third argument to the functions (note that temporary files are used here for compliance with CRAN policies - any path may be used in practice).

path_secret <- file.path(tempdir(), "secret.rds")
cyphr::encrypt_object(obj, key, path_secret)

There is now a file called secret.rds in the temporary directory:

file.exists(path_secret)
## [1] TRUE

though it is not actually an rds file:

readRDS(path_secret)
## Error in readRDS(path_secret): unknown input format

When passed a filename (as opposed to a raw vector), cyphr::decrypt_object will read the object in before decrypting it

cyphr::decrypt_object(path_secret, key)
## $x
##  [1]  1  2  3  4  5  6  7  8  9 10
## 
## $y
## [1] "secret"

Strings

For the case of strings we can do this in a slightly more lightweight way (the above function routes through serialize / deserialize which can be slow and will create larger objects than using charToRaw / rawToChar)

secret <- cyphr::encrypt_string("secret", key)
secret
##  [1] c3 5f 98 fd 33 f0 61 d5 3e f0 37 9d 1b b0 4f 59 af 30 41 e5 df e8 3a a4 7a
## [26] aa 52 be f3 0d 87 2b 4a 6e e1 d2 95 58 88 78 30 1f 85 0a 4a 11

and decrypt:

cyphr::decrypt_string(secret, key)
## [1] "secret"

Plain raw data

If these are not enough for you, you can work directly with raw objects (bunches of bytes) by using encrypt_data:

dat <- sodium::random(100)
dat # some random bytes
##   [1] 3e 04 f9 f7 59 03 04 3b 39 52 13 7a d9 db ed 96 9c 3c ee e8 99 91 38 0b 1f
##  [26] 18 e0 47 86 c6 86 fe d0 2e 2f b9 8e c1 12 b4 11 5a 49 87 d7 75 94 ac c4 d1
##  [51] 2a 17 48 0d df f6 38 a3 e0 e6 c6 93 20 d1 dd 8d 3d a9 1b b8 5a a6 38 94 14
##  [76] 2d 99 8e f2 47 8b 38 69 6c 61 b7 30 0c 1f 6b f1 88 62 bb 08 79 0f 49 cf 84
secret <- cyphr::encrypt_data(dat, key)
secret
##   [1] 54 2e 32 b8 68 37 5e 8e 07 1f 99 05 d8 3b 77 01 f8 39 de 13 4a 0e 33 cb fe
##  [26] ef 8d ac b2 64 13 7e 58 bf e5 61 24 ae 69 b1 a5 14 87 45 a3 22 24 21 f3 da
##  [51] 51 58 17 51 38 c6 db 0f f0 6b 7f 73 ff 2f bc 15 15 d2 bb ac 12 14 dc 75 fc
##  [76] 02 53 b0 92 3f ec eb 2d c1 d8 b7 9d 93 78 e2 db 83 07 85 5f 2f 5f 08 4e cd
## [101] 46 43 16 5a a4 76 00 0f a9 0f e3 6e ac cc 11 fd 11 5b bf c5 dc 31 3e 24 b3
## [126] 0c 7a d4 99 7b 91 88 5e c5 41 02 c4 2e 91 10

Decrypted data is the same as a the original data

identical(cyphr::decrypt_data(secret, key), dat)
## [1] TRUE

Files

Suppose we have written a file that we want to encrypt to send to someone (in a temporary directory for compliance with CRAN policies)

path_data_csv <- file.path(tempdir(), "iris.csv")
write.csv(iris, path_data_csv, row.names = FALSE)

You can encrypt that file with

path_data_enc <- file.path(tempdir(), "iris.csv.enc")
cyphr::encrypt_file(path_data_csv, key, path_data_enc)

This encrypted file can then be decrypted with

path_data_decrypted <- file.path(tempdir(), "idis2.csv")
cyphr::decrypt_file(path_data_enc, key, path_data_decrypted)

Which is identical to the original:

tools::md5sum(c(path_data_csv, path_data_decrypted))
##           /tmp/RtmprgIrZh/iris.csv          /tmp/RtmprgIrZh/idis2.csv 
## "5fe92fe6a2c1928ef5a67b8939fdaf8d" "5fe92fe6a2c1928ef5a67b8939fdaf8d"

An even higher level interface for files

This is the most user-friendly way of using the package when the aim is to encrypt and decrypt files. The package provides a pair of functions cyphr::encrypt and cyphr::decrypt that wrap file writing and file reading functions. In general you would use encrypt when writing a file and decrypt when reading one. They’re designed to be used like so:

Suppose you have a super-secret object that you want to share privately

key <- cyphr::key_sodium(sodium::keygen())
x <- list(a = 1:10, b = "don't tell anyone else")

If you save x to disk with saveRDS it will be readable by everyone until it is deleted. But if you encrypted the file that saveRDS produced it would be protected and only people with the key can read it:

path_object <- file.path(tempdir(), "secret.rds")
cyphr::encrypt(saveRDS(x, path_object), key)

(see below for some more details on how this works).

This file cannot be read with readRDS:

readRDS(path_object)
## Error in readRDS(path_object): unknown input format

but if we wrap the call with decrypt and pass in the config object it can be decrypted and read:

cyphr::decrypt(readRDS(path_object), key)
## $a
##  [1]  1  2  3  4  5  6  7  8  9 10
## 
## $b
## [1] "don't tell anyone else"

What happens in the call above is cyphr uses “non standard evaluation” to rewrite the call above so that it becomes (approximately)

  1. use cyphr::decrypt_file to decrypt “secret.rds” as a temporary file
  2. call readRDS on that temporary file
  3. delete the temporary file (even if there is an error in the above calls)

This non-standard evaluation breaks referential integrity (so may not be suitable for programming). You can always do this manually with encrypt_file / decrypt_file so long as you make sure to clean up after yourself.

The encrypt function inspects the call in the first argument passed to it and works out for the function provided (saveRDS) which argument corresponds to the filename (here "secret.rds"). It then rewrites the call to write out to a temporary file (using tempfile()). Then it calls encrypt_file (see below) on this temporary file to create the file asked for ("secret.rds"). Then it deletes the temporary file, though this will also happen in case of an error in any of the above.

The decrypt function works similarly. It inspects the call and detects that the first argument represents the filename. It decrypts that file to create a temporary file, and then runs readRDS on that file. Again it will delete the temporary file on exit.

The functions supported via this interface are:

  • readLines / writeLines
  • readRDS / writeRDS
  • read / save
  • read.table / write.table
  • read.csv / read.csv2 / write.csv
  • read.delim / read.delim2

But new functions can be added with the rewrite_register function. For example, to support the excellent rio package, whose import and export functions take the filename file you could use:

cyphr::rewrite_register("rio", "import", "file")
cyphr::rewrite_register("rio", "export", "file")

now you can read and write tabular data into and out of a great many different file formats with encryption with calls like

cyphr::encrypt(rio::export(mtcars, "file.json"), key)
cyphr::decrypt(rio::import("file.json"), key)

The functions above use non standard evaluation and so may not be suitable for programming or use in packages. An “escape hatch” is provided via encrypt_ and decrypt_ where the first argument is a quoted expression.

cyphr::encrypt_(quote(saveRDS(x, path_object)), key)
cyphr::decrypt_(quote(readRDS(path_object)), key)
## $a
##  [1]  1  2  3  4  5  6  7  8  9 10
## 
## $b
## [1] "don't tell anyone else"

Session keys

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.

To manually invalidate all keys you can use the cyphr::session_key_refresh function. For example, here is a symmetric key:

key <- cyphr::key_sodium(sodium::keygen())

which we can use to encrypt a secret string

secret <- cyphr::encrypt_string("my secret", key)

and decrypt it:

cyphr::decrypt_string(secret, key)
## [1] "my secret"

If we refresh the session key we invalidate the key object

cyphr::session_key_refresh()

and after this point the key cannot be used any further

cyphr::decrypt_string(secret, key)
## Error: Failed to decrypt key as session key has changed

This approach works because the package holds the session key within its environment (in cyphr:::session$key) which R will not serialize. As noted above - this approach does not prevent an attacker with the ability to snoop on your R session from discovering your private keys or sensitive data but it does prevent accidentally saving keys in a way that would be useful for an attacker to use in a subsequent session.

Further reading

  • The wikipedia page on Public Key cryptography has some nice diagrams that explain how key and data interact https://en.wikipedia.org/wiki/Public-key_cryptography
  • The vignettes in the openssl (vignette(package = "openssl")) and sodium (vignette(package = "openssl")) packages have explanations of how the tools used in cyphr work and interface with R.

Confused? Need help? Found a bug?