rbnacl 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/.coveralls.yml +1 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +4 -0
  4. data/.travis.yml +21 -0
  5. data/.yardopts +1 -0
  6. data/CHANGES.md +4 -0
  7. data/Gemfile +9 -0
  8. data/LICENSE.txt +23 -0
  9. data/README.md +179 -0
  10. data/Rakefile +5 -0
  11. data/images/dragons.png +0 -0
  12. data/images/ed25519.png +0 -0
  13. data/images/logo.png +0 -0
  14. data/lib/rbnacl.rb +46 -0
  15. data/lib/rbnacl/auth.rb +78 -0
  16. data/lib/rbnacl/auth/one_time.rb +38 -0
  17. data/lib/rbnacl/box.rb +141 -0
  18. data/lib/rbnacl/encoder.rb +44 -0
  19. data/lib/rbnacl/encoders/base32.rb +33 -0
  20. data/lib/rbnacl/encoders/base64.rb +30 -0
  21. data/lib/rbnacl/encoders/hex.rb +30 -0
  22. data/lib/rbnacl/encoders/raw.rb +12 -0
  23. data/lib/rbnacl/hash.rb +48 -0
  24. data/lib/rbnacl/hmac/sha256.rb +32 -0
  25. data/lib/rbnacl/hmac/sha512256.rb +35 -0
  26. data/lib/rbnacl/keys/key_comparator.rb +59 -0
  27. data/lib/rbnacl/keys/private_key.rb +62 -0
  28. data/lib/rbnacl/keys/public_key.rb +38 -0
  29. data/lib/rbnacl/keys/signing_key.rb +74 -0
  30. data/lib/rbnacl/keys/verify_key.rb +76 -0
  31. data/lib/rbnacl/nacl.rb +132 -0
  32. data/lib/rbnacl/point.rb +67 -0
  33. data/lib/rbnacl/rake_tasks.rb +56 -0
  34. data/lib/rbnacl/random.rb +19 -0
  35. data/lib/rbnacl/random_nonce_box.rb +109 -0
  36. data/lib/rbnacl/secret_box.rb +86 -0
  37. data/lib/rbnacl/self_test.rb +118 -0
  38. data/lib/rbnacl/serializable.rb +23 -0
  39. data/lib/rbnacl/test_vectors.rb +69 -0
  40. data/lib/rbnacl/util.rb +137 -0
  41. data/lib/rbnacl/version.rb +5 -0
  42. data/rbnacl.gemspec +28 -0
  43. data/rbnacl.gpg +30 -0
  44. data/spec/rbnacl/auth/one_time_spec.rb +8 -0
  45. data/spec/rbnacl/box_spec.rb +42 -0
  46. data/spec/rbnacl/encoder_spec.rb +14 -0
  47. data/spec/rbnacl/encoders/base32_spec.rb +16 -0
  48. data/spec/rbnacl/encoders/base64_spec.rb +15 -0
  49. data/spec/rbnacl/encoders/hex_spec.rb +15 -0
  50. data/spec/rbnacl/hash_spec.rb +52 -0
  51. data/spec/rbnacl/hmac/sha256_spec.rb +8 -0
  52. data/spec/rbnacl/hmac/sha512256_spec.rb +8 -0
  53. data/spec/rbnacl/keys/private_key_spec.rb +68 -0
  54. data/spec/rbnacl/keys/public_key_spec.rb +45 -0
  55. data/spec/rbnacl/keys/signing_key_spec.rb +40 -0
  56. data/spec/rbnacl/keys/verify_key_spec.rb +51 -0
  57. data/spec/rbnacl/point_spec.rb +29 -0
  58. data/spec/rbnacl/random_nonce_box_spec.rb +78 -0
  59. data/spec/rbnacl/random_spec.rb +9 -0
  60. data/spec/rbnacl/secret_box_spec.rb +24 -0
  61. data/spec/rbnacl/util_spec.rb +119 -0
  62. data/spec/shared/authenticator.rb +114 -0
  63. data/spec/shared/box.rb +51 -0
  64. data/spec/shared/key_equality.rb +26 -0
  65. data/spec/spec_helper.rb +14 -0
  66. data/tasks/ci.rake +11 -0
  67. data/tasks/rspec.rake +7 -0
  68. metadata +187 -0
@@ -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
@@ -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