lib/lockbox/box.rb in lockbox-0.1.0 vs lib/lockbox/box.rb in lockbox-0.1.1
- old
+ new
@@ -1,54 +1,85 @@
+require "securerandom"
+
class Lockbox
class Box
- def initialize(key, algorithm: nil)
- # decode hex key
- if key.encoding != Encoding::BINARY && key =~ /\A[0-9a-f]{64}\z/i
- key = [key].pack("H*")
- end
+ def initialize(key: nil, algorithm: nil, encryption_key: nil, decryption_key: nil)
+ raise ArgumentError, "Cannot pass both key and public/private key" if key && (encryption_key || decryption_key)
+ key = decode_key(key) if key
+ encryption_key = decode_key(encryption_key) if encryption_key
+ decryption_key = decode_key(decryption_key) if decryption_key
+
algorithm ||= "aes-gcm"
case algorithm
when "aes-gcm"
+ raise ArgumentError, "Missing key" unless key
require "lockbox/aes_gcm"
@box = AES_GCM.new(key)
when "xchacha20"
+ raise ArgumentError, "Missing key" unless key
require "rbnacl"
@box = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(key)
+ when "hybrid"
+ raise ArgumentError, "Missing key" unless encryption_key || decryption_key
+ require "rbnacl"
+ @encryption_box = RbNaCl::Boxes::Curve25519XSalsa20Poly1305.new(encryption_key.slice(0, 32), encryption_key.slice(32..-1)) if encryption_key
+ @decryption_box = RbNaCl::Boxes::Curve25519XSalsa20Poly1305.new(decryption_key.slice(32..-1), decryption_key.slice(0, 32)) if decryption_key
else
raise ArgumentError, "Unknown algorithm: #{algorithm}"
end
+
+ @algorithm = algorithm
end
def encrypt(message, associated_data: nil)
- nonce = generate_nonce
- ciphertext = @box.encrypt(nonce, message, associated_data)
+ if @algorithm == "hybrid"
+ raise ArgumentError, "No public key set" unless @encryption_box
+ raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
+ nonce = generate_nonce(@encryption_box)
+ ciphertext = @encryption_box.encrypt(nonce, message)
+ else
+ nonce = generate_nonce(@box)
+ ciphertext = @box.encrypt(nonce, message, associated_data)
+ end
nonce + ciphertext
end
def decrypt(ciphertext, associated_data: nil)
- nonce, ciphertext = extract_nonce(ciphertext)
- @box.decrypt(nonce, ciphertext, associated_data)
+ if @algorithm == "hybrid"
+ raise ArgumentError, "No private key set" unless @decryption_box
+ raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
+ nonce, ciphertext = extract_nonce(@decryption_box, ciphertext)
+ @decryption_box.decrypt(nonce, ciphertext)
+ else
+ nonce, ciphertext = extract_nonce(@box, ciphertext)
+ @box.decrypt(nonce, ciphertext, associated_data)
+ end
end
- # protect key for xchacha20
+ # protect key for xchacha20 and hybrid
def inspect
to_s
end
private
- def nonce_bytes
- @box.nonce_bytes
+ def generate_nonce(box)
+ SecureRandom.random_bytes(box.nonce_bytes)
end
- def generate_nonce
- SecureRandom.random_bytes(nonce_bytes)
- end
-
- def extract_nonce(bytes)
+ def extract_nonce(box, bytes)
+ nonce_bytes = box.nonce_bytes
nonce = bytes.slice(0, nonce_bytes)
[nonce, bytes.slice(nonce_bytes..-1)]
+ end
+
+ # decode hex key
+ def decode_key(key)
+ if key.encoding != Encoding::BINARY && key =~ /\A[0-9a-f]{64,128}\z/i
+ key = [key].pack("H*")
+ end
+ key
end
end
end