simple-secrets 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +6 -0
- data/lib/simple-secrets.rb +1 -0
- data/lib/simple_secrets.rb +7 -0
- data/lib/simple_secrets/packet.rb +107 -0
- data/lib/simple_secrets/primitives.rb +372 -0
- data/lib/simple_secrets/version.rb +3 -0
- data/simple_secrets.gemspec +26 -0
- data/spec/packet_spec.rb +95 -0
- data/spec/primitives_spec.rb +362 -0
- data/spec/simple_secrets_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- metadata +133 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Tim Shadel
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
|
2
|
+
# SimpleSecrets [](https://travis-ci.org/timshadel/simple-secrets.rb)
|
3
|
+
|
4
|
+
A Ruby client for [simple-secrets][simple-secrets], the simple, opinionated library for encrypting small packets of data securely.
|
5
|
+
|
6
|
+
[simple-secrets]: https://github.com/timshadel/simple-secrets
|
7
|
+
|
8
|
+
## Examples
|
9
|
+
|
10
|
+
### Basic
|
11
|
+
|
12
|
+
Send:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
require('simple-secrets')
|
16
|
+
|
17
|
+
# Try `head /dev/urandom | shasum -a 256` to make a decent 256-bit key
|
18
|
+
master_key = new Buffer('<64-char hex string (32 bytes, 256 bits)>', 'hex');
|
19
|
+
# => <Buffer 71 c8 67 56 23 4b fd 3c 37 ... >
|
20
|
+
|
21
|
+
var sender = secrets(master_key);
|
22
|
+
var packet = sender.pack('this is a secret message');
|
23
|
+
# => 'OqlG6KVMeyFYmunboS3HIXkvN_nXKTxg2yNkQydZOhvJrZvmfov54hUmkkiZCnlhzyrlwOJkbV7XnPPbqvdzZ6TsFOO5YdmxjxRksZmeIhbhLaMiDbfsOuSY1dBn_ZgtYCw-FRIM'
|
24
|
+
```
|
25
|
+
|
26
|
+
Receive:
|
27
|
+
|
28
|
+
```js
|
29
|
+
var secrets = require('simple-secrets');
|
30
|
+
|
31
|
+
// Same shared key
|
32
|
+
var master_key = new Buffer('<shared-key-hex>', 'hex');
|
33
|
+
var sender = secrets(master_key);
|
34
|
+
var packet = new Buffer('<read data from somewhere>');
|
35
|
+
// => 'OqlG6KVMeyFYmunboS3HIXkvN_nXKTxg2yNkQydZOhvJrZvmfov54hUmkkiZCnlhzyrlwOJkbV7XnPPbqvdzZ6TsFOO5YdmxjxRksZmeIhbhLaMiDbfsOuSY1dBn_ZgtYCw-FRIM'
|
36
|
+
var secret_message = sender.unpack(packet);
|
37
|
+
// => 'this is a secret message'
|
38
|
+
```
|
39
|
+
|
40
|
+
|
41
|
+
## Can you add ...
|
42
|
+
|
43
|
+
This implementation follows [simple-secrets] for 100% compatibility.
|
44
|
+
|
45
|
+
## License
|
46
|
+
|
47
|
+
MIT.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "simple_secrets"
|
@@ -0,0 +1,107 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module SimpleSecrets
|
4
|
+
|
5
|
+
class Packet
|
6
|
+
|
7
|
+
def initialize master_key
|
8
|
+
raise ArgumentError unless master_key
|
9
|
+
|
10
|
+
@master_key = hex_to_bin master_key
|
11
|
+
@identity = Primitives.identify @master_key
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_body data
|
15
|
+
nonce = Primitives.nonce
|
16
|
+
bindata = Primitives.serialize data
|
17
|
+
|
18
|
+
body = "#{nonce}#{bindata}"
|
19
|
+
|
20
|
+
Primitives.zero nonce, bindata
|
21
|
+
body
|
22
|
+
end
|
23
|
+
|
24
|
+
def body_to_data body
|
25
|
+
nonce = body[0...16]
|
26
|
+
bindata = body[16..-1]
|
27
|
+
|
28
|
+
data = Primitives.deserialize bindata
|
29
|
+
|
30
|
+
Primitives.zero nonce, bindata
|
31
|
+
data
|
32
|
+
end
|
33
|
+
|
34
|
+
def encrypt_body body, master_key
|
35
|
+
key = Primitives.derive_sender_key master_key
|
36
|
+
|
37
|
+
cipher_data = Primitives.encrypt body, key
|
38
|
+
|
39
|
+
Primitives.zero key
|
40
|
+
cipher_data
|
41
|
+
end
|
42
|
+
|
43
|
+
def decrypt_body cipher_data, master_key
|
44
|
+
key = Primitives.derive_sender_key master_key
|
45
|
+
iv = cipher_data[0...16]
|
46
|
+
encrypted = cipher_data[16..-1]
|
47
|
+
|
48
|
+
body = Primitives.decrypt encrypted, key, iv
|
49
|
+
|
50
|
+
Primitives.zero key, iv, encrypted
|
51
|
+
body
|
52
|
+
end
|
53
|
+
|
54
|
+
def authenticate data, master_key, identity
|
55
|
+
hmac_key = Primitives.derive_sender_hmac master_key
|
56
|
+
|
57
|
+
auth = "#{identity}#{data}"
|
58
|
+
mac = Primitives.mac auth, hmac_key
|
59
|
+
packet = "#{auth}#{mac}"
|
60
|
+
|
61
|
+
Primitives.zero hmac_key, mac
|
62
|
+
packet
|
63
|
+
end
|
64
|
+
|
65
|
+
def verify packet, master_key, identity
|
66
|
+
packet_id = packet[0...6]
|
67
|
+
return nil unless Primitives.compare packet_id, identity
|
68
|
+
|
69
|
+
data = packet[0...-32]
|
70
|
+
packet_mac = packet[-32..-1]
|
71
|
+
hmac_key = Primitives.derive_sender_hmac master_key
|
72
|
+
mac = Primitives.mac data, hmac_key
|
73
|
+
return nil unless Primitives.compare packet_mac, mac
|
74
|
+
|
75
|
+
Primitives.zero hmac_key, mac
|
76
|
+
packet[6...-32]
|
77
|
+
end
|
78
|
+
|
79
|
+
def pack data
|
80
|
+
body = build_body data
|
81
|
+
encrypted = encrypt_body body, @master_key
|
82
|
+
packet = authenticate encrypted, @master_key, @identity
|
83
|
+
websafe = Primitives.stringify packet
|
84
|
+
|
85
|
+
Primitives.zero body, encrypted, packet
|
86
|
+
websafe
|
87
|
+
end
|
88
|
+
|
89
|
+
def unpack websafe_data
|
90
|
+
packet = Primitives.binify websafe_data
|
91
|
+
cipher_data = verify packet, @master_key, @identity
|
92
|
+
Primitives.zero packet
|
93
|
+
return nil unless cipher_data
|
94
|
+
|
95
|
+
body = decrypt_body cipher_data, @master_key
|
96
|
+
data = body_to_data body
|
97
|
+
|
98
|
+
Primitives.zero body, cipher_data
|
99
|
+
data
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
def hex_to_bin string
|
104
|
+
[string].pack('H*')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,372 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
require 'msgpack'
|
4
|
+
|
5
|
+
module SimpleSecrets
|
6
|
+
|
7
|
+
|
8
|
+
# Public: Various methods useful for performing cryptographic operations.
|
9
|
+
# WARNING: Using any of these primitives in isolation could be Bad. Take cautious.
|
10
|
+
#
|
11
|
+
# Examples
|
12
|
+
#
|
13
|
+
# Primitives.nonce
|
14
|
+
# # => "\x02\x0E\xBB\xBE\xA2\xA4\f\x80\x11N\xCDui\xEE<e"
|
15
|
+
module Primitives
|
16
|
+
|
17
|
+
|
18
|
+
# Public: Provide 16 securely random bytes.
|
19
|
+
#
|
20
|
+
# Examples
|
21
|
+
#
|
22
|
+
# nonce
|
23
|
+
# # => "\x02\x0E\xBB\xBE\xA2\xA4\f\x80\x11N\xCDui\xEE<e"
|
24
|
+
#
|
25
|
+
# Returns 16 random bytes in a binary string
|
26
|
+
def self.nonce
|
27
|
+
OpenSSL::Random.random_bytes 16
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Public: Generate the authentication key for messages originating
|
32
|
+
# from the channel's Sender side.
|
33
|
+
#
|
34
|
+
# Uses the ASCII string 'simple-crypto/sender-hmac-key' as the role.
|
35
|
+
#
|
36
|
+
# master_key - the 256-bit master key of this secure channel
|
37
|
+
#
|
38
|
+
# Examples
|
39
|
+
#
|
40
|
+
# Primitives.derive_sender_hmac(master_key)
|
41
|
+
# # =>
|
42
|
+
#
|
43
|
+
# Returns 256-bit sender hmac key
|
44
|
+
def self.derive_sender_hmac master_key
|
45
|
+
derive(master_key, 'simple-crypto/sender-hmac-key')
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# Public: Generate the encryption key for messages originating from the
|
50
|
+
# channel's Sender side.
|
51
|
+
#
|
52
|
+
# Uses the ASCII string 'simple-crypto/sender-cipher-key' as the role.
|
53
|
+
#
|
54
|
+
# master_key - the 256-bit master key of this secure channel
|
55
|
+
#
|
56
|
+
# Examples
|
57
|
+
#
|
58
|
+
# Primitives.derive_sender_key(master_key)
|
59
|
+
# # =>
|
60
|
+
#
|
61
|
+
# Returns 256-bit sender encryption key
|
62
|
+
def self.derive_sender_key master_key
|
63
|
+
derive(master_key, 'simple-crypto/sender-cipher-key')
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# Public: Generate the authentication key for messages originating
|
68
|
+
# from the channel's Receiver side.
|
69
|
+
#
|
70
|
+
# Uses the ASCII string 'simple-crypto/receiver-hmac-key' as the role.
|
71
|
+
#
|
72
|
+
# master_key - the 256-bit master key of this secure channel
|
73
|
+
#
|
74
|
+
# Examples
|
75
|
+
#
|
76
|
+
# Primitives.derive_receiver_hmac(master_key)
|
77
|
+
# # =>
|
78
|
+
#
|
79
|
+
# Returns 256-bit receiver hmac key
|
80
|
+
def self.derive_receiver_hmac master_key
|
81
|
+
derive(master_key, 'simple-crypto/receiver-hmac-key')
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
# Public: Generate the encryption key for messages originating
|
86
|
+
# from the channel's Receiver side.
|
87
|
+
#
|
88
|
+
# Uses the ASCII string 'simple-crypto/receiver-cipher-key' as the role.
|
89
|
+
#
|
90
|
+
# master_key - the 256-bit master key of this secure channel
|
91
|
+
#
|
92
|
+
# Examples
|
93
|
+
#
|
94
|
+
# Primitives.derive_receiver_key(master_key)
|
95
|
+
# # =>
|
96
|
+
#
|
97
|
+
# Returns 256-bit receiver encryption key
|
98
|
+
def self.derive_receiver_key master_key
|
99
|
+
derive(master_key, 'simple-crypto/receiver-cipher-key')
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
# Public: Encrypt buffer with the given key.
|
104
|
+
#
|
105
|
+
# Uses AES256 with a random 128-bit initialization vector.
|
106
|
+
#
|
107
|
+
# binary - the plaintext binary string
|
108
|
+
# key - the 256-bit encryption key
|
109
|
+
#
|
110
|
+
# Examples
|
111
|
+
#
|
112
|
+
# Primitives.encrypt('', '')
|
113
|
+
# # =>
|
114
|
+
#
|
115
|
+
# Returns a binary string of (IV || ciphertext)
|
116
|
+
def self.encrypt binary, key
|
117
|
+
assertBinary(binary)
|
118
|
+
assertBinary(key)
|
119
|
+
assert256BitBinary(key)
|
120
|
+
|
121
|
+
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
122
|
+
cipher.encrypt
|
123
|
+
cipher.key = key
|
124
|
+
iv = cipher.random_iv
|
125
|
+
|
126
|
+
encrypted = ''.force_encoding('BINARY')
|
127
|
+
encrypted << iv
|
128
|
+
encrypted << cipher.update(binary)
|
129
|
+
encrypted << cipher.final
|
130
|
+
encrypted
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
# Public: Decrypt buffer with the given key and initialization vector.
|
135
|
+
#
|
136
|
+
# Uses AES256.
|
137
|
+
#
|
138
|
+
# binary - ciphertext
|
139
|
+
# key - the 256-bit encryption key
|
140
|
+
# iv - the 128-bit initialization vector
|
141
|
+
#
|
142
|
+
# Examples
|
143
|
+
#
|
144
|
+
# Primitives.decrypt('', '')
|
145
|
+
# # =>
|
146
|
+
#
|
147
|
+
# Returns the plaintext binary string
|
148
|
+
def self.decrypt binary, key, iv
|
149
|
+
assertBinary(binary, key, iv)
|
150
|
+
assert256BitBinary(key)
|
151
|
+
assert128BitBinary(iv)
|
152
|
+
|
153
|
+
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
154
|
+
cipher.decrypt
|
155
|
+
cipher.key = key
|
156
|
+
cipher.iv = iv
|
157
|
+
|
158
|
+
decrypted = ''.force_encoding('BINARY')
|
159
|
+
decrypted << cipher.update(binary)
|
160
|
+
decrypted << cipher.final
|
161
|
+
decrypted
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
# Public: Create a short identifier for potentially sensitive data.
|
166
|
+
#
|
167
|
+
# binary - the data to identify
|
168
|
+
#
|
169
|
+
# Examples
|
170
|
+
#
|
171
|
+
# Primitives.identify('')
|
172
|
+
# # =>
|
173
|
+
#
|
174
|
+
# Returns a 6-byte binary string identifier
|
175
|
+
def self.identify binary
|
176
|
+
assertBinary(binary)
|
177
|
+
|
178
|
+
hash = OpenSSL::Digest::SHA256.new
|
179
|
+
hash << [binary.size].pack("C*")
|
180
|
+
hash << binary
|
181
|
+
hash.digest[0..5]
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
# Public: Create a message authentication code for the given data.
|
186
|
+
# Uses HMAC-SHA256.
|
187
|
+
#
|
188
|
+
# binary - data to authenticate
|
189
|
+
# hmac_key - the authentication key
|
190
|
+
#
|
191
|
+
# Examples
|
192
|
+
#
|
193
|
+
# Primitives.mac('','')
|
194
|
+
# # =>
|
195
|
+
#
|
196
|
+
# Returns a 32-byte MAC binary string
|
197
|
+
def self.mac binary, hmac_key
|
198
|
+
assertBinary(binary, hmac_key)
|
199
|
+
assert256BitBinary(hmac_key)
|
200
|
+
|
201
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, hmac_key, binary)
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
# Public: Use a constant-time comparison algorithm to reduce
|
206
|
+
# side-channel attacks.
|
207
|
+
#
|
208
|
+
# Short-circuits only when the two buffers aren't the same length.
|
209
|
+
#
|
210
|
+
# a - a binary string
|
211
|
+
# b - a binary string
|
212
|
+
#
|
213
|
+
# Examples
|
214
|
+
#
|
215
|
+
# Primitives.compare('','')
|
216
|
+
# # =>
|
217
|
+
#
|
218
|
+
# Returns true if both buffer contents match
|
219
|
+
def self.compare a, b
|
220
|
+
assertBinary(a, b)
|
221
|
+
|
222
|
+
# things must be the same length to compare them.
|
223
|
+
return false if a.bytesize != b.bytesize
|
224
|
+
|
225
|
+
# constant-time compare
|
226
|
+
# hat-tip to https://github.com/freewil/scmp for |=
|
227
|
+
same = 0;
|
228
|
+
(0...a.bytesize).each do |i|
|
229
|
+
same |= a.getbyte(i) ^ b.getbyte(i)
|
230
|
+
end
|
231
|
+
same == 0
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
# Public: Turn a websafe string back into a binary string.
|
236
|
+
#
|
237
|
+
# Uses base64url encoding.
|
238
|
+
#
|
239
|
+
# string - websafe string
|
240
|
+
#
|
241
|
+
# Examples
|
242
|
+
#
|
243
|
+
# Primitives.binify('')
|
244
|
+
# # =>
|
245
|
+
#
|
246
|
+
# Returns the binary version of this string
|
247
|
+
def self.binify string
|
248
|
+
raise 'base64url string required.' unless (string.instance_of?(String) && string =~ /^[a-zA-Z0-9_\-]+$/)
|
249
|
+
|
250
|
+
string += '=' while !(string.size % 4).zero?
|
251
|
+
Base64.urlsafe_decode64(string)
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
# Public: Turn a binary buffer into a websafe string.
|
256
|
+
#
|
257
|
+
# Uses base64url encoding.
|
258
|
+
#
|
259
|
+
# binary - data which needs to be websafe
|
260
|
+
#
|
261
|
+
# Examples
|
262
|
+
#
|
263
|
+
# Primitives.stringify('')
|
264
|
+
# # =>
|
265
|
+
#
|
266
|
+
# Returns the websafe string
|
267
|
+
def self.stringify binary
|
268
|
+
assertBinary(binary)
|
269
|
+
|
270
|
+
Base64.urlsafe_encode64(binary).gsub('=','')
|
271
|
+
end
|
272
|
+
|
273
|
+
|
274
|
+
# Public: Turn a JSON-like object into a binary
|
275
|
+
# representation suitable for use in crypto functions.
|
276
|
+
# This object will possibly be deserialized in a different
|
277
|
+
# programming environment—it should be JSON-like in structure.
|
278
|
+
#
|
279
|
+
# Uses MsgPack data serialization.
|
280
|
+
#
|
281
|
+
# object - any object without cycles which responds to `to_msgpack`
|
282
|
+
#
|
283
|
+
# Examples
|
284
|
+
#
|
285
|
+
# Primitives.serialize('')
|
286
|
+
# # =>
|
287
|
+
#
|
288
|
+
# Returns the binary version of this object
|
289
|
+
def self.serialize object
|
290
|
+
object.to_msgpack
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
# Public: Turn a binary representation into a Ruby object
|
295
|
+
# suitable for use in application logic. This object
|
296
|
+
# possibly originated in a different programming
|
297
|
+
# environment—it should be JSON-like in structure.
|
298
|
+
#
|
299
|
+
# Uses MsgPack data serialization.
|
300
|
+
#
|
301
|
+
# binary - a binary string version of the object
|
302
|
+
#
|
303
|
+
# Examples
|
304
|
+
#
|
305
|
+
# Primitives.deserialize('')
|
306
|
+
# # =>
|
307
|
+
#
|
308
|
+
# Returns the Ruby object
|
309
|
+
def self.deserialize binary
|
310
|
+
assertBinary(binary)
|
311
|
+
|
312
|
+
MessagePack.unpack(binary)
|
313
|
+
end
|
314
|
+
|
315
|
+
|
316
|
+
# Public: Overwrite the contents of the buffer with zeroes.
|
317
|
+
# This is critical for removing sensitive data from memory.
|
318
|
+
#
|
319
|
+
# args - binary strings whose content should be wiped
|
320
|
+
#
|
321
|
+
# Examples
|
322
|
+
#
|
323
|
+
# Primitives.zero('','')
|
324
|
+
# # =>
|
325
|
+
#
|
326
|
+
# Returns an array of references to the strings which have been zeroed
|
327
|
+
def self.zero *args
|
328
|
+
assertBinary(*args)
|
329
|
+
args.each do |buf|
|
330
|
+
buf.gsub!(/./,"\x00")
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
private
|
335
|
+
|
336
|
+
# Private: Generate an encryption or hmac key from the master key and role.
|
337
|
+
# Uses SHA256(key || role). [TODO: link or citation]
|
338
|
+
#
|
339
|
+
# master_key - The 256-bit binary string master key of this secure channel.
|
340
|
+
# role - The part of the protocol in which this key will be used.
|
341
|
+
#
|
342
|
+
# Examples
|
343
|
+
#
|
344
|
+
# derive "\x0ER\xE5\x88\xC2\xBB<\xAFZ?\xA5\xCCx\xA6@AB(Bc\x962\x7F:\xF7\x0E\x1Cl\xB9\x02Y\xE4", "some-protocol/some-role"
|
345
|
+
# # => "~\x80\xB4\xC3>\xC4\xDEw\xB2\xD2\x92\xC9\x88\xA8\xD7p\xAF\xF6Y\x95\x91\xA3\xFDV\xC5qo\x80U\x19P\xB0"
|
346
|
+
#
|
347
|
+
# Returns 256-bit derived key as a 32-byte binary string
|
348
|
+
|
349
|
+
def self.derive master_key, role
|
350
|
+
assertBinary(master_key)
|
351
|
+
assert256BitBinary(master_key)
|
352
|
+
hash = OpenSSL::Digest::SHA256.new
|
353
|
+
hash << master_key
|
354
|
+
hash << role.force_encoding('BINARY')
|
355
|
+
hash.digest
|
356
|
+
end
|
357
|
+
|
358
|
+
def self.assertBinary *binaries
|
359
|
+
binaries.each { |binary| raise "Bad encoding. Binary string required." unless binary.instance_of?(String) && binary.encoding == Encoding::ASCII_8BIT }
|
360
|
+
end
|
361
|
+
|
362
|
+
def self.assert256BitBinary binary
|
363
|
+
raise '256-bit binary string required.' unless binary.size == 32
|
364
|
+
end
|
365
|
+
|
366
|
+
def self.assert128BitBinary binary
|
367
|
+
raise '128-bit binary string required.' unless binary.size == 16
|
368
|
+
end
|
369
|
+
|
370
|
+
end
|
371
|
+
|
372
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'simple_secrets/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "simple-secrets"
|
8
|
+
spec.version = SimpleSecrets::VERSION
|
9
|
+
spec.authors = ["Tim Shadel"]
|
10
|
+
spec.email = ["tim@shadelsoftware.com"]
|
11
|
+
spec.description = %q{A Ruby client for simple-secrets, the simple, opinionated library for encrypting small packets of data securely.}
|
12
|
+
spec.summary = %q{A Ruby client for simple-secrets, the simple, opinionated library for encrypting small packets of data securely.}
|
13
|
+
spec.homepage = "https://github.com/timshadel/simple-secrets.rb"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "msgpack"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
end
|
data/spec/packet_spec.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include SimpleSecrets
|
4
|
+
|
5
|
+
describe Packet do
|
6
|
+
|
7
|
+
let(:master_key){ "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd".force_encoding 'BINARY' }
|
8
|
+
let(:data){ 'foobar' }
|
9
|
+
let(:nonce){ '11'.hex_to_bin 16 } # Generated with Primitives.nonce
|
10
|
+
|
11
|
+
let(:test_body){ "\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\xA6foobar".force_encoding 'BINARY' }
|
12
|
+
let(:bad_id){ 'fd'.hex_to_bin 6 }
|
13
|
+
let(:bad_mac){ 'fd'.hex_to_bin 32 }
|
14
|
+
|
15
|
+
subject{ Packet.new master_key }
|
16
|
+
|
17
|
+
describe '#initialize' do
|
18
|
+
it 'sets its master key' do
|
19
|
+
its_key = subject.instance_variable_get(:@master_key)
|
20
|
+
its_key.unpack('H*').first.should eq master_key
|
21
|
+
its_key.encoding.should eq Encoding::ASCII_8BIT
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'sets its identity' do
|
25
|
+
identity = Primitives.identify subject.instance_variable_get(:@master_key)
|
26
|
+
subject.instance_variable_get(:@identity).should eq identity
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#build_body' do
|
31
|
+
it 'concatenates the serialized body with a nonce' do
|
32
|
+
Primitives.should_receive(:nonce){ nonce }
|
33
|
+
subject.build_body(data).should eq test_body
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#body_to_data' do
|
38
|
+
it 'it splits out the nonce and deserializes the body' do
|
39
|
+
subject.body_to_data(test_body).should eq data
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#encrypt_body and #decrypt_body' do
|
44
|
+
it 'encrypts the data and then decrypts it back' do
|
45
|
+
key = subject.instance_variable_get(:@master_key)
|
46
|
+
|
47
|
+
cipher_data = subject.encrypt_body test_body, key
|
48
|
+
decrypted_body = subject.decrypt_body cipher_data, key
|
49
|
+
test_body.should_not eq cipher_data
|
50
|
+
cipher_data.should_not eq decrypted_body
|
51
|
+
decrypted_body.should eq test_body
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#authenticate and #verify' do
|
56
|
+
it 'creates an authentication signature and then verifies it' do
|
57
|
+
key = subject.instance_variable_get(:@master_key)
|
58
|
+
id = subject.instance_variable_get(:@identity)
|
59
|
+
|
60
|
+
packet = subject.authenticate test_body, key, id
|
61
|
+
data = subject.verify packet, key, id
|
62
|
+
data.should eq test_body
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns nil if the key identity does not match' do
|
66
|
+
key = subject.instance_variable_get(:@master_key)
|
67
|
+
id = subject.instance_variable_get(:@identity)
|
68
|
+
|
69
|
+
packet = subject.authenticate test_body, key, bad_id
|
70
|
+
data = subject.verify packet, key, id
|
71
|
+
data.should be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'returns nil if the MAC does not match' do
|
75
|
+
key = subject.instance_variable_get(:@master_key)
|
76
|
+
id = subject.instance_variable_get(:@identity)
|
77
|
+
|
78
|
+
packet = subject.authenticate test_body, key, id
|
79
|
+
packet = "#{packet[0...-32]}#{bad_mac}"
|
80
|
+
|
81
|
+
data = subject.verify packet, key, id
|
82
|
+
data.should be_nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '.pack and .unpack' do
|
87
|
+
it 'encrypts and signs data into web-safe string, then verifies and decrypts it back' do
|
88
|
+
packed_data = subject.pack data
|
89
|
+
packed_data.should_not eq data
|
90
|
+
|
91
|
+
unpacked_data = subject.unpack packed_data
|
92
|
+
unpacked_data.should eq data
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,362 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include SimpleSecrets
|
4
|
+
|
5
|
+
describe Primitives do
|
6
|
+
|
7
|
+
describe 'nonce' do
|
8
|
+
it 'should return 16 random bytes' do
|
9
|
+
expect(Primitives.nonce.size).to eq(16)
|
10
|
+
expect(Primitives.nonce).to_not eq(Primitives.nonce)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'derive_sender_hmac' do
|
15
|
+
it 'should require a buffer' do
|
16
|
+
buf = "32".hex_to_bin(32)
|
17
|
+
expect { Primitives.derive_sender_hmac('') }.to raise_error(/binary string required/i)
|
18
|
+
expect { Primitives.derive_sender_hmac(buf) }.to_not raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should require a 256-bit key' do
|
22
|
+
short = "33".hex_to_bin(31)
|
23
|
+
exact = "33".hex_to_bin(32)
|
24
|
+
long = "33".hex_to_bin(33)
|
25
|
+
|
26
|
+
expect { Primitives.derive_sender_hmac(short) }.to raise_error(/256-bit/i)
|
27
|
+
expect(Primitives.derive_sender_hmac(exact).encoding).to eq(Encoding::ASCII_8BIT)
|
28
|
+
expect { Primitives.derive_sender_hmac(long) }.to raise_error(/256-bit/i)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should derive a 256-bit hmac key from a 256-bit master key' do
|
32
|
+
master_key = 'bc'.hex_to_bin(32)
|
33
|
+
|
34
|
+
hmac_key = Primitives.derive_sender_hmac(master_key);
|
35
|
+
expect(hmac_key.size).to eq(32)
|
36
|
+
expect(hmac_key).to eq('1e2e2725f135463f05c268ffd1c1687dbc9dd7da65405697471052236b3b3088'.hex_to_bin);
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'derive_sender_key' do
|
41
|
+
it 'should require a buffer' do
|
42
|
+
buf = "32".hex_to_bin(32)
|
43
|
+
expect { Primitives.derive_sender_key('') }.to raise_error(/binary string required/i)
|
44
|
+
expect { Primitives.derive_sender_key(buf) }.to_not raise_error
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should require a 256-bit key' do
|
48
|
+
short = "33".hex_to_bin(31)
|
49
|
+
exact = "33".hex_to_bin(32)
|
50
|
+
long = "33".hex_to_bin(33)
|
51
|
+
|
52
|
+
expect { Primitives.derive_sender_key(short) }.to raise_error(/256-bit/i)
|
53
|
+
expect(Primitives.derive_sender_key(exact).encoding).to eq(Encoding::ASCII_8BIT)
|
54
|
+
expect { Primitives.derive_sender_key(long) }.to raise_error(/256-bit/i)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should derive a 256-bit encryption key from a 256-bit master key' do
|
58
|
+
master_key = 'bc'.hex_to_bin(32)
|
59
|
+
|
60
|
+
hmac_key = Primitives.derive_sender_key(master_key);
|
61
|
+
expect(hmac_key.size).to eq(32)
|
62
|
+
expect(hmac_key).to eq('327b5f32d7ff0beeb0a7224166186e5f1fc2ba681092214a25b1465d1f17d837'.hex_to_bin);
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'derive_receiver_hmac' do
|
67
|
+
it 'should require a buffer' do
|
68
|
+
buf = "32".hex_to_bin(32)
|
69
|
+
expect { Primitives.derive_receiver_hmac('') }.to raise_error(/binary string required/i)
|
70
|
+
expect { Primitives.derive_receiver_hmac(buf) }.to_not raise_error
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should require a 256-bit key' do
|
74
|
+
short = "33".hex_to_bin(31)
|
75
|
+
exact = "33".hex_to_bin(32)
|
76
|
+
long = "33".hex_to_bin(33)
|
77
|
+
|
78
|
+
expect { Primitives.derive_receiver_hmac(short) }.to raise_error(/256-bit/i)
|
79
|
+
expect(Primitives.derive_receiver_hmac(exact).encoding).to eq(Encoding::ASCII_8BIT)
|
80
|
+
expect { Primitives.derive_receiver_hmac(long) }.to raise_error(/256-bit/i)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should derive a 256-bit hmac key from a 256-bit master key' do
|
84
|
+
master_key = 'bc'.hex_to_bin(32)
|
85
|
+
|
86
|
+
hmac_key = Primitives.derive_receiver_hmac(master_key);
|
87
|
+
expect(hmac_key.size).to eq(32)
|
88
|
+
expect(hmac_key).to eq('375f52dff2a263f2d0e0df11d252d25ba18b2f9abae1f0cbf299bab8d8c4904d'.hex_to_bin);
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe 'derive_receiver_key' do
|
93
|
+
it 'should require a buffer' do
|
94
|
+
buf = "32".hex_to_bin(32)
|
95
|
+
expect { Primitives.derive_receiver_key('') }.to raise_error(/binary string required/i)
|
96
|
+
expect { Primitives.derive_receiver_key(buf) }.to_not raise_error
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should require a 256-bit key' do
|
100
|
+
short = "33".hex_to_bin(31)
|
101
|
+
exact = "33".hex_to_bin(32)
|
102
|
+
long = "33".hex_to_bin(33)
|
103
|
+
|
104
|
+
expect { Primitives.derive_receiver_key(short) }.to raise_error(/256-bit/i)
|
105
|
+
expect(Primitives.derive_receiver_key(exact).encoding).to eq(Encoding::ASCII_8BIT)
|
106
|
+
expect { Primitives.derive_receiver_key(long) }.to raise_error(/256-bit/i)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should derive a 256-bit encryption key from a 256-bit master key' do
|
110
|
+
master_key = 'bc'.hex_to_bin(32)
|
111
|
+
|
112
|
+
hmac_key = Primitives.derive_receiver_key(master_key);
|
113
|
+
expect(hmac_key.size).to eq(32)
|
114
|
+
expect(hmac_key).to eq('c7e2a9660369f243aed71b0de0c49ee69719d20261778fdf39991a456566ef22'.hex_to_bin);
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe 'encrypt' do
|
119
|
+
it 'should require a buffer' do
|
120
|
+
buf = "32".hex_to_bin(32)
|
121
|
+
expect { Primitives.encrypt('', '') }.to raise_error(/binary string required/i)
|
122
|
+
expect { Primitives.encrypt('', buf) }.to raise_error(/binary string required/i)
|
123
|
+
expect { Primitives.encrypt(buf, '') }.to raise_error(/binary string required/i)
|
124
|
+
expect { Primitives.encrypt(buf, buf) }.to_not raise_error
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should encrypt data using a 256-bit key' do
|
128
|
+
key = 'cd'.hex_to_bin(32)
|
129
|
+
data = '11'.hex_to_bin(25)
|
130
|
+
|
131
|
+
binmessage = Primitives.encrypt(data, key)
|
132
|
+
iv = binmessage[0...16]
|
133
|
+
ciphertext = binmessage[16..-1]
|
134
|
+
|
135
|
+
expect(iv.size).to eq(16)
|
136
|
+
expect(ciphertext.size).to eq(32)
|
137
|
+
recovered = Primitives.decrypt(ciphertext, key, iv)
|
138
|
+
expect(recovered).to eq(data)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'should return a Buffer of (iv || ciphertext)' do
|
142
|
+
key = 'cd'.hex_to_bin(32)
|
143
|
+
data = '11'.hex_to_bin(25)
|
144
|
+
|
145
|
+
output = Primitives.encrypt(data, key)
|
146
|
+
expect(output.encoding).to eq(Encoding::ASCII_8BIT)
|
147
|
+
# 16-byte IV, 32 bytes to encrypt the 25 data bytes
|
148
|
+
expect(output.size).to eq(48)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe 'decrypt' do
|
153
|
+
it 'should require a buffer' do
|
154
|
+
buf = "32".hex_to_bin(32)
|
155
|
+
expect { Primitives.decrypt('', '', '') }.to raise_error(/binary string required/i)
|
156
|
+
expect { Primitives.decrypt('', '', buf) }.to raise_error(/binary string required/i)
|
157
|
+
expect { Primitives.decrypt('', buf, '') }.to raise_error(/binary string required/i)
|
158
|
+
expect { Primitives.decrypt(buf, '', '') }.to raise_error(/binary string required/i)
|
159
|
+
expect { Primitives.decrypt(buf, buf, '') }.to raise_error(/binary string required/i)
|
160
|
+
expect { Primitives.decrypt(buf, '', buf) }.to raise_error(/binary string required/i)
|
161
|
+
expect { Primitives.decrypt('', buf, buf) }.to raise_error(/binary string required/i)
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should decrypt data using a 256-bit key' do
|
165
|
+
key = "cd".hex_to_bin(32)
|
166
|
+
plaintext = "11".hex_to_bin(25)
|
167
|
+
iv = 'd4a5794c81015dde3b9b0648f2b9f5b9'.hex_to_bin
|
168
|
+
ciphertext = 'cb7f804ec83617144aa261f24af07023a91a3864601a666edea98938f2702dbc'.hex_to_bin
|
169
|
+
|
170
|
+
recovered = Primitives.decrypt(ciphertext, key, iv)
|
171
|
+
expect(recovered).to eq(plaintext)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe 'identify' do
|
176
|
+
it 'should require a buffer' do
|
177
|
+
buf = "32".hex_to_bin(10)
|
178
|
+
expect { Primitives.identify('') }.to raise_error(/binary string required/i)
|
179
|
+
expect { Primitives.identify(buf) }.to_not raise_error
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'should calculate an id for a key' do
|
183
|
+
key = "ab".hex_to_bin(32)
|
184
|
+
id = Primitives.identify(key)
|
185
|
+
|
186
|
+
expect(id.size).to eq(6)
|
187
|
+
expect(id).to eq('0d081b0889d7'.hex_to_bin)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe 'mac' do
|
192
|
+
it 'should create a message authentication code' do
|
193
|
+
key = "9f".hex_to_bin(32)
|
194
|
+
data = "11".hex_to_bin(25)
|
195
|
+
mac = Primitives.mac(data, key)
|
196
|
+
|
197
|
+
expect(mac.size).to eq(32)
|
198
|
+
expect(mac).to eq('adf1793fdef44c54a2c01513c0c7e4e71411600410edbde61558db12d0a01c65'.hex_to_bin)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
describe 'compare' do
|
203
|
+
it 'should require a buffer' do
|
204
|
+
buf = "32".hex_to_bin(10)
|
205
|
+
expect { Primitives.compare('', '') }.to raise_error(/binary string required/i)
|
206
|
+
expect { Primitives.compare('', buf) }.to raise_error(/binary string required/i)
|
207
|
+
expect { Primitives.compare(buf, '') }.to raise_error(/binary string required/i)
|
208
|
+
expect { Primitives.compare(buf, buf) }.to_not raise_error
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'should correctly distinguish data equality' do
|
212
|
+
a = "11".hex_to_bin
|
213
|
+
b = "12".hex_to_bin
|
214
|
+
c = "11".hex_to_bin
|
215
|
+
|
216
|
+
expect(Primitives.compare(a,a)).to be_true
|
217
|
+
expect(Primitives.compare(a,b)).to be_false
|
218
|
+
expect(Primitives.compare(a,c)).to be_true
|
219
|
+
end
|
220
|
+
|
221
|
+
# Timing test, in progress from node.js version
|
222
|
+
# # (node.js) This works fine locally, but has tons of variation on build server
|
223
|
+
# it 'should take just as long to compare different data as identical data', :skip => true do
|
224
|
+
# a = "ff".hex_to_bin(250000)
|
225
|
+
# b = "00".hex_to_bin(250000)
|
226
|
+
# c = "ff".hex_to_bin(250000)
|
227
|
+
#
|
228
|
+
# benchAA = benchmark(primitives.compare, a, a)
|
229
|
+
# benchAB = benchmark(primitives.compare, a, b)
|
230
|
+
# benchAC = benchmark(primitives.compare, a, c)
|
231
|
+
#
|
232
|
+
# naiveAA = benchmark(naiveEquals, a, a)
|
233
|
+
# naiveAB = benchmark(naiveEquals, a, b)
|
234
|
+
# naiveAC = benchmark(naiveEquals, a, c)
|
235
|
+
#
|
236
|
+
# # All constant-time comparisons should be roughly equal in time
|
237
|
+
# expect(difference(benchAA, benchAB)).to.be.greaterThan(0.95)
|
238
|
+
# expect(difference(benchAA, benchAC)).to.be.greaterThan(0.95)
|
239
|
+
# expect(difference(benchAB, benchAC)).to.be.greaterThan(0.95)
|
240
|
+
#
|
241
|
+
# # Naive comparisons of the same item with itself, or with obviously
|
242
|
+
# # different items should be ridiculously fast
|
243
|
+
# expect(difference(benchAA, naiveAA)).to.be.lessThan(0.01)
|
244
|
+
# expect(difference(benchAB, naiveAB)).to.be.lessThan(0.01)
|
245
|
+
#
|
246
|
+
# # It should take just about as long to compare identical arrays as the constant time compare
|
247
|
+
# expect(difference(benchAC, naiveAC)).to.be.greaterThan(0.90)
|
248
|
+
#
|
249
|
+
# function naiveEquals(a, b) {
|
250
|
+
# if (a === b) return true;
|
251
|
+
# for (var i = 0; i < a.length; i++) {
|
252
|
+
# if (a[i] !== b[i]) {
|
253
|
+
# return false;
|
254
|
+
# }
|
255
|
+
# }
|
256
|
+
# return true;
|
257
|
+
# }
|
258
|
+
#
|
259
|
+
# function benchmark(fn, a, b) {
|
260
|
+
# var time = process.hrtime();
|
261
|
+
# for (var i = 0; i < 100; i++) {
|
262
|
+
# fn(a, b);
|
263
|
+
# };
|
264
|
+
# var diff = process.hrtime(time);
|
265
|
+
# return diff[0] * 1e9 + diff[1];
|
266
|
+
# }
|
267
|
+
#
|
268
|
+
# function difference(first, second) {
|
269
|
+
# var smaller = Math.min(first, second);
|
270
|
+
# var larger = Math.max(first, second);
|
271
|
+
# return (smaller / larger);
|
272
|
+
# }
|
273
|
+
#
|
274
|
+
# end
|
275
|
+
end
|
276
|
+
|
277
|
+
describe 'binify' do
|
278
|
+
it 'should require a base64url string' do
|
279
|
+
expect { Primitives.binify(123) }.to raise_error(/string required/i)
|
280
|
+
expect { Primitives.binify('arstnei; another.') }.to raise_error(/base64url/i)
|
281
|
+
expect { Primitives.binify('cartinir90_-') }.to_not raise_error
|
282
|
+
end
|
283
|
+
|
284
|
+
it 'should return a Buffer' do
|
285
|
+
bin = Primitives.binify('abcd')
|
286
|
+
expect(bin.encoding).to eq(Encoding::ASCII_8BIT)
|
287
|
+
expect(bin.size).to eq(3)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe 'stringify' do
|
292
|
+
it 'should require a buffer' do
|
293
|
+
buf = "32".hex_to_bin(10)
|
294
|
+
expect { Primitives.stringify('') }.to raise_error(/binary string required/i)
|
295
|
+
expect { Primitives.stringify(buf) }.to_not raise_error
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'should return a base64url string' do
|
299
|
+
buf = "32".hex_to_bin(10)
|
300
|
+
str = Primitives.stringify(buf)
|
301
|
+
expect(str.encoding).to eq(Encoding::US_ASCII)
|
302
|
+
expect(str.size).to eq(14)
|
303
|
+
expect(str).to match(/^[a-zA-Z0-9_-]+$/)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
describe 'serialize' do
|
308
|
+
it 'should accept javascript object' do
|
309
|
+
expect { Primitives.serialize(1) }.to_not raise_error
|
310
|
+
expect { Primitives.serialize('a') }.to_not raise_error
|
311
|
+
expect { Primitives.serialize([]) }.to_not raise_error
|
312
|
+
expect { Primitives.serialize({}) }.to_not raise_error
|
313
|
+
end
|
314
|
+
|
315
|
+
it 'should return a Buffer' do
|
316
|
+
bin = Primitives.serialize('abcd')
|
317
|
+
expect(bin.encoding).to eq(Encoding::ASCII_8BIT)
|
318
|
+
expect(bin.size).to eq(5)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
describe 'deserialize' do
|
323
|
+
it 'should require a buffer' do
|
324
|
+
buf = "32".hex_to_bin(10)
|
325
|
+
expect { Primitives.deserialize('') }.to raise_error(/binary string required/i)
|
326
|
+
end
|
327
|
+
|
328
|
+
it 'should return a javascript primitive or object' do
|
329
|
+
expect(Primitives.deserialize(Primitives.serialize(1))).to eq(1)
|
330
|
+
expect(Primitives.deserialize(Primitives.serialize('abcd'))).to eq('abcd')
|
331
|
+
expect(Primitives.deserialize(Primitives.serialize([]))).to eq([])
|
332
|
+
expect(Primitives.deserialize(Primitives.serialize({}))).to eq({})
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
describe 'zero' do
|
337
|
+
it 'should require a Buffer' do
|
338
|
+
expect { Primitives.zero({}) }.to raise_error(/binary string required/i)
|
339
|
+
end
|
340
|
+
|
341
|
+
it 'should overwrite all buffer contents with zeros' do
|
342
|
+
b = '7468697320697320736f6d65'.hex_to_bin
|
343
|
+
z = '000000000000000000000000'.hex_to_bin
|
344
|
+
|
345
|
+
expect(b).to_not eq(z)
|
346
|
+
Primitives.zero(b)
|
347
|
+
expect(b).to eq(z)
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'should zero multiple buffers' do
|
351
|
+
b = '7468697320697320736f6d65'.hex_to_bin
|
352
|
+
c = '697320736f6d657468697320'.hex_to_bin
|
353
|
+
z = '000000000000000000000000'.hex_to_bin
|
354
|
+
|
355
|
+
expect(b).to_not eq(z)
|
356
|
+
expect(c).to_not eq(z)
|
357
|
+
Primitives.zero(b, c)
|
358
|
+
expect(b).to eq(z)
|
359
|
+
expect(c).to eq(z)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple-secrets
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tim Shadel
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: msgpack
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.3'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.3'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: A Ruby client for simple-secrets, the simple, opinionated library for
|
79
|
+
encrypting small packets of data securely.
|
80
|
+
email:
|
81
|
+
- tim@shadelsoftware.com
|
82
|
+
executables: []
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- .rspec
|
88
|
+
- .travis.yml
|
89
|
+
- Gemfile
|
90
|
+
- LICENSE.txt
|
91
|
+
- README.md
|
92
|
+
- Rakefile
|
93
|
+
- lib/simple-secrets.rb
|
94
|
+
- lib/simple_secrets.rb
|
95
|
+
- lib/simple_secrets/packet.rb
|
96
|
+
- lib/simple_secrets/primitives.rb
|
97
|
+
- lib/simple_secrets/version.rb
|
98
|
+
- simple_secrets.gemspec
|
99
|
+
- spec/packet_spec.rb
|
100
|
+
- spec/primitives_spec.rb
|
101
|
+
- spec/simple_secrets_spec.rb
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
homepage: https://github.com/timshadel/simple-secrets.rb
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ! '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 1.8.23
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: A Ruby client for simple-secrets, the simple, opinionated library for encrypting
|
128
|
+
small packets of data securely.
|
129
|
+
test_files:
|
130
|
+
- spec/packet_spec.rb
|
131
|
+
- spec/primitives_spec.rb
|
132
|
+
- spec/simple_secrets_spec.rb
|
133
|
+
- spec/spec_helper.rb
|