Permalink
Browse files

Expose access to XSalsa20Poly1305 primitive

This is to start to fix #43

Explicitly expose access to the XSalsa20Poly1305 primitive from the
library.  The primitive class handles all of the input checking, and the
encryption methods.  At the moment, the code has been more or less just
copied across.  Eventually, it might make sense to factor this out into
the SecretBox module.

Also, it's possible that one or other of the method calls might return
something other than a string, augmented with additional information and
methods.
  • Loading branch information...
1 parent 9be368b commit 04eb1563eb5b30938b1322e1034a4952139cdcad @namelessjon namelessjon committed Mar 13, 2013
View
@@ -29,6 +29,9 @@ def self.#{name}(*args)
end
eos
end
+
+ module SecretBox
+ end
SHA256BYTES = 32
wrap_nacl_function :crypto_hash_sha256,
@@ -63,12 +66,13 @@ def self.#{name}(*args)
:crypto_box_curve25519xsalsa20poly1305_ref_open_afternm,
[:pointer, :pointer, :long_long, :pointer, :pointer]
- SECRETBOX_KEYBYTES = 32
- wrap_nacl_function :crypto_secretbox,
+ SecretBox::XSALSA20_POLY1305_KEYBYTES = 32
+ SecretBox::XSALSA20_POLY1305_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]
@@ -0,0 +1,102 @@
+# encoding: binary
+module Crypto
+ module NaCl
+ module SecretBox
+ # The XSalsa20Poly1305Box 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
+ # repudiatable. For non-repudiatable messages, sign them before or after
+ # encryption.
+ #
+ # * 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::SecretBox box class which will
+ # always point to the best primitive the library provides bindings for.
+ # It also provides a nicer interface, with e.g. encoding of the resulting
+ # messages as base64.
+ class XSalsa20Poly1305Box
+ # Number of bytes for a secret key
+ KEYBYTES = NaCl::SecretBox::XSALSA20_POLY1305_KEYBYTES
+ # Number of bytes for a nonce
+ NONCEBYTES = NaCl::SecretBox::XSALSA20_POLY1305_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 [Crypto::LengthError] on invalid keys
+ #
+ # @return [Crypto::SecretBox] The new Box, ready to use
+ def initialize(key)
+ @key = key
+ Util.check_length(@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 [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_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 [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_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
+ end
+ end
+ end
+end
@@ -1,4 +1,5 @@
# encoding: binary
+require 'rbnacl/nacl/secret_box/xsalsa20_poly1305_box'
module Crypto
# The SecretBox class boxes and unboxes messages
#
@@ -17,8 +18,7 @@ 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
+ attr_reader :primitive
# Create a new SecretBox
#
@@ -30,9 +30,9 @@ 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 = NaCl::SecretBox::XSalsa20Poly1305Box)
@key = Encoder[encoding].decode(key) if key
- Util.check_length(@key, KEYBYTES, "Secret key")
+ @primitive = primitive.new(@key)
end
# Encrypts a message
@@ -50,12 +50,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)
+ self.primitive.box(nonce, message)
end
alias encrypt box
@@ -74,12 +69,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)
+ self.primitive.open(nonce, ciphertext)
end
alias decrypt open
end
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Crypto::NaCl::SecretBox::XSalsa20Poly1305Box do
+
+ let (:key) { hex2bytes(Crypto::TestVectors[:secret_key]) }
+
+ context "new" do
+ it "accepts strings" do
+ expect { described_class.new(key) }.to_not raise_error(Exception)
+ end
+
+ it "raises on a nil key" do
+ expect { described_class.new(nil) }.to raise_error(Crypto::LengthError, "Secret key was nil \(Expected #{Crypto::NaCl::SECRETKEYBYTES}\)")
+ end
+
+ it "raises on a short key" do
+ expect { described_class.new("hello") }.to raise_error(Crypto::LengthError, "Secret key was 5 bytes \(Expected #{Crypto::NaCl::SECRETKEYBYTES}\)")
+ end
+ end
+
+ include_examples "box" do
+ let(:box) { described_class.new(key) }
+ end
+end
@@ -10,11 +10,11 @@
end
it "raises on a nil key" do
- expect { Crypto::SecretBox.new(nil) }.to raise_error(Crypto::LengthError, "Secret key was nil \(Expected #{Crypto::NaCl::SECRETKEYBYTES}\)")
+ expect { Crypto::SecretBox.new(nil) }.to raise_error(Crypto::LengthError, "Secret key was nil \(Expected #{Crypto::NaCl::SecretBox::XSALSA20_POLY1305_KEYBYTES}\)")
end
it "raises on a short key" do
- expect { Crypto::SecretBox.new("hello") }.to raise_error(Crypto::LengthError, "Secret key was 5 bytes \(Expected #{Crypto::NaCl::SECRETKEYBYTES}\)")
+ expect { Crypto::SecretBox.new("hello") }.to raise_error(Crypto::LengthError, "Secret key was 5 bytes \(Expected #{Crypto::NaCl::SecretBox::XSALSA20_POLY1305_KEYBYTES}\)")
end
end

0 comments on commit 04eb156

Please sign in to comment.