rbnacl 1.0.0.pre
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/.coveralls.yml +1 -0
- data/.gitignore +19 -0
- data/.rspec +4 -0
- data/.travis.yml +21 -0
- data/.yardopts +1 -0
- data/CHANGES.md +4 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +23 -0
- data/README.md +179 -0
- data/Rakefile +5 -0
- data/images/dragons.png +0 -0
- data/images/ed25519.png +0 -0
- data/images/logo.png +0 -0
- data/lib/rbnacl.rb +46 -0
- data/lib/rbnacl/auth.rb +78 -0
- data/lib/rbnacl/auth/one_time.rb +38 -0
- data/lib/rbnacl/box.rb +141 -0
- data/lib/rbnacl/encoder.rb +44 -0
- data/lib/rbnacl/encoders/base32.rb +33 -0
- data/lib/rbnacl/encoders/base64.rb +30 -0
- data/lib/rbnacl/encoders/hex.rb +30 -0
- data/lib/rbnacl/encoders/raw.rb +12 -0
- data/lib/rbnacl/hash.rb +48 -0
- data/lib/rbnacl/hmac/sha256.rb +32 -0
- data/lib/rbnacl/hmac/sha512256.rb +35 -0
- data/lib/rbnacl/keys/key_comparator.rb +59 -0
- data/lib/rbnacl/keys/private_key.rb +62 -0
- data/lib/rbnacl/keys/public_key.rb +38 -0
- data/lib/rbnacl/keys/signing_key.rb +74 -0
- data/lib/rbnacl/keys/verify_key.rb +76 -0
- data/lib/rbnacl/nacl.rb +132 -0
- data/lib/rbnacl/point.rb +67 -0
- data/lib/rbnacl/rake_tasks.rb +56 -0
- data/lib/rbnacl/random.rb +19 -0
- data/lib/rbnacl/random_nonce_box.rb +109 -0
- data/lib/rbnacl/secret_box.rb +86 -0
- data/lib/rbnacl/self_test.rb +118 -0
- data/lib/rbnacl/serializable.rb +23 -0
- data/lib/rbnacl/test_vectors.rb +69 -0
- data/lib/rbnacl/util.rb +137 -0
- data/lib/rbnacl/version.rb +5 -0
- data/rbnacl.gemspec +28 -0
- data/rbnacl.gpg +30 -0
- data/spec/rbnacl/auth/one_time_spec.rb +8 -0
- data/spec/rbnacl/box_spec.rb +42 -0
- data/spec/rbnacl/encoder_spec.rb +14 -0
- data/spec/rbnacl/encoders/base32_spec.rb +16 -0
- data/spec/rbnacl/encoders/base64_spec.rb +15 -0
- data/spec/rbnacl/encoders/hex_spec.rb +15 -0
- data/spec/rbnacl/hash_spec.rb +52 -0
- data/spec/rbnacl/hmac/sha256_spec.rb +8 -0
- data/spec/rbnacl/hmac/sha512256_spec.rb +8 -0
- data/spec/rbnacl/keys/private_key_spec.rb +68 -0
- data/spec/rbnacl/keys/public_key_spec.rb +45 -0
- data/spec/rbnacl/keys/signing_key_spec.rb +40 -0
- data/spec/rbnacl/keys/verify_key_spec.rb +51 -0
- data/spec/rbnacl/point_spec.rb +29 -0
- data/spec/rbnacl/random_nonce_box_spec.rb +78 -0
- data/spec/rbnacl/random_spec.rb +9 -0
- data/spec/rbnacl/secret_box_spec.rb +24 -0
- data/spec/rbnacl/util_spec.rb +119 -0
- data/spec/shared/authenticator.rb +114 -0
- data/spec/shared/box.rb +51 -0
- data/spec/shared/key_equality.rb +26 -0
- data/spec/spec_helper.rb +14 -0
- data/tasks/ci.rake +11 -0
- data/tasks/rspec.rake +7 -0
- metadata +187 -0
data/lib/rbnacl/box.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
module Crypto
|
3
|
+
# The Box class boxes and unboxes messages between a pair of keys
|
4
|
+
#
|
5
|
+
# This class uses the given public and secret keys to derive a shared key,
|
6
|
+
# which is used with the nonce given to encrypt the given messages and
|
7
|
+
# decrypt the given ciphertexts. The same shared key will generated from
|
8
|
+
# both pairing of keys, so given two keypairs belonging to alice (pkalice,
|
9
|
+
# skalice) and bob(pkbob, skbob), the key derived from (pkalice, skbob) with
|
10
|
+
# equal that from (pkbob, skalice). This is how the system works:
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # On bob's system
|
14
|
+
# bobkey = Crypto::PrivateKey.generate
|
15
|
+
# #=> #<Crypto::PrivateKey ...>
|
16
|
+
#
|
17
|
+
# # send bobkey.public_key to alice
|
18
|
+
# # recieve alice's public key, alicepk
|
19
|
+
# # NB: This is actually the hard part of the system. How to do it securely
|
20
|
+
# # is left as an exercise to for the reader.
|
21
|
+
# alice_pubkey = "..."
|
22
|
+
#
|
23
|
+
# # make a box
|
24
|
+
# alicebob_box = Crypto::Box.new(alice_pubkey, bobkey)
|
25
|
+
# #=> #<Crypto::Box ...>
|
26
|
+
#
|
27
|
+
# # encrypt a message to alice
|
28
|
+
# cipher_text = alicebob_box.box("A bad example of a nonce", "Hello, Alice!")
|
29
|
+
# #=> "..." # a string of bytes, 29 bytes long
|
30
|
+
#
|
31
|
+
# # send ["A bad example of a nonce", cipher_text] to alice
|
32
|
+
# # note that nonces don't have to be secret
|
33
|
+
# # receive [nonce, cipher_text_to_bob] from alice
|
34
|
+
#
|
35
|
+
# # decrypt the reply
|
36
|
+
# # Alice has been a little more sensible than bob, and has a random nonce
|
37
|
+
# # that is too fiddly to type here. But there are other choices than just
|
38
|
+
# # random
|
39
|
+
# plain_text = alicebob_box.open(nonce, cipher_text_to_bob)
|
40
|
+
# #=> "Hey there, Bob!"
|
41
|
+
#
|
42
|
+
# # we have a new message!
|
43
|
+
# # But Eve has tampered with this message, by flipping some bytes around!
|
44
|
+
# # [nonce2, cipher_text_to_bob_honest_love_eve]
|
45
|
+
# alicebob_box.open(nonce2, cipher_text_to_bob_honest_love_eve)
|
46
|
+
#
|
47
|
+
# # BOOM!
|
48
|
+
# # Bob gets a Crypto::CryptoError to deal with!
|
49
|
+
#
|
50
|
+
# It is VITALLY important that the nonce is a nonce, i.e. it is a number used
|
51
|
+
# only once for any given pair of keys. If you fail to do this, you
|
52
|
+
# compromise the privacy of the the messages encrypted. Also, bear in mind
|
53
|
+
# the property mentioned just above. Give your nonces a different prefix, or
|
54
|
+
# have one side use an odd counter and one an even counter. Just make sure
|
55
|
+
# they are different.
|
56
|
+
#
|
57
|
+
# The ciphertexts generated by this class include a 16-byte authenticator which
|
58
|
+
# is checked as part of the decryption. An invalid authenticator will cause
|
59
|
+
# the unbox function to raise. The authenticator is not a signature. Once
|
60
|
+
# you've looked in the box, you've demonstrated the ability to create
|
61
|
+
# arbitrary valid messages, so messages you send are repudiatable. For
|
62
|
+
# non-repudiatable messages, sign them before or after encryption.
|
63
|
+
class Box
|
64
|
+
|
65
|
+
# Create a new Box
|
66
|
+
#
|
67
|
+
# Sets up the Box for deriving the shared key and encrypting and
|
68
|
+
# decrypting messages.
|
69
|
+
#
|
70
|
+
# @param public_key [String,Crypto::PublicKey] The public key to encrypt to
|
71
|
+
# @param private_key [String,Crypto::PrivateKey] The private key to encrypt with
|
72
|
+
# @param encoding [Symbol] Parse keys from the given encoding
|
73
|
+
#
|
74
|
+
# @raise [Crypto::LengthError] on invalid keys
|
75
|
+
#
|
76
|
+
# @return [Crypto::Box] The new Box, ready to use
|
77
|
+
def initialize(public_key, private_key, encoding = :raw)
|
78
|
+
@public_key = Encoder[encoding].decode(public_key) if public_key
|
79
|
+
@private_key = Encoder[encoding].decode(private_key) if private_key
|
80
|
+
Util.check_length(@public_key, PublicKey::BYTES, "Public key")
|
81
|
+
Util.check_length(@private_key, PrivateKey::BYTES, "Private key")
|
82
|
+
end
|
83
|
+
|
84
|
+
# Encrypts a message
|
85
|
+
#
|
86
|
+
# Encrypts the message with the given nonce to the keypair set up when
|
87
|
+
# initializing the class. Make sure the nonce is unique for any given
|
88
|
+
# keypair, or you might as well just send plain text.
|
89
|
+
#
|
90
|
+
# This function takes care of the padding required by the NaCL C API.
|
91
|
+
#
|
92
|
+
# @param nonce [String] A 24-byte string containing the nonce.
|
93
|
+
# @param message [String] The message to be encrypted.
|
94
|
+
#
|
95
|
+
# @raise [Crypto::LengthError] If the nonce is not valid
|
96
|
+
#
|
97
|
+
# @return [String] The ciphertext without the nonce prepended (BINARY encoded)
|
98
|
+
def box(nonce, message)
|
99
|
+
Util.check_length(nonce, Crypto::NaCl::NONCEBYTES, "Nonce")
|
100
|
+
msg = Util.prepend_zeros(NaCl::ZEROBYTES, message)
|
101
|
+
ct = Util.zeros(msg.bytesize)
|
102
|
+
|
103
|
+
NaCl.crypto_box_afternm(ct, msg, msg.bytesize, nonce, beforenm) || raise(CryptoError, "Encryption failed")
|
104
|
+
Util.remove_zeros(NaCl::BOXZEROBYTES, ct)
|
105
|
+
end
|
106
|
+
alias encrypt box
|
107
|
+
|
108
|
+
# Decrypts a ciphertext
|
109
|
+
#
|
110
|
+
# Decrypts the ciphertext with the given nonce using the keypair setup when
|
111
|
+
# initializing the class.
|
112
|
+
#
|
113
|
+
# This function takes care of the padding required by the NaCL C API.
|
114
|
+
#
|
115
|
+
# @param nonce [String] A 24-byte string containing the nonce.
|
116
|
+
# @param ciphertext [String] The message to be decrypted.
|
117
|
+
#
|
118
|
+
# @raise [Crypto::LengthError] If the nonce is not valid
|
119
|
+
# @raise [Crypto::CryptoError] If the ciphertext cannot be authenticated.
|
120
|
+
#
|
121
|
+
# @return [String] The decrypted message (BINARY encoded)
|
122
|
+
def open(nonce, ciphertext)
|
123
|
+
Util.check_length(nonce, Crypto::NaCl::NONCEBYTES, "Nonce")
|
124
|
+
ct = Util.prepend_zeros(NaCl::BOXZEROBYTES, ciphertext)
|
125
|
+
message = Util.zeros(ct.bytesize)
|
126
|
+
|
127
|
+
NaCl.crypto_box_open_afternm(message, ct, ct.bytesize, nonce, beforenm) || raise(CryptoError, "Decryption failed. Ciphertext failed verification.")
|
128
|
+
Util.remove_zeros(NaCl::ZEROBYTES, message)
|
129
|
+
end
|
130
|
+
alias decrypt open
|
131
|
+
|
132
|
+
private
|
133
|
+
def beforenm
|
134
|
+
@k ||= begin
|
135
|
+
k = Util.zeros(NaCl::BEFORENMBYTES)
|
136
|
+
NaCl.crypto_box_beforenm(k, @public_key, @private_key) || raise(CryptoError, "Failed to derive shared key")
|
137
|
+
k
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
module Crypto
|
3
|
+
# Encoders can be used to serialize or deserialize keys, ciphertexts, hashes,
|
4
|
+
# and signatures. To provide an encoder, simply subclass Encoder and call the
|
5
|
+
# register class method, then define the encode and decode methods:
|
6
|
+
#
|
7
|
+
# class CrazysauceEncoder < Crypto::Encoder
|
8
|
+
# register :crazysauce
|
9
|
+
#
|
10
|
+
# def encode(string)
|
11
|
+
# ...
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# def decode(string)
|
15
|
+
# ...
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# Once an encoder has been registered, an instance of it is available via
|
20
|
+
# calling Crypto::Encoder[], e.g. Crypto::Encoder[:hex].encode("foobar")
|
21
|
+
#
|
22
|
+
class Encoder
|
23
|
+
# Hash where encoder objects are stored
|
24
|
+
Registry = {}
|
25
|
+
|
26
|
+
# Register the current class as an encoder
|
27
|
+
def self.register(name)
|
28
|
+
self[name] = self.new
|
29
|
+
end
|
30
|
+
|
31
|
+
# Look up an encoder by the given name
|
32
|
+
def self.[](name)
|
33
|
+
Registry[name.to_sym] or raise ArgumentError, "unsupported encoder: #{name}"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Register an encoder object directly
|
37
|
+
def self.[]=(name, obj)
|
38
|
+
Registry[name.to_sym] = obj
|
39
|
+
end
|
40
|
+
|
41
|
+
def encode(string); raise NotImplementedError, "encoding not implemented"; end
|
42
|
+
def decode(string); raise NotImplementedError, "decoding not implemented"; end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
# Requires the base32 gem
|
3
|
+
require 'base32'
|
4
|
+
|
5
|
+
module Crypto
|
6
|
+
module Encoders
|
7
|
+
# Base64 encoding provider
|
8
|
+
#
|
9
|
+
# Accessable as Crypto::Encoder[:base64]
|
10
|
+
#
|
11
|
+
class Base32 < Crypto::Encoder
|
12
|
+
register :base32
|
13
|
+
|
14
|
+
# Base64 encodes a message
|
15
|
+
#
|
16
|
+
# @param [String] bytes The bytes to encode
|
17
|
+
#
|
18
|
+
# @return [String] Lovely, elegant "Zooko-style" Base32
|
19
|
+
def encode(bytes)
|
20
|
+
::Base32.encode(bytes.to_s).downcase
|
21
|
+
end
|
22
|
+
|
23
|
+
# Hex decodes a message
|
24
|
+
#
|
25
|
+
# @param [String] base32 string to decode.
|
26
|
+
#
|
27
|
+
# @return [String] crisp and clean bytes
|
28
|
+
def decode(base32)
|
29
|
+
::Base32.decode(base32.to_s.upcase)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
module Crypto
|
3
|
+
module Encoders
|
4
|
+
# Base64 encoding provider
|
5
|
+
#
|
6
|
+
# Accessable as Crypto::Encoder[:base64]
|
7
|
+
#
|
8
|
+
class Base64 < Crypto::Encoder
|
9
|
+
register :base64
|
10
|
+
|
11
|
+
# Base64 encodes a message
|
12
|
+
#
|
13
|
+
# @param [String] bytes The bytes to encode
|
14
|
+
#
|
15
|
+
# @return [String] Clunky old base64
|
16
|
+
def encode(bytes)
|
17
|
+
[bytes.to_s].pack("m").gsub("\n", '')
|
18
|
+
end
|
19
|
+
|
20
|
+
# Hex decodes a message
|
21
|
+
#
|
22
|
+
# @param [String] base64 string to decode.
|
23
|
+
#
|
24
|
+
# @return [String] crisp and clean bytes
|
25
|
+
def decode(base64)
|
26
|
+
base64.to_s.unpack("m").first
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
module Crypto
|
3
|
+
module Encoders
|
4
|
+
# Hex encoding provider
|
5
|
+
#
|
6
|
+
# Accessable as Crypto::Encoder[:hex]
|
7
|
+
#
|
8
|
+
class Hex < Crypto::Encoder
|
9
|
+
register :hex
|
10
|
+
|
11
|
+
# Hex encodes a message
|
12
|
+
#
|
13
|
+
# @param [String] bytes The bytes to encode
|
14
|
+
#
|
15
|
+
# @return [String] Tasty, tasty hexidecimal
|
16
|
+
def encode(bytes)
|
17
|
+
bytes.to_s.unpack("H*").first
|
18
|
+
end
|
19
|
+
|
20
|
+
# Hex decodes a message
|
21
|
+
#
|
22
|
+
# @param [String] hex hex to decode.
|
23
|
+
#
|
24
|
+
# @return [String] crisp and clean bytes
|
25
|
+
def decode(hex)
|
26
|
+
[hex.to_s].pack("H*")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
module Crypto
|
3
|
+
module Encoders
|
4
|
+
# Raw encoder which only does a string conversion (if necessary)
|
5
|
+
class Raw < Crypto::Encoder
|
6
|
+
register :raw
|
7
|
+
|
8
|
+
def encode(bytes); bytes.to_s; end
|
9
|
+
def decode(bytes); bytes.to_s; end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/rbnacl/hash.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
module Crypto
|
3
|
+
# Cryptographic hash functions
|
4
|
+
#
|
5
|
+
# Cryptographic hash functions take a variable length message and compute a
|
6
|
+
# fixed length string, the message digest. Even a small change in the input
|
7
|
+
# data should produce a large change in the digest, and it is 'very difficult'
|
8
|
+
# to create two messages with the same digest.
|
9
|
+
#
|
10
|
+
# A cryptographic hash can be used for checking the integrity of data, but
|
11
|
+
# there is no secret involved in the hashing, so anyone can create the hash of
|
12
|
+
# a given message.
|
13
|
+
#
|
14
|
+
# RbNaCl provides the SHA-256 and SHA-512 hash functions.
|
15
|
+
module Hash
|
16
|
+
# Returns the SHA-256 hash of the given data
|
17
|
+
#
|
18
|
+
# There's no streaming done, just pass in the data and be done with it.
|
19
|
+
#
|
20
|
+
# @param [String] data The data, as a collection of bytes
|
21
|
+
# @param [#to_sym] encoding Encoding of the returned hash.
|
22
|
+
#
|
23
|
+
# @raise [CryptoError] If the hashing fails for some reason.
|
24
|
+
#
|
25
|
+
# @return [String] The SHA-256 hash as raw bytes (Or encoded as per the second argument)
|
26
|
+
def self.sha256(data, encoding = :raw)
|
27
|
+
hash = Util.zeros(NaCl::SHA256BYTES)
|
28
|
+
NaCl.crypto_hash_sha256(hash, data, data.bytesize) || raise(CryptoError, "Hashing failed!")
|
29
|
+
Encoder[encoding].encode(hash)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the SHA-512 hash of the given data
|
33
|
+
#
|
34
|
+
# There's no streaming done, just pass in the data and be done with it.
|
35
|
+
#
|
36
|
+
# @param [String] data The data, as a collection of bytes
|
37
|
+
# @param [#to_sym] encoding Encoding of the returned hash.
|
38
|
+
#
|
39
|
+
# @raise [CryptoError] If the hashing fails for some reason.
|
40
|
+
#
|
41
|
+
# @return [String] The SHA-512 hash as raw bytes (Or encoded as per the second argument)
|
42
|
+
def self.sha512(data, encoding = :raw)
|
43
|
+
hash = Util.zeros(NaCl::SHA512BYTES)
|
44
|
+
NaCl.crypto_hash_sha512(hash, data, data.bytesize) || raise(CryptoError, "Hashing failed!")
|
45
|
+
Encoder[encoding].encode(hash)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
module Crypto
|
3
|
+
module HMAC
|
4
|
+
# Computes an authenticator as HMAC-SHA-256
|
5
|
+
#
|
6
|
+
# The authenticator can be used at a later time to verify the provenence of
|
7
|
+
# the message by recomputing the HMAC over the message and then comparing it to
|
8
|
+
# the provided authenticator. The class provides methods for generating
|
9
|
+
# signatures and also has a constant-time implementation for checking them.
|
10
|
+
#
|
11
|
+
# This is a secret key authenticator, i.e. anyone who can verify signatures
|
12
|
+
# can also create them.
|
13
|
+
#
|
14
|
+
# @see http://nacl.cr.yp.to/auth.html
|
15
|
+
class SHA256 < Auth
|
16
|
+
# Number of bytes in a valid key
|
17
|
+
KEYBYTES = NaCl::HMACSHA256_KEYBYTES
|
18
|
+
|
19
|
+
# Number of bytes in a valid authenticator
|
20
|
+
BYTES = NaCl::HMACSHA256_BYTES
|
21
|
+
|
22
|
+
private
|
23
|
+
def compute_authenticator(message, authenticator)
|
24
|
+
NaCl.crypto_auth_hmacsha256(authenticator, message, message.bytesize, key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def verify_message(message, authenticator)
|
28
|
+
NaCl.crypto_auth_hmacsha256_verify(authenticator, message, message.bytesize, key)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
module Crypto
|
3
|
+
module HMAC
|
4
|
+
# Computes an authenticator as HMAC-SHA-512 truncated to 256-bits
|
5
|
+
#
|
6
|
+
# The authenticator can be used at a later time to verify the provenence of
|
7
|
+
# the message by recomputing the HMAC over the message and then comparing it to
|
8
|
+
# the provided authenticator. The class provides methods for generating
|
9
|
+
# signatures and also has a constant-time implementation for checking them.
|
10
|
+
#
|
11
|
+
# This is a secret key authenticator, i.e. anyone who can verify signatures
|
12
|
+
# can also create them.
|
13
|
+
#
|
14
|
+
# @see http://nacl.cr.yp.to/auth.html
|
15
|
+
class SHA512256 < Auth
|
16
|
+
# Number of bytes in a valid key
|
17
|
+
KEYBYTES = NaCl::HMACSHA512256_KEYBYTES
|
18
|
+
|
19
|
+
# Number of bytes in a valid authenticator
|
20
|
+
BYTES = NaCl::HMACSHA512256_BYTES
|
21
|
+
|
22
|
+
private
|
23
|
+
def compute_authenticator(message, authenticator)
|
24
|
+
NaCl.crypto_auth_hmacsha512256(authenticator, message, message.bytesize, key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def verify_message(message, authenticator)
|
28
|
+
NaCl.crypto_auth_hmacsha512256_verify(authenticator, message, message.bytesize, key)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# TIMTOWTDI!
|
33
|
+
SHA512 = SHA512256
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
module Crypto
|
3
|
+
# Implements comparisons of keys
|
4
|
+
#
|
5
|
+
# This permits both timing invariant equality tests, as well as
|
6
|
+
# lexicographical sorting.
|
7
|
+
module KeyComparator
|
8
|
+
include Comparable
|
9
|
+
# spaceship operator
|
10
|
+
#
|
11
|
+
# @param other [KeyComparator,#to_str] The thing to compare
|
12
|
+
#
|
13
|
+
# @return [0] if the keys are equal
|
14
|
+
# @return [1] if the key is larger than the other key
|
15
|
+
# @return [-1] if the key is smaller than the other key
|
16
|
+
# @return [nil] if comparison doesn't make sense
|
17
|
+
def <=>(other)
|
18
|
+
if KeyComparator > other.class
|
19
|
+
other = other.to_bytes
|
20
|
+
elsif other.respond_to?(:to_str)
|
21
|
+
other = other.to_str
|
22
|
+
else
|
23
|
+
return nil
|
24
|
+
end
|
25
|
+
|
26
|
+
if Util.verify32(self.to_bytes, other)
|
27
|
+
return 0
|
28
|
+
elsif self.to_bytes > other
|
29
|
+
return 1
|
30
|
+
else
|
31
|
+
return -1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# equality operator
|
36
|
+
#
|
37
|
+
# The equality operator is explicity defined, despite including Comparable
|
38
|
+
# and having a spaceship operator, so that if equality tests are desired,
|
39
|
+
# they can be timing invariant, without any chance that the further
|
40
|
+
# comparisons for greater than and less than can leak information. Maybe
|
41
|
+
# this is too paranoid, but I don't know how ruby works under the hood with
|
42
|
+
# comparable.
|
43
|
+
#
|
44
|
+
# @param other [KeyComparator,#to_str] The thing to compare
|
45
|
+
#
|
46
|
+
# @return [true] if the keys are equal
|
47
|
+
# @return [false] if they keys are not equal
|
48
|
+
def ==(other)
|
49
|
+
if KeyComparator > other.class
|
50
|
+
other = other.to_bytes
|
51
|
+
elsif other.respond_to?(:to_str)
|
52
|
+
other = other.to_str
|
53
|
+
else
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
Util.verify32(self.to_bytes, other)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|