Permalink
Browse files

Support specification of Box primitive

This obviously defaults to Curve25519XSalsa20Poly1305, which is also the
only primitive currently implemented.

Still need to add the necessary support for changing out the Key, too.
  • Loading branch information...
1 parent e1ac68a commit ed3d9d863dd0fdf8fd1aca9d787f822d3bf541c8 @namelessjon namelessjon committed Mar 13, 2013
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
@@ -94,14 +117,9 @@ def initialize(public_key, private_key, encoding = :raw)
#
# @raise [Crypto::LengthError] If the nonce is not valid
#
- # @return [String] The ciphertext without the nonce prepended (BINARY encoded)
+ # @return [Crypto::Ciphertext] 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,143 @@
+# 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 [Crypto::Ciphertext] 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")
+ ct = Util.remove_zeros(NaCl::BOXZEROBYTES, ct)
+ Ciphertext.new(ct, primitive)
+ 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
@@ -49,20 +49,20 @@ module SecretBox
:crypto_box_curve25519xsalsa20poly1305_ref_keypair,
[:pointer, :pointer]
- NONCEBYTES = 24
+ CURVE25519_XSALSA20_POLY1305_BOX_NONCEBYTES = 24
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]
@@ -0,0 +1,42 @@
+# encoding: binary
+require 'spec_helper'
+
+describe Crypto::Box::Curve25519XSalsa20Poly1305 do
+ let(:alicepk_hex) { Crypto::TestVectors[:alice_public] }
+ let(:bobsk_hex) { Crypto::TestVectors[:bob_private] }
+
+ let(:alicepk) { hex2bytes(alicepk_hex) }
+ let(:bobsk) { hex2bytes(bobsk_hex) }
+ let(:alice_key) { Crypto::PublicKey.new(alicepk) }
+ let(:bob_key) { Crypto::PrivateKey.new(bobsk) }
+
+ context "new" do
+ it "accepts strings" do
+ expect { described_class.new(alicepk, bobsk) }.to_not raise_error(Exception)
+ end
+
+ it "accepts KeyPairs" do
+ expect { described_class.new(alice_key, bob_key) }.to_not raise_error(Exception)
+ end
+
+ it "raises on a nil public key" do
+ expect { described_class.new(nil, bobsk) }.to raise_error(Crypto::LengthError, /Public key was nil \(Expected 32\)/)
+ end
+
+ it "raises on an invalid public key" do
+ expect { described_class.new("hello", bobsk) }.to raise_error(Crypto::LengthError, /Public key was 5 bytes \(Expected 32\)/)
+ end
+
+ it "raises on a nil secret key" do
+ expect { described_class.new(alicepk, nil) }.to raise_error(Crypto::LengthError, /Private key was nil \(Expected 32\)/)
+ end
+
+ it "raises on an invalid secret key" do
+ expect { described_class.new(alicepk, "hello") }.to raise_error(Crypto::LengthError, /Private key was 5 bytes \(Expected 32\)/)
+ end
+ end
+
+ include_examples 'box' do
+ let(:box) { described_class.new(alicepk, bobsk) }
+ end
+end
@@ -33,6 +33,10 @@
it "behaves like a string for addition" do
(ciphertext + text).should == text * 2
end
+
+ it "behaves like a string for addition" do
+ (text + ciphertext).should == text * 2
+ end
it "accepts a default encoding" do
encoded_ciphertext.should == Crypto::Encoder[:hex].encode(text)
View
@@ -8,7 +8,7 @@
let(:invalid_nonce_long) { nonce + nonce } # too long!
let(:message) { hex2bytes(Crypto::TestVectors[:box_message]) }
let(:ciphertext) { hex2bytes(Crypto::TestVectors[:box_ciphertext]) }
- let (:nonce_error_regex) { /Nonce.*(Expected #{Crypto::NaCl::NONCEBYTES})/ }
+ let (:nonce_error_regex) { /Nonce.*(Expected #{box.nonce_bytes})/ }
let(:corrupt_ciphertext) { ciphertext[80] = " " } # picked at random by fair diceroll
context "box" do

0 comments on commit ed3d9d8

Please sign in to comment.