diff --git a/lib/rbnacl.rb b/lib/rbnacl.rb index 7b4dcda..e229933 100644 --- a/lib/rbnacl.rb +++ b/lib/rbnacl.rb @@ -1,4 +1,14 @@ # encoding: binary +require "rbnacl/version" +require "rbnacl/nacl" +require "rbnacl/serializable" +require "rbnacl/key_comparator" +require "rbnacl/auth" +require "rbnacl/util" +require "rbnacl/random" +require "rbnacl/random_nonce_box" +require "rbnacl/test_vectors" + module RbNaCl # Oh no, something went wrong! # @@ -18,29 +28,48 @@ class LengthError < ArgumentError; end # This indicates that an attempt has been made to use something (probably a key) # with an incorrect primitive class IncorrectPrimitiveError < ArgumentError; end -end -require "rbnacl/nacl" -require "rbnacl/version" -require "rbnacl/serializable" -require "rbnacl/keys/key_comparator" -require "rbnacl/keys/private_key" -require "rbnacl/keys/public_key" -require "rbnacl/keys/signing_key" -require "rbnacl/keys/verify_key" -require "rbnacl/box" -require "rbnacl/secret_box" -require "rbnacl/hash" -require "rbnacl/hash/blake2b" -require "rbnacl/util" -require "rbnacl/auth" -require "rbnacl/hmac/sha512256" -require "rbnacl/hmac/sha256" -require "rbnacl/auth/one_time" -require "rbnacl/random" -require "rbnacl/point" -require "rbnacl/random_nonce_box" -require "rbnacl/test_vectors" + # The signature was forged or otherwise corrupt + class BadSignatureError < CryptoError; end + + # Public Key Encryption (Box): Curve25519XSalsa20Poly1305 + require "rbnacl/curve25519xsalsa20poly1305/private_key" + require "rbnacl/curve25519xsalsa20poly1305/public_key" + require "rbnacl/curve25519xsalsa20poly1305/box" + + # Secret Key Encryption (SecretBox): XSalsa20Poly1305 + require "rbnacl/xsalsa20poly1305/secret_box" + + # Digital Signatures: Ed25519 + require "rbnacl/ed25519/signing_key" + require "rbnacl/ed25519/verify_key" + + # Diffie-Hellman: Curve25519 + require "rbnacl/curve25519/point" + + # One-time Authentication: Poly1305 + require "rbnacl/poly1305/one_time_auth" + + # Blake2b hash function + require "rbnacl/blake2b/hash" + + # NIST hash and HMAC functions + require "rbnacl/hash" + require "rbnacl/sha256/hmac" + require "rbnacl/sha512256/hmac" + + # + # Bind aliases used by the public API + # + Box = Curve25519XSalsa20Poly1305::Box + PrivateKey = Curve25519XSalsa20Poly1305::PrivateKey + PublicKey = Curve25519XSalsa20Poly1305::PublicKey + SecretBox = XSalsa20Poly1305::SecretBox + SigningKey = Ed25519::SigningKey + VerifyKey = Ed25519::VerifyKey + Point = Curve25519::Point + OneTimeAuth = Poly1305::OneTimeAuth +end # Select platform-optimized versions of algorithms Thread.exclusive { RbNaCl::NaCl.sodium_init } diff --git a/lib/rbnacl/hash/blake2b.rb b/lib/rbnacl/blake2b/hash.rb similarity index 98% rename from lib/rbnacl/hash/blake2b.rb rename to lib/rbnacl/blake2b/hash.rb index e54e736..8ca71db 100644 --- a/lib/rbnacl/hash/blake2b.rb +++ b/lib/rbnacl/blake2b/hash.rb @@ -1,5 +1,5 @@ module RbNaCl - module Hash + module Blake2b # The Blake2b hash function # # Blake2b is based on Blake, a SHA3 finalist which was snubbed in favor of @@ -9,7 +9,7 @@ module Hash # # Blake2b provides for up to 64-bit digests and also supports a keyed mode # similar to HMAC - class Blake2b + class Hash # Create a new Blake2b hash object # # @param [Hash] opts Blake2b configuration diff --git a/lib/rbnacl/box.rb b/lib/rbnacl/box.rb deleted file mode 100644 index 97b60e5..0000000 --- a/lib/rbnacl/box.rb +++ /dev/null @@ -1,171 +0,0 @@ -# encoding: binary -module RbNaCl - # The Box class boxes and unboxes messages between a pair of keys - # - # This class uses the given public and secret keys to derive a shared key, - # which is used with the nonce given to encrypt the given messages and - # decrypt the given ciphertexts. The same shared key will generated from - # both pairing of keys, so given two keypairs belonging to alice (pkalice, - # skalice) and bob(pkbob, skbob), the key derived from (pkalice, skbob) with - # equal that from (pkbob, skalice). This is how the system works: - # - # @example - # # On bob's system - # bobkey = RbNaCl::PrivateKey.generate - # #=> # - # - # # send bobkey.public_key to alice - # # recieve alice's public key, alicepk - # # NB: This is actually the hard part of the system. How to do it securely - # # is left as an exercise to for the reader. - # alice_pubkey = "..." - # - # # make a box - # alicebob_box = RbNaCl::Box.new(alice_pubkey, bobkey) - # #=> # - # - # # encrypt a message to alice - # cipher_text = alicebob_box.box("A bad example of a nonce", "Hello, Alice!") - # #=> "..." # a string of bytes, 29 bytes long - # - # # send ["A bad example of a nonce", cipher_text] to alice - # # note that nonces don't have to be secret - # # receive [nonce, cipher_text_to_bob] from alice - # - # # decrypt the reply - # # Alice has been a little more sensible than bob, and has a random nonce - # # that is too fiddly to type here. But there are other choices than just - # # random - # plain_text = alicebob_box.open(nonce, cipher_text_to_bob) - # #=> "Hey there, Bob!" - # - # # we have a new message! - # # But Eve has tampered with this message, by flipping some bytes around! - # # [nonce2, cipher_text_to_bob_honest_love_eve] - # alicebob_box.open(nonce2, cipher_text_to_bob_honest_love_eve) - # - # # BOOM! - # # Bob gets a RbNaCl::CryptoError to deal with! - # - # It is VITALLY important that the nonce is a nonce, i.e. it is a number used - # only once for any given pair of keys. If you fail to do this, you - # compromise the privacy of the the messages encrypted. Also, bear in mind - # the property mentioned just above. Give your nonces a different prefix, or - # have one side use an odd counter and one an even counter. Just make sure - # they are different. - # - # The ciphertexts generated by this class include a 16-byte authenticator which - # is checked as part of the decryption. An invalid authenticator will cause - # the unbox function to raise. The authenticator is not a signature. Once - # you've looked in the box, you've demonstrated the ability to create - # arbitrary valid messages, so messages you send are repudiable. For - # non-repudiable messages, sign them before or after encryption. - class Box - - # Number of bytes in a Box nonce - NONCEBYTES = NaCl::CURVE25519_XSALSA20_POLY1305_BOX_NONCEBYTES - - # Create a new Box - # - # Sets up the Box for deriving the shared key and encrypting and - # decrypting messages. - # - # @param public_key [String,RbNaCl::PublicKey] The public key to encrypt to - # @param private_key [String,RbNaCl::PrivateKey] The private key to encrypt with - # @param encoding [Symbol] Parse keys from the given encoding - # - # @raise [RbNaCl::LengthError] on invalid keys - # - # @return [RbNaCl::Box] The new Box, ready to use - def initialize(public_key, private_key, encoding = :raw) - @public_key = PublicKey === public_key ? public_key : PublicKey.new(public_key) - @private_key = PrivateKey === private_key ? private_key : PrivateKey.new(private_key) - raise IncorrectPrimitiveError unless @public_key.primitive == primitive && @private_key.primitive == primitive - end - - # Encrypts a message - # - # Encrypts the message with the given nonce to the keypair set up when - # initializing the class. Make sure the nonce is unique for any given - # keypair, or you might as well just send plain text. - # - # This function takes care of the padding required by the NaCL C API. - # - # @param nonce [String] A 24-byte string containing the nonce. - # @param message [String] The message to be encrypted. - # - # @raise [RbNaCl::LengthError] If the nonce is not valid - # - # @return [String] The ciphertext without the nonce prepended (BINARY encoded) - def box(nonce, message) - Util.check_length(nonce, nonce_bytes, "Nonce") - msg = Util.prepend_zeros(NaCl::ZEROBYTES, message) - ct = Util.zeros(msg.bytesize) - - NaCl.crypto_box_curve25519_xsalsa20_poly1305_afternm(ct, msg, msg.bytesize, nonce, beforenm) || raise(CryptoError, "Encryption failed") - Util.remove_zeros(NaCl::BOXZEROBYTES, ct) - end - alias encrypt box - - # Decrypts a ciphertext - # - # Decrypts the ciphertext with the given nonce using the keypair setup when - # initializing the class. - # - # This function takes care of the padding required by the NaCL C API. - # - # @param nonce [String] A 24-byte string containing the nonce. - # @param ciphertext [String] The message to be decrypted. - # - # @raise [RbNaCl::LengthError] If the nonce is not valid - # @raise [RbNaCl::CryptoError] If the ciphertext cannot be authenticated. - # - # @return [String] The decrypted message (BINARY encoded) - def open(nonce, ciphertext) - Util.check_length(nonce, nonce_bytes, "Nonce") - ct = Util.prepend_zeros(NaCl::BOXZEROBYTES, ciphertext) - message = Util.zeros(ct.bytesize) - - NaCl.crypto_box_curve25519_xsalsa20_poly1305_open_afternm(message, ct, ct.bytesize, nonce, beforenm) || raise(CryptoError, "Decryption failed. Ciphertext failed verification.") - Util.remove_zeros(NaCl::ZEROBYTES, message) - end - alias decrypt open - - # The crypto primitive for the box class - # - # @return [Symbol] The primitive used - def self.primitive - :curve25519_xsalsa20_poly1305 - end - - # The crypto primitive for the box class - # - # @return [Symbol] The primitive used - def primitive - self.class.primitive - end - - # The nonce bytes for the box class - # - # @return [Integer] The number of bytes in a valid nonce - def self.nonce_bytes - NONCEBYTES - end - - # The nonce bytes for the box instance - # - # @return [Integer] The number of bytes in a valid nonce - def nonce_bytes - NONCEBYTES - end - - private - def beforenm - @k ||= begin - k = Util.zeros(NaCl::CURVE25519_XSALSA20_POLY1305_BOX_BEFORENMBYTES) - NaCl.crypto_box_curve25519_xsalsa20_poly1305_beforenm(k, @public_key.to_s, @private_key.to_s) || raise(CryptoError, "Failed to derive shared key") - k - end - end - end -end diff --git a/lib/rbnacl/curve25519/point.rb b/lib/rbnacl/curve25519/point.rb new file mode 100644 index 0000000..df6b7d4 --- /dev/null +++ b/lib/rbnacl/curve25519/point.rb @@ -0,0 +1,69 @@ +# encoding: binary +module RbNaCl + module Curve25519 + # NaCl's base point (a.k.a. standard group element), serialized as hex + STANDARD_GROUP_ELEMENT = ["0900000000000000000000000000000000000000000000000000000000000000"].pack("H*").freeze + + # Order of the standard group + STANDARD_GROUP_ORDER = 2**252 + 27742317777372353535851937790883648493 + + # Points provide the interface to NaCl's Curve25519 high-speed elliptic + # curve cryptography, which can be used for implementing Diffie-Hellman + # and other forms of public key cryptography (e.g. RbNaCl::Box) + # + # Objects of the Point class represent points on Edwards curves. NaCl + # defines a base point (the "standard group element") which we can + # multiply by an arbitrary integer. This is how NaCl computes public + # keys from private keys. + class Point + include KeyComparator + include Serializable + + # Number of bytes in a scalar on this curve + SCALARBYTES = NaCl::ED25519_SCALARBYTES + + # Creates a new Point from the given serialization + # + # @param [String] point location of a group element (32-bytes) + # + # @return [RbNaCl::Point] the Point at this location + def initialize(point) + @point = point.to_str + + # FIXME: really should have a separate constant here for group element size + # Group elements and scalars are both 32-bits, but that's for convenience + Util.check_length(@point, SCALARBYTES, "group element") + end + + # Multiply the given integer by this point + # This ordering is a bit confusing because traditionally the point + # would be the right-hand operand. + # + # @param [String] integer value to multiply with this Point (32-bytes) + # + # @return [RbNaCl::Point] result as a Point object + def mult(integer, encoding = :raw) + integer = integer.to_str + Util.check_length(integer, SCALARBYTES, "integer") + + result = Util.zeros(SCALARBYTES) + NaCl.crypto_scalarmult_curve25519(result, integer, @point) + + self.class.new(result) + end + + # Return the point serialized as bytes + # + # @return [String] 32-byte string representing this point + def to_bytes; @point; end + + @base_point = Point.new(STANDARD_GROUP_ELEMENT) + + # NaCl's standard base point for all Curve25519 public keys + # + # @return [RbNaCl::Point] standard base point (a.k.a. standard group element) + def self.base; @base_point; end + def self.base_point; @base_point; end + end + end +end diff --git a/lib/rbnacl/curve25519xsalsa20poly1305/box.rb b/lib/rbnacl/curve25519xsalsa20poly1305/box.rb new file mode 100644 index 0000000..fef0530 --- /dev/null +++ b/lib/rbnacl/curve25519xsalsa20poly1305/box.rb @@ -0,0 +1,173 @@ +# encoding: binary +module RbNaCl + module Curve25519XSalsa20Poly1305 + # The Box class boxes and unboxes messages between a pair of keys + # + # This class uses the given public and secret keys to derive a shared key, + # which is used with the nonce given to encrypt the given messages and + # decrypt the given ciphertexts. The same shared key will generated from + # both pairing of keys, so given two keypairs belonging to alice (pkalice, + # skalice) and bob(pkbob, skbob), the key derived from (pkalice, skbob) with + # equal that from (pkbob, skalice). This is how the system works: + # + # @example + # # On bob's system + # bobkey = RbNaCl::PrivateKey.generate + # #=> # + # + # # send bobkey.public_key to alice + # # recieve alice's public key, alicepk + # # NB: This is actually the hard part of the system. How to do it securely + # # is left as an exercise to for the reader. + # alice_pubkey = "..." + # + # # make a box + # alicebob_box = RbNaCl::Box.new(alice_pubkey, bobkey) + # #=> # + # + # # encrypt a message to alice + # cipher_text = alicebob_box.box("A bad example of a nonce", "Hello, Alice!") + # #=> "..." # a string of bytes, 29 bytes long + # + # # send ["A bad example of a nonce", cipher_text] to alice + # # note that nonces don't have to be secret + # # receive [nonce, cipher_text_to_bob] from alice + # + # # decrypt the reply + # # Alice has been a little more sensible than bob, and has a random nonce + # # that is too fiddly to type here. But there are other choices than just + # # random + # plain_text = alicebob_box.open(nonce, cipher_text_to_bob) + # #=> "Hey there, Bob!" + # + # # we have a new message! + # # But Eve has tampered with this message, by flipping some bytes around! + # # [nonce2, cipher_text_to_bob_honest_love_eve] + # alicebob_box.open(nonce2, cipher_text_to_bob_honest_love_eve) + # + # # BOOM! + # # Bob gets a RbNaCl::CryptoError to deal with! + # + # It is VITALLY important that the nonce is a nonce, i.e. it is a number used + # only once for any given pair of keys. If you fail to do this, you + # compromise the privacy of the the messages encrypted. Also, bear in mind + # the property mentioned just above. Give your nonces a different prefix, or + # have one side use an odd counter and one an even counter. Just make sure + # they are different. + # + # The ciphertexts generated by this class include a 16-byte authenticator which + # is checked as part of the decryption. An invalid authenticator will cause + # the unbox function to raise. The authenticator is not a signature. Once + # you've looked in the box, you've demonstrated the ability to create + # arbitrary valid messages, so messages you send are repudiable. For + # non-repudiable messages, sign them before or after encryption. + class Box + + # Number of bytes in a Box nonce + NONCEBYTES = NaCl::CURVE25519_XSALSA20_POLY1305_BOX_NONCEBYTES + + # Create a new Box + # + # Sets up the Box for deriving the shared key and encrypting and + # decrypting messages. + # + # @param public_key [String,RbNaCl::PublicKey] The public key to encrypt to + # @param private_key [String,RbNaCl::PrivateKey] The private key to encrypt with + # @param encoding [Symbol] Parse keys from the given encoding + # + # @raise [RbNaCl::LengthError] on invalid keys + # + # @return [RbNaCl::Box] The new Box, ready to use + def initialize(public_key, private_key, encoding = :raw) + @public_key = PublicKey === public_key ? public_key : PublicKey.new(public_key) + @private_key = PrivateKey === private_key ? private_key : PrivateKey.new(private_key) + raise IncorrectPrimitiveError unless @public_key.primitive == primitive && @private_key.primitive == primitive + end + + # Encrypts a message + # + # Encrypts the message with the given nonce to the keypair set up when + # initializing the class. Make sure the nonce is unique for any given + # keypair, or you might as well just send plain text. + # + # This function takes care of the padding required by the NaCL C API. + # + # @param nonce [String] A 24-byte string containing the nonce. + # @param message [String] The message to be encrypted. + # + # @raise [RbNaCl::LengthError] If the nonce is not valid + # + # @return [String] The ciphertext without the nonce prepended (BINARY encoded) + def box(nonce, message) + Util.check_length(nonce, nonce_bytes, "Nonce") + msg = Util.prepend_zeros(NaCl::ZEROBYTES, message) + ct = Util.zeros(msg.bytesize) + + NaCl.crypto_box_curve25519_xsalsa20_poly1305_afternm(ct, msg, msg.bytesize, nonce, beforenm) || raise(CryptoError, "Encryption failed") + Util.remove_zeros(NaCl::BOXZEROBYTES, ct) + end + alias encrypt box + + # Decrypts a ciphertext + # + # Decrypts the ciphertext with the given nonce using the keypair setup when + # initializing the class. + # + # This function takes care of the padding required by the NaCL C API. + # + # @param nonce [String] A 24-byte string containing the nonce. + # @param ciphertext [String] The message to be decrypted. + # + # @raise [RbNaCl::LengthError] If the nonce is not valid + # @raise [RbNaCl::CryptoError] If the ciphertext cannot be authenticated. + # + # @return [String] The decrypted message (BINARY encoded) + def open(nonce, ciphertext) + Util.check_length(nonce, nonce_bytes, "Nonce") + ct = Util.prepend_zeros(NaCl::BOXZEROBYTES, ciphertext) + message = Util.zeros(ct.bytesize) + + NaCl.crypto_box_curve25519_xsalsa20_poly1305_open_afternm(message, ct, ct.bytesize, nonce, beforenm) || raise(CryptoError, "Decryption failed. Ciphertext failed verification.") + Util.remove_zeros(NaCl::ZEROBYTES, message) + end + alias decrypt open + + # The crypto primitive for the box class + # + # @return [Symbol] The primitive used + def self.primitive + :curve25519_xsalsa20_poly1305 + end + + # The crypto primitive for the box class + # + # @return [Symbol] The primitive used + def primitive + self.class.primitive + end + + # The nonce bytes for the box class + # + # @return [Integer] The number of bytes in a valid nonce + def self.nonce_bytes + NONCEBYTES + end + + # The nonce bytes for the box instance + # + # @return [Integer] The number of bytes in a valid nonce + def nonce_bytes + NONCEBYTES + end + + private + def beforenm + @k ||= begin + k = Util.zeros(NaCl::CURVE25519_XSALSA20_POLY1305_BOX_BEFORENMBYTES) + NaCl.crypto_box_curve25519_xsalsa20_poly1305_beforenm(k, @public_key.to_s, @private_key.to_s) || raise(CryptoError, "Failed to derive shared key") + k + end + end + end + end +end diff --git a/lib/rbnacl/curve25519xsalsa20poly1305/private_key.rb b/lib/rbnacl/curve25519xsalsa20poly1305/private_key.rb new file mode 100644 index 0000000..20f11a2 --- /dev/null +++ b/lib/rbnacl/curve25519xsalsa20poly1305/private_key.rb @@ -0,0 +1,79 @@ +# encoding: binary +module RbNaCl + module Curve25519XSalsa20Poly1305 + # RbNaCl::Box private key. Keep it safe + # + # This class generates and stores NaCL private keys, as well as providing a + # reference to the public key associated with this private key, if that's + # provided. + # + # Note that the documentation for NaCl refers to this as a secret key, but in + # this library its a private key, to avoid confusing the issue with the + # SecretBox, which does symmetric encryption. + class PrivateKey + include KeyComparator + include Serializable + + # The size of the key, in bytes + BYTES = NaCl::CURVE25519_XSALSA20_POLY1305_SECRETKEY_BYTES + + # Initializes a new PrivateKey for key operations. + # + # Takes the (optionally encoded) private key bytes. This class can then be + # used for various key operations, including deriving the corresponding + # PublicKey + # + # @param private_key [String] The private key + # @param key_encoding [Symbol] The encoding of the key + # + # @raise [TypeError] If the key is nil + # @raise [RbNaCl::LengthError] If the key is not valid after decoding. + # + # @return A new PrivateKey + def initialize(private_key) + @private_key = Util.check_string(private_key, BYTES, "Private key") + end + + # Generates a new keypair + # + # @raise [RbNaCl::CryptoError] if key generation fails, due to insufficient randomness. + # + # @return [RbNaCl::PrivateKey] A new private key, with the associated public key also set. + def self.generate + pk = Util.zeros(NaCl::CURVE25519_XSALSA20_POLY1305_PUBLICKEY_BYTES) + sk = Util.zeros(NaCl::CURVE25519_XSALSA20_POLY1305_SECRETKEY_BYTES) + NaCl.crypto_box_curve25519xsalsa20poly1305_keypair(pk, sk) || raise(CryptoError, "Failed to generate a key pair") + new(sk) + end + + # The raw bytes of the key + # + # @return [String] the raw bytes. + def to_bytes + @private_key + end + + # the public key associated with this private key + # + # @return [PublicKey] the key + def public_key + @public_key ||= PublicKey.new(Point.base.mult(to_bytes)) + end + + # The crypto primitive the PrivateKey class is to be used for + # + # @return [Symbol] The primitive + def self.primitive + :curve25519_xsalsa20_poly1305 + end + + # The crypto primitive this PrivateKey is to be used for. + # + # @return [Symbol] The primitive + def primitive + self.class.primitive + end + + end + end +end diff --git a/lib/rbnacl/curve25519xsalsa20poly1305/public_key.rb b/lib/rbnacl/curve25519xsalsa20poly1305/public_key.rb new file mode 100644 index 0000000..0950eae --- /dev/null +++ b/lib/rbnacl/curve25519xsalsa20poly1305/public_key.rb @@ -0,0 +1,52 @@ +# encoding: binary +module RbNaCl + module Curve25519XSalsa20Poly1305 + # RbNaCl::Box public key. Send to your friends. + # + # This class stores the NaCL public key, and provides some convenience + # functions for working with it. + class PublicKey + include KeyComparator + include Serializable + + # The size of the key, in bytes + BYTES = NaCl::CURVE25519_XSALSA20_POLY1305_PUBLICKEY_BYTES + + # Initializes a new PublicKey for key operations. + # + # Takes the (optionally encoded) public key bytes. This can be shared with + # many people and used to establish key pairs with their private key, for + # the exchanging of messages using a RbNaCl::Box + # + # @param public_key [String] The public key + # + # @raise [RbNaCl::LengthError] If the key is not valid after decoding. + # + # @return A new PublicKey + def initialize(public_key) + @public_key = Util.check_string(public_key, BYTES, "Public key") + end + + # The raw bytes of the key + # + # @return [String] the raw bytes. + def to_bytes + @public_key + end + + # The crypto primitive the PublicKey class is to be used for + # + # @return [Symbol] The primitive + def self.primitive + :curve25519_xsalsa20_poly1305 + end + + # The crypto primitive this PublicKey is to be used for. + # + # @return [Symbol] The primitive + def primitive + self.class.primitive + end + end + end +end diff --git a/lib/rbnacl/ed25519/signing_key.rb b/lib/rbnacl/ed25519/signing_key.rb new file mode 100644 index 0000000..2ed6b23 --- /dev/null +++ b/lib/rbnacl/ed25519/signing_key.rb @@ -0,0 +1,94 @@ +# encoding: binary +module RbNaCl + module Ed25519 + # Private key for producing digital signatures using the Ed25519 algorithm. + # Ed25519 provides a 128-bit security level, that is to say, all known attacks + # take at least 2^128 operations, providing the same security level as + # AES-128, NIST P-256, and RSA-3072. + # + # Signing keys are produced from a 32-byte (256-bit) random seed value. + # This value can be passed into the SigningKey constructor as a String + # whose bytesize is 32. + # + # The public VerifyKey can be computed from the private 32-byte seed value + # as well, eliminating the need to store a "keypair". + # + # SigningKey produces 64-byte (512-bit) signatures. The signatures are + # deterministic: signing the same message will always produce the same + # signature. This prevents "entropy failure" seen in other signature + # algorithms like DSA and ECDSA, where poor random number generators can + # leak enough information to recover the private key. + class SigningKey + include KeyComparator + include Serializable + + attr_reader :verify_key + + # Generate a random SigningKey + # + # @return [RbNaCl::SigningKey] Freshly-generated random SigningKey + def self.generate + new RbNaCl::Random.random_bytes(NaCl::ED25519_SEED_BYTES) + end + + # Create a SigningKey from a seed value + # + # @param seed [String] Random 32-byte value (i.e. private key) + # + # @return [RbNaCl::SigningKey] Key which can sign messages + def initialize(seed) + seed = seed.to_s + + Util.check_length(seed, NaCl::ED25519_SEED_BYTES, "seed") + + pk = Util.zeros(NaCl::ED25519_VERIFYKEY_BYTES) + sk = Util.zeros(NaCl::ED25519_SIGNINGKEY_BYTES) + + NaCl.crypto_sign_ed25519_seed_keypair(pk, sk, seed) || raise(CryptoError, "Failed to generate a key pair") + + @seed, @signing_key = seed, sk + @verify_key = VerifyKey.new(pk) + end + + # Sign a message using this key + # + # @param message [String] Message to be signed by this key + # + # @return [String] Signature as bytes + def sign(message) + buffer = Util.prepend_zeros(signature_bytes, message) + buffer_len = Util.zeros(FFI::Type::LONG_LONG.size) + + NaCl.crypto_sign_ed25519(buffer, buffer_len, message, message.bytesize, @signing_key) + + buffer[0, signature_bytes] + end + + # Return the raw seed value of this key + # + # @return [String] seed used to create this key + def to_bytes; @seed; end + + # The crypto primitive the SigningKey class uses for signatures + # + # @return [Symbol] The primitive + def self.primitive; :ed25519; end + + # The crypto primitive this SigningKey class uses for signatures + # + # @return [Symbol] The primitive + def primitive; self.class.primitive; end + + # The size of signatures generated by the SigningKey class + # + # @return [Integer] The number of bytes in a signature + def self.signature_bytes; NaCl::ED25519_SIGNATUREBYTES; end + + # The size of signatures generated by the SigningKey instance + # + # @return [Integer] The number of bytes in a signature + def signature_bytes; NaCl::ED25519_SIGNATUREBYTES; end + + end + end +end diff --git a/lib/rbnacl/ed25519/verify_key.rb b/lib/rbnacl/ed25519/verify_key.rb new file mode 100644 index 0000000..c13e8b0 --- /dev/null +++ b/lib/rbnacl/ed25519/verify_key.rb @@ -0,0 +1,85 @@ +# encoding: binary +module RbNaCl + module Ed25519 + # The public key counterpart to an Ed25519 SigningKey for producing digital + # signatures. Like the name says, VerifyKeys can be used to verify that a + # given digital signature is authentic. + # + # For more information on the Ed25519 digital signature system, please see + # the SigningKey documentation. + class VerifyKey + include KeyComparator + include Serializable + + # Create a new VerifyKey object from a public key. + # + # @param key [String] Ed25519 public key + # + # @return [RbNaCl::VerifyKey] Key which can verify messages + def initialize(key) + @key = key.to_str + Util.check_length(@key, NaCl::ED25519_VERIFYKEY_BYTES, "key") + end + + # Verify a signature for a given message + # + # @param signature [String] Alleged signature to be checked + # @param message [String] Message to be authenticated + # + # @return [Boolean] was the signature authentic? + def verify(signature, message) + signature = signature.to_str + Util.check_length(signature, signature_bytes, "signature") + + sig_and_msg = signature + message + buffer = Util.zeros(sig_and_msg.bytesize) + buffer_len = Util.zeros(FFI::Type::LONG_LONG.size) + + NaCl.crypto_sign_ed25519_open(buffer, buffer_len, sig_and_msg, sig_and_msg.bytesize, @key) + end + + # Verify a signature for a given message or raise exception + # + # "Dangerous" (but really safer) verify that raises an exception if a + # signature check fails. This is probably less likely to go unnoticed than + # an improperly checked verify, as you are forced to deal with the + # exception explicitly (and failing signature checks are certainly an + # exceptional condition!) + # + # The arguments are otherwise the same as the verify method. + # + # @param message [String] Message to be authenticated + # @param signature [String] Alleged signature to be checked + # + # @return [true] Will raise BadSignatureError if signature check fails + def verify!(message, signature) + verify(message, signature) or raise BadSignatureError, "signature was forged/corrupt" + end + + # Return the raw key in byte format + # + # @return [String] raw key as bytes + def to_bytes; @key; end + + # The crypto primitive the VerifyKey class uses for signatures + # + # @return [Symbol] The primitive + def self.primitive; :ed25519; end + + # The crypto primitive this VerifyKey class uses for signatures + # + # @return [Symbol] The primitive + def primitive; self.class.primitive; end + + # The size of signatures verified by the VerifyKey class + # + # @return [Integer] The number of bytes in a signature + def self.signature_bytes; NaCl::ED25519_SIGNATUREBYTES; end + + # The size of signatures verified by the VerifyKey instance + # + # @return [Integer] The number of bytes in a signature + def signature_bytes; NaCl::ED25519_SIGNATUREBYTES; end + end + end +end diff --git a/lib/rbnacl/hash.rb b/lib/rbnacl/hash.rb index 401339c..0ea4777 100644 --- a/lib/rbnacl/hash.rb +++ b/lib/rbnacl/hash.rb @@ -60,7 +60,7 @@ def self.sha512(data) def self.blake2b(data, options = {}) key = options[:key] digest_size = options[:digest_size] || NaCl::BLAKE2B_OUTBYTES - Blake2b.new(options).digest(data) + Blake2b::Hash.new(options).digest(data) end else diff --git a/lib/rbnacl/keys/key_comparator.rb b/lib/rbnacl/key_comparator.rb similarity index 100% rename from lib/rbnacl/keys/key_comparator.rb rename to lib/rbnacl/key_comparator.rb diff --git a/lib/rbnacl/keys/private_key.rb b/lib/rbnacl/keys/private_key.rb deleted file mode 100644 index a5e7e50..0000000 --- a/lib/rbnacl/keys/private_key.rb +++ /dev/null @@ -1,77 +0,0 @@ -# encoding: binary -module RbNaCl - # RbNaCl::Box private key. Keep it safe - # - # This class generates and stores NaCL private keys, as well as providing a - # reference to the public key associated with this private key, if that's - # provided. - # - # Note that the documentation for NaCl refers to this as a secret key, but in - # this library its a private key, to avoid confusing the issue with the - # SecretBox, which does symmetric encryption. - class PrivateKey - include KeyComparator - include Serializable - - # The size of the key, in bytes - BYTES = NaCl::CURVE25519_XSALSA20_POLY1305_SECRETKEY_BYTES - - # Initializes a new PrivateKey for key operations. - # - # Takes the (optionally encoded) private key bytes. This class can then be - # used for various key operations, including deriving the corresponding - # PublicKey - # - # @param private_key [String] The private key - # @param key_encoding [Symbol] The encoding of the key - # - # @raise [TypeError] If the key is nil - # @raise [RbNaCl::LengthError] If the key is not valid after decoding. - # - # @return A new PrivateKey - def initialize(private_key) - @private_key = Util.check_string(private_key, BYTES, "Private key") - end - - # Generates a new keypair - # - # @raise [RbNaCl::CryptoError] if key generation fails, due to insufficient randomness. - # - # @return [RbNaCl::PrivateKey] A new private key, with the associated public key also set. - def self.generate - pk = Util.zeros(NaCl::CURVE25519_XSALSA20_POLY1305_PUBLICKEY_BYTES) - sk = Util.zeros(NaCl::CURVE25519_XSALSA20_POLY1305_SECRETKEY_BYTES) - NaCl.crypto_box_curve25519xsalsa20poly1305_keypair(pk, sk) || raise(CryptoError, "Failed to generate a key pair") - new(sk) - end - - # The raw bytes of the key - # - # @return [String] the raw bytes. - def to_bytes - @private_key - end - - # the public key associated with this private key - # - # @return [PublicKey] the key - def public_key - @public_key ||= PublicKey.new(Point.base.mult(to_bytes)) - end - - # The crypto primitive the PrivateKey class is to be used for - # - # @return [Symbol] The primitive - def self.primitive - :curve25519_xsalsa20_poly1305 - end - - # The crypto primitive this PrivateKey is to be used for. - # - # @return [Symbol] The primitive - def primitive - self.class.primitive - end - - end -end diff --git a/lib/rbnacl/keys/public_key.rb b/lib/rbnacl/keys/public_key.rb deleted file mode 100644 index e6f084c..0000000 --- a/lib/rbnacl/keys/public_key.rb +++ /dev/null @@ -1,50 +0,0 @@ -# encoding: binary -module RbNaCl - # RbNaCl::Box public key. Send to your friends. - # - # This class stores the NaCL public key, and provides some convenience - # functions for working with it. - class PublicKey - include KeyComparator - include Serializable - - # The size of the key, in bytes - BYTES = NaCl::CURVE25519_XSALSA20_POLY1305_PUBLICKEY_BYTES - - # Initializes a new PublicKey for key operations. - # - # Takes the (optionally encoded) public key bytes. This can be shared with - # many people and used to establish key pairs with their private key, for - # the exchanging of messages using a RbNaCl::Box - # - # @param public_key [String] The public key - # - # @raise [RbNaCl::LengthError] If the key is not valid after decoding. - # - # @return A new PublicKey - def initialize(public_key) - @public_key = Util.check_string(public_key, BYTES, "Public key") - end - - # The raw bytes of the key - # - # @return [String] the raw bytes. - def to_bytes - @public_key - end - - # The crypto primitive the PublicKey class is to be used for - # - # @return [Symbol] The primitive - def self.primitive - :curve25519_xsalsa20_poly1305 - end - - # The crypto primitive this PublicKey is to be used for. - # - # @return [Symbol] The primitive - def primitive - self.class.primitive - end - end -end diff --git a/lib/rbnacl/keys/signing_key.rb b/lib/rbnacl/keys/signing_key.rb deleted file mode 100644 index d4030eb..0000000 --- a/lib/rbnacl/keys/signing_key.rb +++ /dev/null @@ -1,92 +0,0 @@ -# encoding: binary -module RbNaCl - # Private key for producing digital signatures using the Ed25519 algorithm. - # Ed25519 provides a 128-bit security level, that is to say, all known attacks - # take at least 2^128 operations, providing the same security level as - # AES-128, NIST P-256, and RSA-3072. - # - # Signing keys are produced from a 32-byte (256-bit) random seed value. - # This value can be passed into the SigningKey constructor as a String - # whose bytesize is 32. - # - # The public VerifyKey can be computed from the private 32-byte seed value - # as well, eliminating the need to store a "keypair". - # - # SigningKey produces 64-byte (512-bit) signatures. The signatures are - # deterministic: signing the same message will always produce the same - # signature. This prevents "entropy failure" seen in other signature - # algorithms like DSA and ECDSA, where poor random number generators can - # leak enough information to recover the private key. - class SigningKey - include KeyComparator - include Serializable - - attr_reader :verify_key - - # Generate a random SigningKey - # - # @return [RbNaCl::SigningKey] Freshly-generated random SigningKey - def self.generate - new RbNaCl::Random.random_bytes(NaCl::ED25519_SEED_BYTES) - end - - # Create a SigningKey from a seed value - # - # @param seed [String] Random 32-byte value (i.e. private key) - # - # @return [RbNaCl::SigningKey] Key which can sign messages - def initialize(seed) - seed = seed.to_s - - Util.check_length(seed, NaCl::ED25519_SEED_BYTES, "seed") - - pk = Util.zeros(NaCl::ED25519_VERIFYKEY_BYTES) - sk = Util.zeros(NaCl::ED25519_SIGNINGKEY_BYTES) - - NaCl.crypto_sign_ed25519_seed_keypair(pk, sk, seed) || raise(CryptoError, "Failed to generate a key pair") - - @seed, @signing_key = seed, sk - @verify_key = VerifyKey.new(pk) - end - - # Sign a message using this key - # - # @param message [String] Message to be signed by this key - # - # @return [String] Signature as bytes - def sign(message) - buffer = Util.prepend_zeros(signature_bytes, message) - buffer_len = Util.zeros(FFI::Type::LONG_LONG.size) - - NaCl.crypto_sign_ed25519(buffer, buffer_len, message, message.bytesize, @signing_key) - - buffer[0, signature_bytes] - end - - # Return the raw seed value of this key - # - # @return [String] seed used to create this key - def to_bytes; @seed; end - - # The crypto primitive the SigningKey class uses for signatures - # - # @return [Symbol] The primitive - def self.primitive; :ed25519; end - - # The crypto primitive this SigningKey class uses for signatures - # - # @return [Symbol] The primitive - def primitive; self.class.primitive; end - - # The size of signatures generated by the SigningKey class - # - # @return [Integer] The number of bytes in a signature - def self.signature_bytes; NaCl::ED25519_SIGNATUREBYTES; end - - # The size of signatures generated by the SigningKey instance - # - # @return [Integer] The number of bytes in a signature - def signature_bytes; NaCl::ED25519_SIGNATUREBYTES; end - - end -end diff --git a/lib/rbnacl/keys/verify_key.rb b/lib/rbnacl/keys/verify_key.rb deleted file mode 100644 index d7f71e9..0000000 --- a/lib/rbnacl/keys/verify_key.rb +++ /dev/null @@ -1,86 +0,0 @@ -# encoding: binary -module RbNaCl - # The signature was forged or otherwise corrupt - class BadSignatureError < CryptoError; end - - # The public key counterpart to an Ed25519 SigningKey for producing digital - # signatures. Like the name says, VerifyKeys can be used to verify that a - # given digital signature is authentic. - # - # For more information on the Ed25519 digital signature system, please see - # the SigningKey documentation. - class VerifyKey - include KeyComparator - include Serializable - - # Create a new VerifyKey object from a public key. - # - # @param key [String] Ed25519 public key - # - # @return [RbNaCl::VerifyKey] Key which can verify messages - def initialize(key) - @key = key.to_str - Util.check_length(@key, NaCl::ED25519_VERIFYKEY_BYTES, "key") - end - - # Verify a signature for a given message - # - # @param signature [String] Alleged signature to be checked - # @param message [String] Message to be authenticated - # - # @return [Boolean] was the signature authentic? - def verify(signature, message) - signature = signature.to_str - Util.check_length(signature, signature_bytes, "signature") - - sig_and_msg = signature + message - buffer = Util.zeros(sig_and_msg.bytesize) - buffer_len = Util.zeros(FFI::Type::LONG_LONG.size) - - NaCl.crypto_sign_ed25519_open(buffer, buffer_len, sig_and_msg, sig_and_msg.bytesize, @key) - end - - # Verify a signature for a given message or raise exception - # - # "Dangerous" (but really safer) verify that raises an exception if a - # signature check fails. This is probably less likely to go unnoticed than - # an improperly checked verify, as you are forced to deal with the - # exception explicitly (and failing signature checks are certainly an - # exceptional condition!) - # - # The arguments are otherwise the same as the verify method. - # - # @param message [String] Message to be authenticated - # @param signature [String] Alleged signature to be checked - # - # @return [true] Will raise BadSignatureError if signature check fails - def verify!(message, signature) - verify(message, signature) or raise BadSignatureError, "signature was forged/corrupt" - end - - # Return the raw key in byte format - # - # @return [String] raw key as bytes - def to_bytes; @key; end - - # The crypto primitive the VerifyKey class uses for signatures - # - # @return [Symbol] The primitive - def self.primitive; :ed25519; end - - # The crypto primitive this VerifyKey class uses for signatures - # - # @return [Symbol] The primitive - def primitive; self.class.primitive; end - - # The size of signatures verified by the VerifyKey class - # - # @return [Integer] The number of bytes in a signature - def self.signature_bytes; NaCl::ED25519_SIGNATUREBYTES; end - - # The size of signatures verified by the VerifyKey instance - # - # @return [Integer] The number of bytes in a signature - def signature_bytes; NaCl::ED25519_SIGNATUREBYTES; end - end -end diff --git a/lib/rbnacl/point.rb b/lib/rbnacl/point.rb deleted file mode 100644 index 3726636..0000000 --- a/lib/rbnacl/point.rb +++ /dev/null @@ -1,68 +0,0 @@ -# encoding: binary -module RbNaCl - # NaCl's base point (a.k.a. standard group element), serialized as hex - STANDARD_GROUP_ELEMENT = ["0900000000000000000000000000000000000000000000000000000000000000"].pack("H*").freeze - - # Order of the standard group - STANDARD_GROUP_ORDER = 2**252 + 27742317777372353535851937790883648493 - - # Points provide the interface to NaCl's Curve25519 high-speed elliptic - # curve cryptography, which can be used for implementing Diffie-Hellman - # and other forms of public key cryptography (e.g. RbNaCl::Box) - # - # Objects of the Point class represent points on Edwards curves. NaCl - # defines a base point (the "standard group element") which we can - # multiply by an arbitrary integer. This is how NaCl computes public - # keys from private keys. - class Point - include KeyComparator - include Serializable - - # Number of bytes in a scalar on this curve - SCALARBYTES = NaCl::ED25519_SCALARBYTES - - # Creates a new Point from the given serialization - # - # @param [String] point location of a group element (32-bytes) - # - # @return [RbNaCl::Point] the Point at this location - def initialize(point) - @point = point.to_str - - # FIXME: really should have a separate constant here for group element size - # Group elements and scalars are both 32-bits, but that's for convenience - Util.check_length(@point, SCALARBYTES, "group element") - end - - # Multiply the given integer by this point - # This ordering is a bit confusing because traditionally the point - # would be the right-hand operand. - # - # @param [String] integer value to multiply with this Point (32-bytes) - # - # @return [RbNaCl::Point] result as a Point object - def mult(integer, encoding = :raw) - integer = integer.to_str - Util.check_length(integer, SCALARBYTES, "integer") - - result = Util.zeros(SCALARBYTES) - NaCl.crypto_scalarmult_curve25519(result, integer, @point) - - self.class.new(result) - end - - # Return the point serialized as bytes - # - # @return [String] 32-byte string representing this point - def to_bytes; @point; end - - @base_point = Point.new(STANDARD_GROUP_ELEMENT) - - # NaCl's standard base point for all Curve25519 public keys - # - # @return [RbNaCl::Point] standard base point (a.k.a. standard group element) - def self.base; @base_point; end - def self.base_point; @base_point; end - end -end - diff --git a/lib/rbnacl/auth/one_time.rb b/lib/rbnacl/poly1305/one_time_auth.rb similarity index 96% rename from lib/rbnacl/auth/one_time.rb rename to lib/rbnacl/poly1305/one_time_auth.rb index 72d4efd..53b9ed1 100644 --- a/lib/rbnacl/auth/one_time.rb +++ b/lib/rbnacl/poly1305/one_time_auth.rb @@ -1,6 +1,6 @@ # encoding: binary module RbNaCl - class Auth + module Poly1305 # Computes an authenticator using poly1305 # # The authenticator can be used at a later time to verify the provenance of @@ -17,7 +17,7 @@ class Auth # can also create them. # # @see http://nacl.cr.yp.to/onetimeauth.html - class OneTime < self + class OneTimeAuth < Auth # Number of bytes in a valid key KEYBYTES = NaCl::ONETIME_KEYBYTES diff --git a/lib/rbnacl/secret_box.rb b/lib/rbnacl/secret_box.rb deleted file mode 100644 index 4cede9a..0000000 --- a/lib/rbnacl/secret_box.rb +++ /dev/null @@ -1,117 +0,0 @@ -# encoding: binary -module RbNaCl - # The SecretBox class boxes and unboxes messages - # - # This class uses the given secret key to encrypt and decrypt messages. - # - # It is VITALLY important that the nonce is a nonce, i.e. it is a number used - # only once for any given pair of keys. If you fail to do this, you - # compromise the privacy of the messages encrypted. Give your nonces a - # different prefix, or have one side use an odd counter and one an even counter. - # Just make sure they are different. - # - # The ciphertexts generated by this class include a 16-byte authenticator which - # is checked as part of the decryption. An invalid authenticator will cause - # the unbox function to raise. The authenticator is not a signature. Once - # you've looked in the box, you've demonstrated the ability to create - # arbitrary valid messages, so messages you send are repudiable. For - # non-repudiable messages, sign them before or after encryption. - class SecretBox - # Number of bytes for a secret key - KEYBYTES = NaCl::XSALSA20_POLY1305_SECRETBOX_KEYBYTES - - # Number of bytes for a nonce - NONCEBYTES = NaCl::XSALSA20_POLY1305_SECRETBOX_NONCEBYTES - - # Create a new SecretBox - # - # Sets up the Box with a secret key fro encrypting and decrypting messages. - # - # @param key [String] The key to encrypt and decrypt with - # - # @raise [RbNaCl::LengthError] on invalid keys - # - # @return [RbNaCl::SecretBox] The new Box, ready to use - def initialize(key) - @key = Util.check_string(key, KEYBYTES, "Secret key") - end - - # Encrypts a message - # - # Encrypts the message with the given nonce to the key set up when - # initializing the class. Make sure the nonce is unique for any given - # key, or you might as well just send plain text. - # - # This function takes care of the padding required by the NaCL C API. - # - # @param nonce [String] A 24-byte string containing the nonce. - # @param message [String] The message to be encrypted. - # - # @raise [RbNaCl::LengthError] If the nonce is not valid - # - # @return [String] The ciphertext without the nonce prepended (BINARY encoded) - def box(nonce, message) - Util.check_length(nonce, nonce_bytes, "Nonce") - msg = Util.prepend_zeros(NaCl::ZEROBYTES, message) - ct = Util.zeros(msg.bytesize) - - NaCl.crypto_secretbox_xsalsa20poly1305(ct, msg, msg.bytesize, nonce, @key) || raise(CryptoError, "Encryption failed") - Util.remove_zeros(NaCl::BOXZEROBYTES, ct) - end - alias encrypt box - - # Decrypts a ciphertext - # - # Decrypts the ciphertext with the given nonce using the key setup when - # initializing the class. - # - # This function takes care of the padding required by the NaCL C API. - # - # @param nonce [String] A 24-byte string containing the nonce. - # @param ciphertext [String] The message to be decrypted. - # - # @raise [RbNaCl::LengthError] If the nonce is not valid - # @raise [RbNaCl::CryptoError] If the ciphertext cannot be authenticated. - # - # @return [String] The decrypted message (BINARY encoded) - def open(nonce, ciphertext) - Util.check_length(nonce, nonce_bytes, "Nonce") - ct = Util.prepend_zeros(NaCl::BOXZEROBYTES, ciphertext) - message = Util.zeros(ct.bytesize) - - NaCl.crypto_secretbox_xsalsa20poly1305_open(message, ct, ct.bytesize, nonce, @key) || raise(CryptoError, "Decryption failed. Ciphertext failed verification.") - Util.remove_zeros(NaCl::ZEROBYTES, message) - end - alias decrypt open - - # The crypto primitive for the SecretBox class - # - # @return [Symbol] The primitive used - def self.primitive; :xsalsa20_poly1305; end - - # The crypto primitive for the SecretBox instance - # - # @return [Symbol] The primitive used - def primitive; self.class.primitive; end - - # The nonce bytes for the SecretBox class - # - # @return [Integer] The number of bytes in a valid nonce - def self.nonce_bytes; NONCEBYTES; end - - # The nonce bytes for the SecretBox instance - # - # @return [Integer] The number of bytes in a valid nonce - def nonce_bytes; NONCEBYTES; end - - # The key bytes for the SecretBox class - # - # @return [Integer] The number of bytes in a valid key - def self.key_bytes; KEYBYTES; end - - # The key bytes for the SecretBox instance - # - # @return [Integer] The number of bytes in a valid key - def key_bytes; KEYBYTES; end - end -end diff --git a/lib/rbnacl/self_test.rb b/lib/rbnacl/self_test.rb index 51d646a..8945a1e 100644 --- a/lib/rbnacl/self_test.rb +++ b/lib/rbnacl/self_test.rb @@ -127,8 +127,8 @@ def hmac_test(klass, tag) RbNaCl::SelfTest.secret_box_test RbNaCl::SelfTest.digital_signature_test RbNaCl::SelfTest.sha256_test -RbNaCl::SelfTest.hmac_test RbNaCl::HMAC::SHA256, :auth_hmacsha256 -RbNaCl::SelfTest.hmac_test RbNaCl::HMAC::SHA512256, :auth_hmacsha512256 -RbNaCl::SelfTest.hmac_test RbNaCl::Auth::OneTime, :auth_onetime +RbNaCl::SelfTest.hmac_test RbNaCl::SHA256::HMAC, :auth_hmacsha256 +RbNaCl::SelfTest.hmac_test RbNaCl::SHA512256::HMAC, :auth_hmacsha512256 +RbNaCl::SelfTest.hmac_test RbNaCl::OneTimeAuth, :auth_onetime puts "POST Completed in #{Time.now - start} s" if $DEBUG diff --git a/lib/rbnacl/hmac/sha256.rb b/lib/rbnacl/sha256/hmac.rb similarity index 97% rename from lib/rbnacl/hmac/sha256.rb rename to lib/rbnacl/sha256/hmac.rb index c496832..6448c72 100644 --- a/lib/rbnacl/hmac/sha256.rb +++ b/lib/rbnacl/sha256/hmac.rb @@ -1,6 +1,6 @@ # encoding: binary module RbNaCl - module HMAC + module SHA256 # Computes an authenticator as HMAC-SHA-256 # # The authenticator can be used at a later time to verify the provenance of @@ -12,7 +12,7 @@ module HMAC # can also create them. # # @see http://nacl.cr.yp.to/auth.html - class SHA256 < Auth + class HMAC < Auth # Number of bytes in a valid key KEYBYTES = NaCl::HMACSHA256_KEYBYTES diff --git a/lib/rbnacl/hmac/sha512256.rb b/lib/rbnacl/sha512256/hmac.rb similarity index 93% rename from lib/rbnacl/hmac/sha512256.rb rename to lib/rbnacl/sha512256/hmac.rb index 8a4d632..a055221 100644 --- a/lib/rbnacl/hmac/sha512256.rb +++ b/lib/rbnacl/sha512256/hmac.rb @@ -1,6 +1,6 @@ # encoding: binary module RbNaCl - module HMAC + module SHA512256 # Computes an authenticator as HMAC-SHA-512 truncated to 256-bits # # The authenticator can be used at a later time to verify the provenance of @@ -12,7 +12,7 @@ module HMAC # can also create them. # # @see http://nacl.cr.yp.to/auth.html - class SHA512256 < Auth + class HMAC < Auth # Number of bytes in a valid key KEYBYTES = NaCl::HMACSHA512256_KEYBYTES @@ -35,8 +35,5 @@ def verify_message(authenticator, message) NaCl.crypto_auth_hmacsha512256_verify(authenticator, message, message.bytesize, key) end end - - # TIMTOWTDI! - SHA512 = SHA512256 end end diff --git a/lib/rbnacl/xsalsa20poly1305/secret_box.rb b/lib/rbnacl/xsalsa20poly1305/secret_box.rb new file mode 100644 index 0000000..96a2d7d --- /dev/null +++ b/lib/rbnacl/xsalsa20poly1305/secret_box.rb @@ -0,0 +1,119 @@ +# encoding: binary +module RbNaCl + module XSalsa20Poly1305 + # The SecretBox class boxes and unboxes messages + # + # This class uses the given secret key to encrypt and decrypt messages. + # + # It is VITALLY important that the nonce is a nonce, i.e. it is a number used + # only once for any given pair of keys. If you fail to do this, you + # compromise the privacy of the messages encrypted. Give your nonces a + # different prefix, or have one side use an odd counter and one an even counter. + # Just make sure they are different. + # + # The ciphertexts generated by this class include a 16-byte authenticator which + # is checked as part of the decryption. An invalid authenticator will cause + # the unbox function to raise. The authenticator is not a signature. Once + # you've looked in the box, you've demonstrated the ability to create + # arbitrary valid messages, so messages you send are repudiable. For + # non-repudiable messages, sign them before or after encryption. + class SecretBox + # Number of bytes for a secret key + KEYBYTES = NaCl::XSALSA20_POLY1305_SECRETBOX_KEYBYTES + + # Number of bytes for a nonce + NONCEBYTES = NaCl::XSALSA20_POLY1305_SECRETBOX_NONCEBYTES + + # Create a new SecretBox + # + # Sets up the Box with a secret key fro encrypting and decrypting messages. + # + # @param key [String] The key to encrypt and decrypt with + # + # @raise [RbNaCl::LengthError] on invalid keys + # + # @return [RbNaCl::SecretBox] The new Box, ready to use + def initialize(key) + @key = Util.check_string(key, KEYBYTES, "Secret key") + end + + # Encrypts a message + # + # Encrypts the message with the given nonce to the key set up when + # initializing the class. Make sure the nonce is unique for any given + # key, or you might as well just send plain text. + # + # This function takes care of the padding required by the NaCL C API. + # + # @param nonce [String] A 24-byte string containing the nonce. + # @param message [String] The message to be encrypted. + # + # @raise [RbNaCl::LengthError] If the nonce is not valid + # + # @return [String] The ciphertext without the nonce prepended (BINARY encoded) + def box(nonce, message) + Util.check_length(nonce, nonce_bytes, "Nonce") + msg = Util.prepend_zeros(NaCl::ZEROBYTES, message) + ct = Util.zeros(msg.bytesize) + + NaCl.crypto_secretbox_xsalsa20poly1305(ct, msg, msg.bytesize, nonce, @key) || raise(CryptoError, "Encryption failed") + Util.remove_zeros(NaCl::BOXZEROBYTES, ct) + end + alias encrypt box + + # Decrypts a ciphertext + # + # Decrypts the ciphertext with the given nonce using the key setup when + # initializing the class. + # + # This function takes care of the padding required by the NaCL C API. + # + # @param nonce [String] A 24-byte string containing the nonce. + # @param ciphertext [String] The message to be decrypted. + # + # @raise [RbNaCl::LengthError] If the nonce is not valid + # @raise [RbNaCl::CryptoError] If the ciphertext cannot be authenticated. + # + # @return [String] The decrypted message (BINARY encoded) + def open(nonce, ciphertext) + Util.check_length(nonce, nonce_bytes, "Nonce") + ct = Util.prepend_zeros(NaCl::BOXZEROBYTES, ciphertext) + message = Util.zeros(ct.bytesize) + + NaCl.crypto_secretbox_xsalsa20poly1305_open(message, ct, ct.bytesize, nonce, @key) || raise(CryptoError, "Decryption failed. Ciphertext failed verification.") + Util.remove_zeros(NaCl::ZEROBYTES, message) + end + alias decrypt open + + # The crypto primitive for the SecretBox class + # + # @return [Symbol] The primitive used + def self.primitive; :xsalsa20_poly1305; end + + # The crypto primitive for the SecretBox instance + # + # @return [Symbol] The primitive used + def primitive; self.class.primitive; end + + # The nonce bytes for the SecretBox class + # + # @return [Integer] The number of bytes in a valid nonce + def self.nonce_bytes; NONCEBYTES; end + + # The nonce bytes for the SecretBox instance + # + # @return [Integer] The number of bytes in a valid nonce + def nonce_bytes; NONCEBYTES; end + + # The key bytes for the SecretBox class + # + # @return [Integer] The number of bytes in a valid key + def self.key_bytes; KEYBYTES; end + + # The key bytes for the SecretBox instance + # + # @return [Integer] The number of bytes in a valid key + def key_bytes; KEYBYTES; end + end + end +end diff --git a/spec/rbnacl/blake2b/hash_spec.rb b/spec/rbnacl/blake2b/hash_spec.rb new file mode 100644 index 0000000..ddd0026 --- /dev/null +++ b/spec/rbnacl/blake2b/hash_spec.rb @@ -0,0 +1,28 @@ +# encoding: binary +require 'spec_helper' + +if RbNaCl::NaCl.supported_version? :libsodium, '0.4.0' + describe RbNaCl::Blake2b::Hash do + let(:reference_string) { vector :blake2b_message } + let(:reference_string_hash) { vector :blake2b_digest } + let(:empty_string_hash) { vector :blake2b_empty } + + it "calculates the correct hash for a reference string" do + RbNaCl::Hash.blake2b(reference_string).should eq reference_string_hash + end + + it "calculates the correct hash for an empty string" do + RbNaCl::Hash.blake2b("").should eq empty_string_hash + end + + context "keyed" do + let(:reference_string) { vector :blake2b_keyed_message } + let(:reference_key) { vector :blake2b_key } + let(:reference_string_hash) { vector :blake2b_keyed_digest } + + it "calculates keyed hashes correctly" do + RbNaCl::Hash.blake2b(reference_string, :key => reference_key).should eq reference_string_hash + end + end + end +end diff --git a/spec/rbnacl/box_spec.rb b/spec/rbnacl/curve25519xsalsa20poly1305/box_spec.rb similarity index 100% rename from spec/rbnacl/box_spec.rb rename to spec/rbnacl/curve25519xsalsa20poly1305/box_spec.rb diff --git a/spec/rbnacl/keys/private_key_spec.rb b/spec/rbnacl/curve25519xsalsa20poly1305/private_key_spec.rb similarity index 100% rename from spec/rbnacl/keys/private_key_spec.rb rename to spec/rbnacl/curve25519xsalsa20poly1305/private_key_spec.rb diff --git a/spec/rbnacl/keys/public_key_spec.rb b/spec/rbnacl/curve25519xsalsa20poly1305/public_key_spec.rb similarity index 100% rename from spec/rbnacl/keys/public_key_spec.rb rename to spec/rbnacl/curve25519xsalsa20poly1305/public_key_spec.rb diff --git a/spec/rbnacl/keys/signing_key_spec.rb b/spec/rbnacl/ed25519/signing_key_spec.rb similarity index 100% rename from spec/rbnacl/keys/signing_key_spec.rb rename to spec/rbnacl/ed25519/signing_key_spec.rb diff --git a/spec/rbnacl/keys/verify_key_spec.rb b/spec/rbnacl/ed25519/verify_key_spec.rb similarity index 100% rename from spec/rbnacl/keys/verify_key_spec.rb rename to spec/rbnacl/ed25519/verify_key_spec.rb diff --git a/spec/rbnacl/hash_spec.rb b/spec/rbnacl/hash_spec.rb index dd0ce90..a2ec3d9 100644 --- a/spec/rbnacl/hash_spec.rb +++ b/spec/rbnacl/hash_spec.rb @@ -37,30 +37,4 @@ expect { RbNaCl::Hash.sha512("\0") }.to_not raise_error(/ArgumentError: string contains null byte/) end end - - if RbNaCl::NaCl.supported_version? :libsodium, '0.4.0' - context "blake2b" do - let(:reference_string) { vector :blake2b_message } - let(:reference_string_hash) { vector :blake2b_digest } - let(:empty_string_hash) { vector :blake2b_empty } - - it "calculates the correct hash for a reference string" do - RbNaCl::Hash.blake2b(reference_string).should eq reference_string_hash - end - - it "calculates the correct hash for an empty string" do - RbNaCl::Hash.blake2b("").should eq empty_string_hash - end - - context "keyed" do - let(:reference_string) { vector :blake2b_keyed_message } - let(:reference_key) { vector :blake2b_key } - let(:reference_string_hash) { vector :blake2b_keyed_digest } - - it "calculates keyed hashes correctly" do - RbNaCl::Hash.blake2b(reference_string, :key => reference_key).should eq reference_string_hash - end - end - end - end end diff --git a/spec/rbnacl/auth/one_time_spec.rb b/spec/rbnacl/poly1305/one_time_auth_spec.rb similarity index 77% rename from spec/rbnacl/auth/one_time_spec.rb rename to spec/rbnacl/poly1305/one_time_auth_spec.rb index c0b9889..5f20ea2 100644 --- a/spec/rbnacl/auth/one_time_spec.rb +++ b/spec/rbnacl/poly1305/one_time_auth_spec.rb @@ -1,7 +1,7 @@ # encoding: binary require 'spec_helper' -describe RbNaCl::Auth::OneTime do +describe RbNaCl::OneTimeAuth do let(:tag) { vector :auth_onetime } include_examples "authenticator" diff --git a/spec/rbnacl/hmac/sha256_spec.rb b/spec/rbnacl/sha256/hmac_spec.rb similarity index 78% rename from spec/rbnacl/hmac/sha256_spec.rb rename to spec/rbnacl/sha256/hmac_spec.rb index 5dec805..0785695 100644 --- a/spec/rbnacl/hmac/sha256_spec.rb +++ b/spec/rbnacl/sha256/hmac_spec.rb @@ -1,7 +1,7 @@ # encoding: binary require 'spec_helper' -describe RbNaCl::HMAC::SHA256 do +describe RbNaCl::SHA256::HMAC do let(:tag) { vector :auth_hmacsha256 } include_examples "authenticator" diff --git a/spec/rbnacl/hmac/sha512256_spec.rb b/spec/rbnacl/sha256512/hmac_spec.rb similarity index 77% rename from spec/rbnacl/hmac/sha512256_spec.rb rename to spec/rbnacl/sha256512/hmac_spec.rb index 6d86bbb..f45f33b 100644 --- a/spec/rbnacl/hmac/sha512256_spec.rb +++ b/spec/rbnacl/sha256512/hmac_spec.rb @@ -1,7 +1,7 @@ # encoding: binary require 'spec_helper' -describe RbNaCl::HMAC::SHA512256 do +describe RbNaCl::SHA512256::HMAC do let(:tag) { vector :auth_hmacsha512256 } include_examples "authenticator"