Skip to content

Commit

Permalink
Merge a25bd5d into 9be368b
Browse files Browse the repository at this point in the history
  • Loading branch information
namelessjon committed Mar 13, 2013
2 parents 9be368b + a25bd5d commit 603b262
Show file tree
Hide file tree
Showing 12 changed files with 425 additions and 90 deletions.
58 changes: 31 additions & 27 deletions lib/rbnacl/box.rb
@@ -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
#
Expand Down Expand Up @@ -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
#
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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
142 changes: 142 additions & 0 deletions lib/rbnacl/box/curve25519_xsalsa20_poly1305.rb
@@ -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 [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")
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
17 changes: 9 additions & 8 deletions lib/rbnacl/nacl.rb
Expand Up @@ -46,29 +46,30 @@ def self.#{name}(*args)
: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]

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]

Expand Down
13 changes: 9 additions & 4 deletions lib/rbnacl/random_nonce_box.rb
Expand Up @@ -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
#
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
46 changes: 29 additions & 17 deletions lib/rbnacl/secret_box.rb
@@ -1,4 +1,5 @@
# encoding: binary
require 'rbnacl/secret_box/xsalsa20_poly1305'
module Crypto
# The SecretBox class boxes and unboxes messages
#
Expand All @@ -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
#
Expand All @@ -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
Expand All @@ -48,14 +70,9 @@ def initialize(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_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

Expand All @@ -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
Expand Down

0 comments on commit 603b262

Please sign in to comment.