Skip to content

Commit 99d90dd

Browse files
authored
Merge pull request #4 from simonratner/more-docs
Tweak docs
2 parents e16082b + b2fedb1 commit 99d90dd

File tree

1 file changed

+114
-85
lines changed

1 file changed

+114
-85
lines changed

README.md

Lines changed: 114 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,22 @@ Encrypted model attributes in your favourite ORM.
1313
* 128-bit authentication tag
1414
* Additional authenticated data:
1515
* Key id: use different keys for different attributes (or different users),
16-
rotate keys over time without re-encrypting
16+
rotate keys for new data over time without re-encrypting old data
1717
* Object id: prevent substitution of encrypted values
1818

1919
All keys should be 32 bytes long, and cryptographically random. Manage these
20-
keys as you would any other credentials (environment config, keychain, vault).
21-
Generate keys with:
20+
keys as you would any other sensitive credentials (environment config, vault,
21+
keychain). You can generate random keys with this snippet:
2222
```
2323
node -p "require('crypto').randomBytes(32).toString('base64')"
2424
```
2525

26+
Refer to [NIST Special Publication 800-38D](http://doi.org/10.6028/NIST.SP.800-38D)
27+
for additional recommendations. In particular, you should pay attention to
28+
uniqueness requirements for keys and IVs, and constraints on the number of
29+
invocations with a given key (Section 8). These should inform key rotation
30+
policies.
31+
2632
# Threat model
2733

2834
This is designed to protect you from leaking sensitive user data under very
@@ -47,11 +53,109 @@ npm install encrypted-attr
4753

4854
# Use
4955

50-
While this module can be used stand-alone to encrypt individual values (see
51-
[tests](/test/encrypted-attr.spec.js)), it is designed to be wrapped into a
52-
plugin or hook for your favourite ORM. Eventually, this package may include
53-
such plugins for common ORMs, but for now, here's an example of integrating
54-
with [thinky](https://github.com/neumino/thinky):
56+
This module can be used stand-alone to encrypt individual values, or wrapped
57+
into a plugin or hook to your favourite ORM.
58+
59+
Here is a quick example to get started:
60+
61+
```js
62+
const EncryptedAttributes = require('encrypted-attr')
63+
64+
let encryptedAttributes = EncryptedAttributes(null, {
65+
keys: {
66+
k1: crypto.randomBytes(32).toString('base64') // use a persistent key
67+
},
68+
keyId: 'k1'
69+
})
70+
71+
let secretNumber = '555-55-5555'
72+
let encryptedNumber = encryptedAttributes.encryptAttribute(null, secretNumber)
73+
// YWVzLTI1Ni1nY20kJGsx$r2JF/XHvpsgNwJDs$c/P6GwnUZGokEjQ=$sEMv0cyKPBL90mo2zZ1MpQ
74+
```
75+
76+
### EncryptedAttributes(attributes, options)
77+
78+
Construct an instance to handle encryption and decryption. You should construct
79+
for each unique set of attributes and keys that you want to encrypt.
80+
81+
| Parameter | Type | Required? | Details |
82+
| :--------- | :--------------- | :--------- | :---------------------------- |
83+
| attributes | array of strings | _Optional_ | List attributes which should be encrypted. These can be specified as top-level string keys, or nested paths using dot-separated notation. This list is used by `encryptAll`/`decryptAll`, and is also useful for creating helper methods on your models. |
84+
| options | dictionary | _Optional_ | See below. |
85+
86+
These options are supported:
87+
88+
| Option | Type | Required? | Details |
89+
| :--------- | :--------------- | :--------- | :---------------------------- |
90+
| keys | dictionary | Required | Dictionary of all relevant data encryption keys, as `base64` strings. Since encrypted strings _embed the key id that was used to encrypt them_, it's important that `keys` contains the appropriate key for any previously encrypted data you might run across. |
91+
| keyId | string | Required | The id of the key to use for all _new encryptions_. This is not necessarily the only key that will be used for decryptions, because the key id gets embedded into the encrypted string. When that string is decrypted, this module unpacks that key id and uses it to determine the appropriate decryption key. This approach allows multiple keys to be used for the same attribute. (Note that this option is only _technically_ required if you need to encrypt new data. If you are only decrypting existing data, you do not need to provide it.) |
92+
| verifyId | boolean | _Optional_ | Whether or not to (a) use the `id` property of a provided source object as an additional piece of metadata during encryption, and (b) expect that metadata to be embedded in encrypted strings during decryption, and throw an error if the expected id does not match. Defaults to `false`. |
93+
94+
95+
### encryptAttribute(sourceObject, plaintextString)
96+
97+
Encrypt a value using one of your keys (specifically, the key indicated by the
98+
`keyId` option specified in the constructor).
99+
100+
Returns the encrypted representation of the value. Does nothing if the provided
101+
value is already encrypted using this module (so this method is idempotent).
102+
103+
```js
104+
let encryptedString = encryptAttributes.encryptAttribute(sourceObject, plaintextString)
105+
```
106+
107+
> `sourceObject` is optional, and only relevant if the `verifyId` option is used;
108+
otherwise you may pass `null` or `undefined`.
109+
110+
111+
### encryptAll(sourceObject)
112+
113+
Encrypt a subset of fields in the provided plain object. The set of fields to
114+
be encrypted is specified by the array of attribute paths supplied to the
115+
`EncryptedAttributes` constructor.
116+
117+
Returns the source object with any to-by-encrypted fields replaced by their
118+
encrypted representation. Note that this method modifies the provided object.
119+
120+
```js
121+
let partiallyEncryptedObject = encryptedAttributes.encryptAll(sourceObject)
122+
```
123+
124+
125+
### decryptAttribute(sourceObject, encryptedString)
126+
127+
Decrypt a value.
128+
129+
Returns the plaintext string. Does nothing if the provided value does not look
130+
like it was encrypted using this module (so this method is idempotent).
131+
132+
```js
133+
let plaintextString = encryptedAttributes.decryptAttribute(sourceObject, encryptedString)
134+
```
135+
136+
> `sourceObject` is optional, and only relevant if the `verifyId` option is used;
137+
otherwise you may pass `null` or `undefined`.
138+
139+
140+
### decryptAll(sourceObject)
141+
142+
Decrypt a subset of fields in the provided plain object. The set of fields to
143+
be decrypted is specified by the array of attribute paths supplied to the
144+
145+
Returns the source object with any encrypted fields replaced by their plaintext
146+
value. Note that this method modifies the provided object.
147+
148+
```js
149+
let decryptedObject = encryptedAttributes.decryptAll(partiallyEncryptedObject)
150+
```
151+
152+
153+
# Use with an ORM
154+
155+
While this module can be used stand-alone, it is designed to be wrapped into
156+
a plugin or hook to your favourite ORM. Eventually, this package may include
157+
such plugins for common ORMs, but for now, here's an example using
158+
[thinky](https://github.com/neumino/thinky):
55159

56160
```js
57161
const EncryptedAttributes = require('encrypted-attr')
@@ -122,89 +226,14 @@ async function storeSomeSecrets (doc) {
122226
// id: '543bed92-e241-4151-9d8f-1aa942c36d24',
123227
// nested: {
124228
// hint: 'colour',
125-
// secret: 'YWVzLTI1Ni1nY20kMSQwMQ==$JvDvLhZ1GlqYgCXx$wQCLkW7u$kt5To2YBdG5USLmtBTHS+g'
229+
// secret: 'YWVzLTI1Ni1nY20k...$NFvcFAaPTDay3uWH$t3G9Jrpy$g+BJT/UvfuboMB3ARiDRIQ'
126230
// },
127-
// secret: 'YWVzLTI1Ni1nY20kMSQwMQ==$0n/ZpuUUIHRzAX5H$jbUS$bFRZOEe3mBrnWVQX6DMA3g'
231+
// secret: 'YWVzLTI1Ni1nY20k...$6UdqWqv9ox305Wmt$zyNF$S5BOgZSvMG3H24foFaTQjg'
128232
// }
129233
}
130234
```
131235

132236

133-
### Standalone usage
134-
135-
136-
After requiring and invoking this module as shown above, you'll be able to call any of several available methods on the hydrated "encryptedAttributes" object. Those methods are documented below.
137-
138-
> Alternatively, you can simply chain:
139-
>
140-
> ```js
141-
> const EncryptedAttributes = require('encrypted-attr');
142-
>
143-
> var rawSocialSecurityNumber = '555-55-5555';
144-
>
145-
> var encryptedSSN = EncryptedAttributes(['ssn'], {
146-
> keyId: 'default',
147-
> keys: { default: 'keyboardcat' }
148-
> })
149-
> .encryptAttribute(undefined, '555-55-5555');
150-
> ```
151-
152-
153-
##### encryptAttribute()
154-
155-
Encrypt a string using one of your keys (specifically, the key indicated by the configured `keyId`).
156-
157-
```js
158-
var encryptedString = ea.encryptAttribute(optionalSourceObj, rawString);
159-
```
160-
161-
> `optionalSourceObj` is only relevant if the `verifyId` option is enabled. If you don't have that option enabled, you can simply pass in `undefined`.
162-
163-
##### decryptAttribute()
164-
165-
Decrypt a value.
166-
167-
```js
168-
var rawString = ea.encryptAttribute(optionalSourceObj, encryptedString)
169-
```
170-
171-
> `optionalSourceObj` is only relevant if the `verifyId` option is enabled. If you don't have that option enabled, you can simply pass in `undefined`.
172-
173-
174-
##### encryptAll()
175-
176-
Encrypt a subset of the provided dictionary (i.e. plain JavaScript object) of raw, unencrypted values.
177-
178-
```js
179-
var partiallyEncryptedObj = ea.encryptAll(rawObj)
180-
```
181-
182-
> Only fields identified in the array of keypaths you passed in during initial setup of this module will actually be encrypted.
183-
184-
185-
##### decryptAll()
186-
187-
Decrypt a subset of the provided dictionary of encrypted values.
188-
189-
```js
190-
var rawObj = ea.decryptAll(partiallyEncryptedObj)
191-
```
192-
193-
> Only fields identified in the array of keypaths you passed in during initial setup of this module will actually be decrypted.
194-
195-
196-
197-
198-
# Options
199-
200-
| Option | Type | Required? | Details |
201-
|:-----------|----------------|:---------------|:---------------------------------------------------------------------|
202-
| keyId | ((string)) | Required(ish) | The id of the key to use for all **new encryptions**. This is _not_ necessarily the only key that will be used for decryptions though, because the key id you choose gets embedded into the encrypted string itself. Then before that string is decrypted, this module simply unpacks that key id and uses it to determine the appropriate decryption key. This approach allows for using multiple keys. (Note that this option is only _technically_ required if you need to encrypt new data. If you are only decrypting existing data, you needn't pass it in.) |
203-
| keys | ((dictionary)) | Required | A dictionary of all relevant data encryption keys (DEKs). Since encrypted strings _contain the key id that was used to encrypt them_, it's important that `keys` contain the appropriate keys for any past key ids it might run across when attempting to decrypt those strings.
204-
| verifyId | ((boolean)) | _Optional._ | Whether or not to (A) use the `id` property of a provided source object as an additional piece of metadata during encryption, and (B) expect that metadata to be embedded in encrypted strings during decryption, and throw an error if the expected idea does not match. Defaults to `false`.
205-
206-
207-
208237
# License
209238

210239
[MIT](LICENSE)

0 commit comments

Comments
 (0)