Permalink
Browse files

Merge pull request #46 from cryptosphere/primitive_specification

Primitive specification
  • Loading branch information...
namelessjon committed Mar 14, 2013
2 parents 9be368b + 8c025d1 commit 83a122eadbdffee10e65f18a4d23d1791d16c095
View
@@ -1,4 +1,5 @@
# encoding: binary
+require 'rbnacl/box/curve25519_xsalsa20_poly1305'
module Crypto
# The Box class boxes and unboxes messages between a pair of keys
#
@@ -61,6 +62,8 @@ module Crypto
# arbitrary valid messages, so messages you send are repudiatable. For
# non-repudiatable messages, sign them before or after encryption.
class Box
+ # Default primitive to use
+ DEFAULT_PRIMITIVE = Box::Curve25519XSalsa20Poly1305
# Create a new Box
#
@@ -74,11 +77,31 @@ class Box
# @raise [Crypto::LengthError] on invalid keys
#
# @return [Crypto::Box] The new Box, ready to use
- def initialize(public_key, private_key, encoding = :raw)
- @public_key = Encoder[encoding].decode(public_key) if public_key
- @private_key = Encoder[encoding].decode(private_key) if private_key
- Util.check_length(@public_key, PublicKey::BYTES, "Public key")
- Util.check_length(@private_key, PrivateKey::BYTES, "Private key")
+ def initialize(public_key, private_key, encoding = :raw, primitive = DEFAULT_PRIMITIVE)
+ public_key = Encoder[encoding].decode(public_key) if public_key
+ private_key = Encoder[encoding].decode(private_key) if private_key
+ @primitive = primitive.new(public_key, private_key)
+ end
+
+ # returns the defaul primitive for the Box class
+ #
+ # @return [Symbol] the default primitive
+ def self.primitive
+ DEFAULT_PRIMITIVE.primitive
+ end
+
+ # returns the primitive of this instance
+ #
+ # @return [Symbol] the default primitive
+ def primitive
+ @primitive.primitive
+ end
+
+ # returns the number of bytes in a nonce
+ #
+ # @return [Integer] Number of nonce bytes
+ def nonce_bytes
+ @primitive.nonce_bytes
end
# Encrypts a message
@@ -96,12 +119,7 @@ def initialize(public_key, private_key, encoding = :raw)
#
# @return [String] The ciphertext without the nonce prepended (BINARY encoded)
def box(nonce, message)
- Util.check_length(nonce, Crypto::NaCl::NONCEBYTES, "Nonce")
- msg = Util.prepend_zeros(NaCl::ZEROBYTES, message)
- ct = Util.zeros(msg.bytesize)
-
- NaCl.crypto_box_afternm(ct, msg, msg.bytesize, nonce, beforenm) || raise(CryptoError, "Encryption failed")
- Util.remove_zeros(NaCl::BOXZEROBYTES, ct)
+ @primitive.box(nonce, message)
end
alias encrypt box
@@ -120,22 +138,8 @@ def box(nonce, message)
#
# @return [String] The decrypted message (BINARY encoded)
def open(nonce, ciphertext)
- Util.check_length(nonce, Crypto::NaCl::NONCEBYTES, "Nonce")
- ct = Util.prepend_zeros(NaCl::BOXZEROBYTES, ciphertext)
- message = Util.zeros(ct.bytesize)
-
- NaCl.crypto_box_open_afternm(message, ct, ct.bytesize, nonce, beforenm) || raise(CryptoError, "Decryption failed. Ciphertext failed verification.")
- Util.remove_zeros(NaCl::ZEROBYTES, message)
+ @primitive.open(nonce, ciphertext)
end
alias decrypt open
-
- private
- def beforenm
- @k ||= begin
- k = Util.zeros(NaCl::BEFORENMBYTES)
- NaCl.crypto_box_beforenm(k, @public_key, @private_key) || raise(CryptoError, "Failed to derive shared key")
- k
- end
- end
end
end
@@ -0,0 +1,142 @@
+# encoding: binary
+module Crypto
+ class Box
+
+ # The Curve25519XSalsa20Poly1305 class boxes and opens 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:
+ #
+ #
+ # 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 repudiatable. For
+ # non-repudiatable messages, sign them before or after encryption.
+ #
+ # * Shared key derivation: Curve25519, a fast elliptic curve curve
+ # * Encryption: XSalsa20, a fast stream cipher primitive
+ # * Authentication: Poly1305, a fast one time authentication primitive
+ #
+ # **WARNING**: This class provides direct access to a low-level
+ # cryptographic method. You should not use this class without good reason
+ # and instead use the Crypto::Box box class which will always point
+ # to the best primitive the library provides bindings for. It also
+ # provides a nicer interface, with e.g. decoding of ascii-encoded keys.
+ class Curve25519XSalsa20Poly1305
+ # Number of bytes for a nonce
+ NONCEBYTES = NaCl::CURVE25519_XSALSA20_POLY1305_BOX_NONCEBYTES
+
+ # Number of bytes for the shared key
+ BEFORENMBYTES = NaCl::CURVE25519_XSALSA20_POLY1305_BOX_BEFORENMBYTES
+
+ # Create a new Curve25519XSalsa20Poly1305 box
+ #
+ # Sets up the Curve25519XSalsa20Poly1305 box for deriving the shared key and
+ # encrypting and decrypting messages.
+ #
+ # @param public_key [String,Crypto::PublicKey] The public key to encrypt to
+ # @param private_key [String,Crypto::PrivateKey] The private key to encrypt with
+ #
+ # @raise [Crypto::LengthError] on invalid keys
+ #
+ # @return [Crypto::Box] The new Curve25519XSalsa20Poly1305 box, ready to use
+ def initialize(public_key, private_key)
+ @public_key = public_key.to_s if public_key
+ @private_key = private_key.to_s if private_key
+ Util.check_length(@public_key, PublicKey::BYTES, "Public key")
+ Util.check_length(@private_key, PrivateKey::BYTES, "Private key")
+ end
+
+ # Returns the primitive name
+ #
+ # @return [Symbol] the primitive name
+ def self.primitive
+ :curve25519_xsalsa20_poly1305
+ end
+
+ # Returns the primitive name
+ #
+ # @return [Symbol] the primitive name
+ def primitive
+ self.class.primitive
+ end
+
+ # returns the number of bytes in a nonce
+ #
+ # @return [Integer] Number of nonce bytes
+ def nonce_bytes
+ NONCEBYTES
+ 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 [Crypto::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, NONCEBYTES, "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 [Crypto::LengthError] If the nonce is not valid
+ # @raise [Crypto::CryptoError] If the ciphertext cannot be authenticated.
+ #
+ # @return [String] The decrypted message (BINARY encoded)
+ def open(nonce, ciphertext)
+ Util.check_length(nonce, NONCEBYTES, "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
+
+ private
+ def beforenm
+ @k ||= begin
+ k = Util.zeros(BEFORENMBYTES)
+ NaCl.crypto_box_curve25519_xsalsa20_poly1305_beforenm(k, @public_key, @private_key) || raise(CryptoError, "Failed to derive shared key")
+ k
+ end
+ end
+ end
+ end
+end
View
@@ -46,29 +46,31 @@ def self.#{name}(*args)
:crypto_box_curve25519xsalsa20poly1305_ref_keypair,
[:pointer, :pointer]
- NONCEBYTES = 24
+ CURVE25519_XSALSA20_POLY1305_BOX_NONCEBYTES = 24
+ NONCEBYTES = CURVE25519_XSALSA20_POLY1305_BOX_NONCEBYTES
ZEROBYTES = 32
BOXZEROBYTES = 16
- BEFORENMBYTES = 32
+ CURVE25519_XSALSA20_POLY1305_BOX_BEFORENMBYTES = 32
- wrap_nacl_function :crypto_box_beforenm,
+ wrap_nacl_function :crypto_box_curve25519_xsalsa20_poly1305_beforenm,
:crypto_box_curve25519xsalsa20poly1305_ref_beforenm,
[:pointer, :pointer, :pointer]
- wrap_nacl_function :crypto_box_afternm,
+ wrap_nacl_function :crypto_box_curve25519_xsalsa20_poly1305_afternm,
:crypto_box_curve25519xsalsa20poly1305_ref_afternm,
[:pointer, :pointer, :long_long, :pointer, :pointer]
- wrap_nacl_function :crypto_box_open_afternm,
+ wrap_nacl_function :crypto_box_curve25519_xsalsa20_poly1305_open_afternm,
:crypto_box_curve25519xsalsa20poly1305_ref_open_afternm,
[:pointer, :pointer, :long_long, :pointer, :pointer]
- SECRETBOX_KEYBYTES = 32
- wrap_nacl_function :crypto_secretbox,
+ XSALSA20_POLY1305_SECRETBOX_KEYBYTES = 32
+ XSALSA20_POLY1305_SECRETBOX_NONCEBYTES = 24
+ wrap_nacl_function :crypto_secretbox_xsalsa20poly1305,
:crypto_secretbox_xsalsa20poly1305_ref,
[:pointer, :pointer, :long_long, :pointer, :pointer]
- wrap_nacl_function :crypto_secretbox_open,
+ wrap_nacl_function :crypto_secretbox_xsalsa20poly1305_open,
:crypto_secretbox_xsalsa20poly1305_ref_open,
[:pointer, :pointer, :long_long, :pointer, :pointer]
@@ -27,8 +27,6 @@ module Crypto
# there is no protection against messages being reordered and replayed by an
# active adversary.
class RandomNonceBox
- # the size of the nonce
- NONCEBYTES = NaCl::NONCEBYTES
# Create a new RandomNonceBox
#
@@ -64,6 +62,13 @@ def self.from_keypair(private_key, public_key)
new(Box.new(private_key, public_key))
end
+ # Returns the primitive of the underlying box
+ #
+ # @return [Symbol] The primitive of the underlying box
+ def primitive
+ @box.primitive
+ end
+
# Encrypts the message with a random nonce
#
# Encrypts the message with a random nonce, then returns the ciphertext with
@@ -98,11 +103,11 @@ def open(enciphered_message, encoding = :raw)
private
def generate_nonce
- Random.random_bytes(NONCEBYTES)
+ Random.random_bytes(@box.nonce_bytes)
end
def extract_nonce(bytes)
- nonce = bytes.slice!(0, NONCEBYTES)
+ nonce = bytes.slice!(0, @box.nonce_bytes)
[nonce, bytes]
end
end
View
@@ -1,4 +1,5 @@
# encoding: binary
+require 'rbnacl/secret_box/xsalsa20_poly1305'
module Crypto
# The SecretBox class boxes and unboxes messages
#
@@ -17,8 +18,8 @@ module Crypto
# arbitrary valid messages, so messages you send are repudiatable. For
# non-repudiatable messages, sign them before or after encryption.
class SecretBox
- # Number of bytes for a secret key
- KEYBYTES = NaCl::SECRETBOX_KEYBYTES
+ # The default primitive to use
+ DEFAULT_PRIMITIVE = SecretBox::XSalsa20Poly1305
# Create a new SecretBox
#
@@ -30,9 +31,30 @@ class SecretBox
# @raise [Crypto::LengthError] on invalid keys
#
# @return [Crypto::SecretBox] The new Box, ready to use
- def initialize(key, encoding = :raw)
+ def initialize(key, encoding = :raw, primitive = DEFAULT_PRIMITIVE)
@key = Encoder[encoding].decode(key) if key
- Util.check_length(@key, KEYBYTES, "Secret key")
+ @primitive = primitive.new(@key)
+ end
+
+ # returns the defaul primitive for the SecretBox class
+ #
+ # @return [Symbol] the default primitive
+ def self.primitive
+ DEFAULT_PRIMITIVE.primitive
+ end
+
+ # returns the primitive of this instance
+ #
+ # @return [Symbol] the default primitive
+ def primitive
+ @primitive.primitive
+ end
+
+ # returns the number of bytes in a nonce
+ #
+ # @return [Integer] Number of nonce bytes
+ def nonce_bytes
+ @primitive.nonce_bytes
end
# Encrypts a message
@@ -50,12 +72,7 @@ def initialize(key, encoding = :raw)
#
# @return [String] The ciphertext without the nonce prepended (BINARY encoded)
def box(nonce, message)
- Util.check_length(nonce, Crypto::NaCl::NONCEBYTES, "Nonce")
- msg = Util.prepend_zeros(NaCl::ZEROBYTES, message)
- ct = Util.zeros(msg.bytesize)
-
- NaCl.crypto_secretbox(ct, msg, msg.bytesize, nonce, @key) || raise(CryptoError, "Encryption failed")
- Util.remove_zeros(NaCl::BOXZEROBYTES, ct)
+ @primitive.box(nonce, message)
end
alias encrypt box
@@ -74,12 +91,7 @@ def box(nonce, message)
#
# @return [String] The decrypted message (BINARY encoded)
def open(nonce, ciphertext)
- Util.check_length(nonce, Crypto::NaCl::NONCEBYTES, "Nonce")
- ct = Util.prepend_zeros(NaCl::BOXZEROBYTES, ciphertext)
- message = Util.zeros(ct.bytesize)
-
- NaCl.crypto_secretbox_open(message, ct, ct.bytesize, nonce, @key) || raise(CryptoError, "Decryption failed. Ciphertext failed verification.")
- Util.remove_zeros(NaCl::ZEROBYTES, message)
+ @primitive.open(nonce, ciphertext)
end
alias decrypt open
end
Oops, something went wrong.

0 comments on commit 83a122e

Please sign in to comment.